@@ -109,6 +109,8 @@ import {
109109 _BinaryenFunctionSetBody ,
110110 _BinaryenGetExport ,
111111 _BinaryenGetFunction ,
112+ _BinaryenGetFunctionByIndex ,
113+ _BinaryenGetNumFunctions ,
112114 _BinaryenLocalSetGetIndex ,
113115 _BinaryenLocalSetGetValue ,
114116 _BinaryenLocalSetIsTee ,
@@ -322,7 +324,7 @@ export class ShadowStackPass extends Pass {
322324 /** Makes a check that the current stack pointer is valid. */
323325 makeStackCheck ( ) : ExpressionRef {
324326 let module = this . module ;
325- if ( ! this . hasStackCheckFunction ) {
327+ if ( ! this . hasStackCheckFunction && ! module . hasFunction ( "~stack_check" ) ) {
326328 this . hasStackCheckFunction = true ;
327329 module . addFunction ( "~stack_check" , TypeRef . None , TypeRef . None , null ,
328330 module . if (
@@ -573,65 +575,96 @@ export class ShadowStackPass extends Pass {
573575
574576 /** @override */
575577 walkModule ( ) : void {
576- // Run the pass normally
577- super . walkModule ( ) ;
578-
579- // Instrument returns in functions utilizing stack slots
578+ // Walk functions in a loop until no new functions are added.
579+ // This is necessary because transforming __tostack calls and instrumenting
580+ // functions may trigger compilation of new functions (e.g., via makeStackCheck -> makeStaticAbort).
581+ let moduleRef = this . module . ref ;
580582 let module = this . module ;
581- let instrumentReturns = new InstrumentReturns ( this ) ;
582- for ( let _keys = Map_keys ( this . slotMaps ) , i = 0 , k = _keys . length ; i < k ; ++ i ) {
583- let func = _keys [ i ] ;
584- let slotMap = changetype < SlotMap > ( this . slotMaps . get ( func ) ) ;
585- let frameSize = slotMap . size * this . ptrSize ;
583+ let lastNumFunctions : Index = 0 ;
584+ let iteration = 0 ;
586585
587- // Instrument function returns
588- instrumentReturns . frameSize = frameSize ;
589- instrumentReturns . walkFunction ( func ) ;
586+ // Set of functions that have already been instrumented
587+ let instrumentedFunctions = new Set < FunctionRef > ( ) ;
590588
591- // Instrument function entry
592- let stmts = new Array < ExpressionRef > ( ) ;
593- // __stack_pointer -= frameSize
594- stmts . push (
595- this . makeStackOffset ( - frameSize )
596- ) ;
597- // memory.fill(__stack_pointer, 0, frameSize)
598- this . makeStackFill ( frameSize , stmts ) ;
599-
600- // Handle implicit return
601- let body = _BinaryenFunctionGetBody ( func ) ;
602- let bodyType = _BinaryenExpressionGetType ( body ) ;
603- if ( bodyType == TypeRef . Unreachable ) {
604- // body
605- stmts . push (
606- body
607- ) ;
608- } else if ( bodyType == TypeRef . None ) {
609- // body
610- stmts . push (
611- body
612- ) ;
613- // __stack_pointer += frameSize
614- stmts . push (
615- this . makeStackOffset ( + frameSize )
616- ) ;
617- } else {
618- let temp = this . getSharedTemp ( func , bodyType ) ;
619- // t = body
620- stmts . push (
621- module . local_set ( temp , body , false )
622- ) ;
623- // __stack_pointer += frameSize
624- stmts . push (
625- this . makeStackOffset ( + frameSize )
626- ) ;
627- // -> t
589+ while ( true ) {
590+ let currentNumFunctions = _BinaryenGetNumFunctions ( moduleRef ) ;
591+ if ( currentNumFunctions == lastNumFunctions ) break ;
592+
593+ // Walk only the newly added functions (from lastNumFunctions to currentNumFunctions)
594+ for ( let i = lastNumFunctions ; i < currentNumFunctions ; ++ i ) {
595+ this . walkFunction ( _BinaryenGetFunctionByIndex ( moduleRef , i ) ) ;
596+ }
597+
598+ // Instrument returns and entries for functions with slots that haven't been instrumented yet
599+ let instrumentReturns = new InstrumentReturns ( this ) ;
600+ for ( let _keys = Map_keys ( this . slotMaps ) , i = 0 , k = _keys . length ; i < k ; ++ i ) {
601+ let func = _keys [ i ] ;
602+ if ( instrumentedFunctions . has ( func ) ) continue ;
603+ instrumentedFunctions . add ( func ) ;
604+
605+ let slotMap = changetype < SlotMap > ( this . slotMaps . get ( func ) ) ;
606+ let frameSize = slotMap . size * this . ptrSize ;
607+
608+ // Instrument function returns
609+ instrumentReturns . frameSize = frameSize ;
610+ instrumentReturns . walkFunction ( func ) ;
611+
612+ // Instrument function entry
613+ let stmts = new Array < ExpressionRef > ( ) ;
614+ // __stack_pointer -= frameSize
628615 stmts . push (
629- module . local_get ( temp , bodyType )
616+ this . makeStackOffset ( - frameSize )
630617 ) ;
618+ // memory.fill(__stack_pointer, 0, frameSize)
619+ this . makeStackFill ( frameSize , stmts ) ;
620+
621+ // Handle implicit return
622+ let body = _BinaryenFunctionGetBody ( func ) ;
623+ let bodyType = _BinaryenExpressionGetType ( body ) ;
624+ if ( bodyType == TypeRef . Unreachable ) {
625+ // body
626+ stmts . push (
627+ body
628+ ) ;
629+ } else if ( bodyType == TypeRef . None ) {
630+ // body
631+ stmts . push (
632+ body
633+ ) ;
634+ // __stack_pointer += frameSize
635+ stmts . push (
636+ this . makeStackOffset ( + frameSize )
637+ ) ;
638+ } else {
639+ let temp = this . getSharedTemp ( func , bodyType ) ;
640+ // t = body
641+ stmts . push (
642+ module . local_set ( temp , body , false )
643+ ) ;
644+ // __stack_pointer += frameSize
645+ stmts . push (
646+ this . makeStackOffset ( + frameSize )
647+ ) ;
648+ // -> t
649+ stmts . push (
650+ module . local_get ( temp , bodyType )
651+ ) ;
652+ }
653+ _BinaryenFunctionSetBody ( func , module . flatten ( stmts , bodyType ) ) ;
654+ }
655+
656+ lastNumFunctions = currentNumFunctions ;
657+ iteration ++ ;
658+
659+ // Safety limit to prevent infinite loops
660+ if ( iteration > 100 ) {
661+ throw new Error ( "ShadowStackPass: too many iterations, possible infinite loop" ) ;
631662 }
632- _BinaryenFunctionSetBody ( func , module . flatten ( stmts , bodyType ) ) ;
633663 }
634664
665+ // Walk globals (only once, since they don't trigger new function compilation)
666+ this . walkGlobals ( ) ;
667+
635668 // Update functions we added more locals to
636669 // TODO: _BinaryenFunctionAddVar ?
637670 for ( let _keys = Map_keys ( this . tempMaps ) , i = 0 , k = _keys . length ; i < k ; ++ i ) {
0 commit comments