11use crate :: bytecompiler:: ByteCompiler ;
2- #[ cfg( not( feature = "experimental" ) ) ]
2+ #[ cfg( feature = "experimental" ) ]
3+ use crate :: bytecompiler:: jump_control:: JumpControlInfoFlags ;
34use boa_ast:: statement:: Block ;
45#[ cfg( feature = "experimental" ) ]
56use boa_ast:: {
67 declaration:: LexicalDeclaration ,
78 operations:: { LexicallyScopedDeclaration , lexically_scoped_declarations} ,
8- statement:: Block ,
99} ;
1010
1111impl ByteCompiler < ' _ > {
1212 /// Compile a [`Block`] `boa_ast` node
1313 pub ( crate ) fn compile_block ( & mut self , block : & Block , use_expr : bool ) {
14- let scope = self . push_declarative_scope ( block. scope ( ) ) ;
15- self . block_declaration_instantiation ( block) ;
16-
17- // Count how many `using` bindings are in this block (statically known at compile time)
14+ // Check if this block has `using` declarations
1815 #[ cfg( feature = "experimental" ) ]
1916 let using_count: u32 = lexically_scoped_declarations ( block)
2017 . iter ( )
@@ -30,13 +27,100 @@ impl ByteCompiler<'_> {
3027 } )
3128 . sum ( ) ;
3229
33- self . compile_statement_list ( block. statement_list ( ) , use_expr, true ) ;
34-
35- // Emit DisposeResources with the static count if there are any using declarations
3630 #[ cfg( feature = "experimental" ) ]
37- if using_count > 0 {
38- self . bytecode . emit_dispose_resources ( using_count. into ( ) ) ;
31+ let has_using = using_count > 0 ;
32+
33+ #[ cfg( not( feature = "experimental" ) ) ]
34+ let has_using = false ;
35+
36+ if has_using {
37+ // Blocks with `using` declarations need try-finally semantics
38+ // to ensure disposal happens on all exit paths (normal, exception, break, continue, return)
39+ self . compile_block_with_disposal (
40+ block,
41+ use_expr,
42+ #[ cfg( feature = "experimental" ) ]
43+ using_count,
44+ ) ;
45+ } else {
46+ // Normal block compilation
47+ self . compile_block_without_disposal ( block, use_expr) ;
3948 }
49+ }
50+
51+ /// Compile a block without disposal semantics (normal block)
52+ fn compile_block_without_disposal ( & mut self , block : & Block , use_expr : bool ) {
53+ let scope = self . push_declarative_scope ( block. scope ( ) ) ;
54+ self . block_declaration_instantiation ( block) ;
55+ self . compile_statement_list ( block. statement_list ( ) , use_expr, true ) ;
56+ self . pop_declarative_scope ( scope) ;
57+ }
58+
59+ /// Compile a block with disposal semantics (block with `using` declarations)
60+ ///
61+ /// This wraps the block in try-finally semantics to ensure resources are disposed
62+ /// on all exit paths: normal completion, exceptions, and early exits (break/continue/return).
63+ #[ cfg( feature = "experimental" ) ]
64+ fn compile_block_with_disposal ( & mut self , block : & Block , use_expr : bool , using_count : u32 ) {
65+ let scope = self . push_declarative_scope ( block. scope ( ) ) ;
66+ self . block_declaration_instantiation ( block) ;
67+
68+ // Allocate registers for finally control flow (same pattern as try-finally)
69+ let finally_re_throw = self . register_allocator . alloc ( ) ;
70+ let finally_jump_index = self . register_allocator . alloc ( ) ;
71+
72+ self . bytecode . emit_store_true ( finally_re_throw. variable ( ) ) ;
73+ self . bytecode . emit_store_zero ( finally_jump_index. variable ( ) ) ;
74+
75+ // Push jump control info to handle break/continue/return through disposal
76+ self . push_try_with_finally_control_info ( & finally_re_throw, & finally_jump_index, use_expr) ;
77+
78+ // Push exception handler to catch any exceptions during block execution
79+ let handler = self . push_handler ( ) ;
80+
81+ // Compile the block body (this includes the `using` declarations)
82+ self . compile_statement_list ( block. statement_list ( ) , use_expr, true ) ;
83+
84+ // Normal exit: mark that we don't need to re-throw
85+ self . bytecode . emit_store_false ( finally_re_throw. variable ( ) ) ;
86+
87+ let finally_jump = self . jump ( ) ;
88+
89+ // Exception path: patch the handler
90+ self . patch_handler ( handler) ;
91+
92+ let catch_handler = self . push_handler ( ) ;
93+ let error = self . register_allocator . alloc ( ) ;
94+ self . bytecode . emit_exception ( error. variable ( ) ) ;
95+ self . bytecode . emit_store_true ( finally_re_throw. variable ( ) ) ;
96+
97+ let no_throw = self . jump ( ) ;
98+ self . patch_handler ( catch_handler) ;
99+
100+ self . patch_jump ( no_throw) ;
101+ self . patch_jump ( finally_jump) ;
102+
103+ // Finally block: dispose resources
104+ let finally_start = self . next_opcode_location ( ) ;
105+ self . jump_info
106+ . last_mut ( )
107+ . expect ( "there should be a jump control info" )
108+ . flags |= JumpControlInfoFlags :: IN_FINALLY ;
109+
110+ // Emit disposal logic
111+ self . bytecode . emit_dispose_resources ( using_count. into ( ) ) ;
112+
113+ // Re-throw if there was an exception
114+ let do_not_throw_exit = self . jump_if_false ( & finally_re_throw) ;
115+ self . bytecode . emit_throw ( error. variable ( ) ) ;
116+ self . register_allocator . dealloc ( error) ;
117+ self . patch_jump ( do_not_throw_exit) ;
118+
119+ // Pop jump control info (this handles break/continue/return via jump table)
120+ self . pop_try_with_finally_control_info ( finally_start) ;
121+
122+ self . register_allocator . dealloc ( finally_re_throw) ;
123+ self . register_allocator . dealloc ( finally_jump_index) ;
40124
41125 self . pop_declarative_scope ( scope) ;
42126 }
0 commit comments