@@ -564,3 +564,64 @@ func TestCanonicalizer_BlockOrderingStability(t *testing.T) {
564564 }
565565 }
566566}
567+
568+ func TestCanonicalizer_HoistSafety_Chan (t * testing.T ) {
569+ t .Parallel ()
570+
571+ // Test len(chan) and cap(chan) are not hoisted.
572+ src := `package main
573+ func foo(c chan int) int {
574+ sum := 0
575+ for i := 0; i < 10; i++ {
576+ c <- i
577+ sum += len(c) // This must NOT be hoisted
578+ sum += cap(c) // This must NOT be hoisted
579+ }
580+ return sum
581+ }`
582+
583+ fn := testutil .CompileAndGetFunction (t , src , "foo" )
584+
585+ c := ir .NewCanonicalizer (ir .DefaultLiteralPolicy )
586+ defer ir .ReleaseCanonicalizer (c )
587+
588+ out := c .CanonicalizeFunction (fn )
589+
590+ lines := strings .Split (out , "\n " )
591+ var loopHeaderIdx int
592+ var lenCallIdx int = - 1
593+ var capCallIdx int = - 1
594+ loopFound := false
595+
596+ for i , line := range lines {
597+ if strings .Contains (line , "; LoopHeader" ) {
598+ loopHeaderIdx = i
599+ loopFound = true
600+ }
601+ if strings .Contains (line , "Call <builtin:len>" ) {
602+ lenCallIdx = i
603+ }
604+ if strings .Contains (line , "Call <builtin:cap>" ) {
605+ capCallIdx = i
606+ }
607+ }
608+
609+ if ! loopFound {
610+ t .Fatal ("Failed to detect LoopHeader in canonical output" )
611+ }
612+
613+ if lenCallIdx == - 1 {
614+ t .Fatal ("Failed to find len() call in canonical output" )
615+ }
616+ if capCallIdx == - 1 {
617+ t .Fatal ("Failed to find cap() call in canonical output" )
618+ }
619+
620+ // If len() or cap() was hoisted, it would appear in the pre-header (before loop header).
621+ if lenCallIdx < loopHeaderIdx {
622+ t .Errorf ("Security Flaw: len(chan) was incorrectly hoisted out of the loop." )
623+ }
624+ if capCallIdx < loopHeaderIdx {
625+ t .Errorf ("Security Flaw: cap(chan) was incorrectly hoisted out of the loop." )
626+ }
627+ }
0 commit comments