@@ -498,6 +498,135 @@ class AttrRowTests
498498 }
499499 }
500500
501+ TEST_METHOD (TestReverseIteratorWalkFromMiddle)
502+ {
503+ // GH #3409, walking backwards through color range runs out of bounds
504+ // We're going to create an attribute row with assorted colors and varying lengths
505+ // just like the row of text on the Ubuntu prompt line that triggered this bug being found.
506+ // Then we're going to walk backwards through the iterator like a selection-expand-to-left
507+ // operation and ensure we don't run off the bounds.
508+
509+ // walk the chain, from index, stepSize at a time
510+ // ensure we don't crash
511+ auto testWalk = [](ATTR_ROW* chain, size_t index, int stepSize) {
512+ // move to starting index
513+ auto iter = chain->cbegin ();
514+ iter += index;
515+
516+ // Now walk backwards in a loop until 0.
517+ while (iter)
518+ {
519+ iter -= stepSize;
520+ }
521+
522+ Log::Comment (L" We made it through without crashing!" );
523+ };
524+
525+ // take one step of size stepSize on the chain
526+ // index is where we start from
527+ // expectedAttribute is what we expect to read here
528+ auto verifyStep = [](ATTR_ROW* chain, size_t index, int stepSize, TextAttribute expectedAttribute) {
529+ // move to starting index
530+ auto iter = chain->cbegin ();
531+ iter += index;
532+
533+ // Now step backwards
534+ iter -= stepSize;
535+
536+ VERIFY_ARE_EQUAL (expectedAttribute, *iter);
537+ };
538+
539+ Log::Comment (L" Reverse iterate through ubuntu prompt" );
540+ {
541+ // Create attr row representing a buffer that's 121 wide.
542+ auto chain = std::make_unique<ATTR_ROW>(121 , _DefaultAttr);
543+
544+ // The repro case had 4 chain segments.
545+ chain->_list .resize (4 );
546+
547+ // The color 10 went for the first 18.
548+ chain->_list [0 ].SetAttributes (TextAttribute (0xA ));
549+ chain->_list [0 ].SetLength (18 );
550+
551+ // Default color for the next 1
552+ chain->_list [1 ].SetAttributes (TextAttribute ());
553+ chain->_list [1 ].SetLength (1 );
554+
555+ // Color 12 for the next 29
556+ chain->_list [2 ].SetAttributes (TextAttribute (0xC ));
557+ chain->_list [2 ].SetLength (29 );
558+
559+ // Then default color to end the run
560+ chain->_list [3 ].SetAttributes (TextAttribute ());
561+ chain->_list [3 ].SetLength (73 );
562+
563+ // The sum of the lengths should be 121.
564+ VERIFY_ARE_EQUAL (chain->_cchRowWidth , chain->_list [0 ]._cchLength + chain->_list [1 ]._cchLength + chain->_list [2 ]._cchLength + chain->_list [3 ]._cchLength );
565+
566+ auto index = chain->_list [0 ].GetLength ();
567+ auto stepSize = 1 ;
568+ testWalk (chain.get (), index, stepSize);
569+ }
570+
571+ Log::Comment (L" Reverse iterate across a text run in the chain" );
572+ {
573+ // Create attr row representing a buffer that's 3 wide.
574+ auto chain = std::make_unique<ATTR_ROW>(3 , _DefaultAttr);
575+
576+ // The repro case had 3 chain segments.
577+ chain->_list .resize (3 );
578+
579+ // The color 10 went for the first 1.
580+ chain->_list [0 ].SetAttributes (TextAttribute (0xA ));
581+ chain->_list [0 ].SetLength (1 );
582+
583+ // The color 11 for the next 1
584+ chain->_list [1 ].SetAttributes (TextAttribute (0xB ));
585+ chain->_list [1 ].SetLength (1 );
586+
587+ // Color 12 for the next 1
588+ chain->_list [2 ].SetAttributes (TextAttribute (0xC ));
589+ chain->_list [2 ].SetLength (1 );
590+
591+ // The sum of the lengths should be 3.
592+ VERIFY_ARE_EQUAL (chain->_cchRowWidth , chain->_list [0 ]._cchLength + chain->_list [1 ]._cchLength + chain->_list [2 ]._cchLength );
593+
594+ // on 'ABC', step from B to A
595+ auto index = 1 ;
596+ auto stepSize = 1 ;
597+ verifyStep (chain.get (), index, stepSize, TextAttribute (0xA ));
598+ }
599+
600+ Log::Comment (L" Reverse iterate across two text runs in the chain" );
601+ {
602+ // Create attr row representing a buffer that's 3 wide.
603+ auto chain = std::make_unique<ATTR_ROW>(3 , _DefaultAttr);
604+
605+ // The repro case had 3 chain segments.
606+ chain->_list .resize (3 );
607+
608+ // The color 10 went for the first 1.
609+ chain->_list [0 ].SetAttributes (TextAttribute (0xA ));
610+ chain->_list [0 ].SetLength (1 );
611+
612+ // The color 11 for the next 1
613+ chain->_list [1 ].SetAttributes (TextAttribute (0xB ));
614+ chain->_list [1 ].SetLength (1 );
615+
616+ // Color 12 for the next 1
617+ chain->_list [2 ].SetAttributes (TextAttribute (0xC ));
618+ chain->_list [2 ].SetLength (1 );
619+
620+ // The sum of the lengths should be 3.
621+ VERIFY_ARE_EQUAL (chain->_cchRowWidth , chain->_list [0 ]._cchLength + chain->_list [1 ]._cchLength + chain->_list [2 ]._cchLength );
622+
623+ // on 'ABC', step from C to A
624+ auto index = 2 ;
625+ auto stepSize = 2 ;
626+ verifyStep (chain.get (), index, stepSize, TextAttribute (0xA ));
627+ }
628+ }
629+
501630 TEST_METHOD (TestSetAttrToEnd)
502631 {
503632 const WORD wTestAttr = FOREGROUND_BLUE | BACKGROUND_GREEN;
0 commit comments