@@ -586,7 +586,7 @@ public void pruneExceptionUnmarksVersionsAtNonZeroOffset() throws Exception {
586586 fail ("expected CorruptValueException" );
587587 } catch (final CorruptValueException expected ) {
588588 }
589- assertArrayEquals ("prune must leave the byte array unchanged when it throws" , before , bytes );
589+ assertArrayEquals ("prune must leave an MVV without stale marks unchanged when it throws" , before , bytes );
590590 }
591591
592592 /**
@@ -740,6 +740,44 @@ public void pruneStaleMarkedVersionPrunedInMultiVersionPath() throws Exception {
740740 }
741741 }
742742
743+ /**
744+ * The up-front sweep must not follow a corrupted length field: a
745+ * misaligned traversal lands inside a value and unmark() clears bit 15 of
746+ * a payload byte — inside the MVV region, past what
747+ * {@link #pruneExceptionMustNotUnmarkPastRegionEnd} guards. Prune must
748+ * reject the malformed region without writing anything.
749+ */
750+ @ Test
751+ public void pruneMustNotSweepMisalignedMvv () throws Exception {
752+ final TimestampAllocator tsa = new TimestampAllocator ();
753+ final TransactionIndex ti = new TransactionIndex (tsa , 1 );
754+
755+ /*
756+ * Two aborted versions, so no pass marks anything and any byte that
757+ * differs after prune was written by the sweep. The 0xFF fill of the
758+ * second value makes a misaligned unmark() visible (bit 15 cleared).
759+ */
760+ final int offset = 7 ;
761+ final byte [] bytes = new byte [offset + 100 ];
762+ final byte [] v1 = { 0x1 , 0x2 , 0x3 , 0x4 };
763+ final byte [] v2 = new byte [20 ];
764+ Arrays .fill (v2 , (byte ) 0xFF );
765+ int length = storeAbortedVersion (tsa , ti , bytes , offset , -1 , v1 );
766+ length = storeAbortedVersion (tsa , ti , bytes , offset , length , v2 );
767+ ti .updateActiveTransactionCache ();
768+
769+ /* Corrupt the first version's length field: it claims 9 bytes, not 4. */
770+ MVV .putLength (bytes , offset + 1 , 9 );
771+
772+ final byte [] before = bytes .clone ();
773+ try {
774+ MVV .prune (bytes , offset , length , ti , true , new ArrayList <MVV .PrunedVersion >());
775+ fail ("expected CorruptValueException" );
776+ } catch (final CorruptValueException expected ) {
777+ }
778+ assertArrayEquals ("prune must not write into a misaligned MVV region" , before , bytes );
779+ }
780+
743781 //
744782 // Test helper methods
745783 //
@@ -760,6 +798,22 @@ private static int storeCommittedVersion(final TimestampAllocator tsa, final Tra
760798 return newLength ;
761799 }
762800
801+ /**
802+ * Store <code>value</code> as a new version created by a registered
803+ * transaction and abort it, so that {@link MVV#prune} sees an aborted
804+ * version and marks nothing.
805+ */
806+ private static int storeAbortedVersion (final TimestampAllocator tsa , final TransactionIndex ti ,
807+ final byte [] bytes , final int offset , final int length , final byte [] value ) throws Exception {
808+ final TransactionStatus status = ti .registerTransaction ();
809+ final int newLength = MVV .storeVersion (bytes , offset , length , bytes .length ,
810+ TransactionIndex .ts2vh (status .getTs ()), value , 0 , value .length ) & STORE_LENGTH_MASK ;
811+ status .incrementMvvCount ();
812+ status .abort ();
813+ ti .notifyCompleted (status , tsa .updateTimestamp ());
814+ return newLength ;
815+ }
816+
763817 private static int writeArray (final byte [] array , final int ... contents ) {
764818 assert contents .length <= array .length : "Too many values for array" ;
765819 for (int i = 0 ; i < contents .length ; ++i ) {
0 commit comments