88import org .jruby .RubySymbol ;
99import org .jruby .ast .IterNode ;
1010import org .jruby .ast .StrNode ;
11- import org .jruby .common .IRubyWarnings ;
1211import org .jruby .ext .coverage .CoverageData ;
1312import org .jruby .ir .IRClassBody ;
1413import org .jruby .ir .IRClosure ;
@@ -167,44 +166,58 @@ public static InterpreterContext buildRoot(IRManager manager, ParseResult rootNo
167166 return manager .getBuilderFactory ().topIRBuilder (manager , script , rootNode ).buildRootInner (rootNode );
168167 }
169168
169+ protected int currentLine () {
170+ return lastProcessedLineNum ;
171+ }
172+
173+ // FIXME: When legacy parser goes away this should be totally rewritten as prism represents the whole structure as one node vs
174+ // legacy which has ensure as the parent to any rescue.
170175 // FIXME: consider mod_rescue, rescue, and pure ensure as separate entries
171- // Note: reference is only passed in via Prism on legacy this is desugared into AST.
172- protected Operand buildEnsureInternal (U body , U elseNode , U [] exceptions , U rescueBody , X optRescue , boolean isModifier ,
173- U ensureNode , boolean isRescue , U reference ) {
174- // Save $!
175- final Variable savedGlobalException = temp ();
176- addInstr (new GetGlobalVariableInstr (savedGlobalException , symbol ("$!" )));
177-
178- // ------------ Build the body of the ensure block ------------
179- //
176+ /**
177+ * @param body the main statements to be executed
178+ * @param elseNode else path in a begin
179+ * @param exceptions possible list of excpetions which will jump to rescueBody
180+ * @param rescueBody the statements executed if the matching exception(s) occurs
181+ * @param optRescue possible other rescue in the same begin
182+ * @param isModifier foo rescue bar
183+ * @param ensureNode ensure block
184+ * @param reference variable for the rescue (NameError => name)
185+ * @return return last operand of begin execution
186+ *
187+ * Note: reference is only passed in via Prism on legacy this is desugared into AST.
188+ */
189+ protected Operand buildEnsureInternal (U body , U elseNode , U [] exceptions , U rescueBody , X optRescue ,
190+ boolean isModifier , U ensureNode , boolean isRescue , U reference ) {
191+ var savedGlobalException = addResultInstr (new GetGlobalVariableInstr (temp (), symbol ("$!" )));
192+
180193 // The ensure code is built first so that when the protected body is being built,
181194 // the ensure code can be cloned at break/next/return sites in the protected body.
195+ EnsureBlockInfo ebi = new EnsureBlockInfo (scope , getCurrentLoop (), activeRescuers .peek (), currentLine () + 1 );
182196
183- // Push a new ensure block node onto the stack of ensure bodies being built
184- // The body's instructions are stashed and emitted later .
185- EnsureBlockInfo ebi = new EnsureBlockInfo ( scope , getCurrentLoop (), activeRescuers . peek ()) ;
197+ // Rescue will change $! but we need to restore $! later. prism: ensure and isRescue means it is both (we
198+ // call this method again below to rip those two things apart .
199+ if ( isRescue && ensureNode == null ) ebi . savedGlobalException = savedGlobalException ;
186200
187- // Record $! save var if we had a non-empty rescue node.
188- // $! will be restored from it where required.
189- if (isRescue ) ebi .savedGlobalException = savedGlobalException ;
190-
191- ensureBodyBuildStack .push (ebi );
192- Operand ensureRetVal = ensureNode == null ? nil () : build (ensureNode );
193- ensureBodyBuildStack .pop ();
201+ // Record body of ensure and push to ensure body stack if there is an actual ensure body.
202+ Operand ensureRetVal = processEnsureBody (ensureNode , ebi );
194203
195204 // ------------ Build the protected region ------------
196205 activeEnsureBlockStack .push (ebi );
197206
198207 // Start of protected region
199- addInstr (new LabelInstr (ebi .regionStart ));
200- addInstr (new ExceptionRegionStartMarkerInstr (ebi .dummyRescueBlockLabel ));
201- activeRescuers .push (ebi .dummyRescueBlockLabel );
208+ addInstr (new LabelInstr (ebi .start ));
209+ addInstr (new ExceptionRegionStartMarkerInstr (ebi .ensureRescue ));
210+ activeRescuers .push (ebi .ensureRescue );
202211
203212 // Generate IR for code being protected
204- Variable ensureExprValue = temp ();
205213 Operand rv ;
206214 if (isRescue ) {
207- rv = buildRescueInternal (body , elseNode , exceptions , rescueBody , optRescue , isModifier , ebi , reference );
215+ if (ensureNode != null ) {
216+ // both were passed in via Prism. Let's pretend they are nested like legacy where ensures enclose the rescue.
217+ rv = buildEnsureInternal (body , elseNode , exceptions , rescueBody , optRescue , isModifier , null , isRescue , reference );
218+ } else {
219+ rv = buildRescueInternal (body , elseNode , exceptions , rescueBody , optRescue , isModifier , ebi , reference );
220+ }
208221 } else {
209222 rv = build (body );
210223 }
@@ -215,8 +228,9 @@ protected Operand buildEnsureInternal(U body, U elseNode, U[] exceptions, U resc
215228
216229 // Is this a begin..(rescue..)?ensure..end node that actually computes a value?
217230 // (vs. returning from protected body)
218- boolean isEnsureExpr = ensureNode != null && rv != U_NIL && ! isRescue ;
231+ boolean isEnsureExpr = ensureNode != null && rv != U_NIL ;
219232
233+ Variable ensureExprValue = temp ();
220234 // Clone the ensure body and jump to the end
221235 if (isEnsureExpr ) {
222236 addInstr (new CopyInstr (ensureExprValue , rv ));
@@ -230,27 +244,36 @@ protected Operand buildEnsureInternal(U body, U elseNode, U[] exceptions, U resc
230244 // ------------ Emit the ensure body alongwith dummy rescue block ------------
231245 // Now build the dummy rescue block that
232246 // catches all exceptions thrown by the body
233- addInstr (new LabelInstr (ebi .dummyRescueBlockLabel ));
247+ addInstr (new LabelInstr (ebi .ensureRescue ));
234248 Variable exc = addResultInstr (new ReceiveJRubyExceptionInstr (temp ()));
235249
236250 // Now emit the ensure body's stashed instructions
237- if (ensureNode != null ) ebi .emitBody (this );
251+ if (ensureNode != null ) ebi .emitEnsureBody (this );
238252
239253 // 1. Ensure block has no explicit return => the result of the entire ensure expression is the result of the protected body.
240254 // 2. Ensure block has an explicit return => the result of the protected body is ignored.
241255 // U_NIL => there was a return from within the ensure block!
256+ // FIXME: This U_NIL case is wrong in a few cases: 'ensure; return 1 if true; end' or weirder 'ensure; return 1; true; end'
242257 if (ensureRetVal == U_NIL ) rv = U_NIL ;
243258
244- // Return (rethrow exception/end)
245- // rethrows the caught exception from the dummy ensure block
246- addInstr (new ThrowExceptionInstr (exc ));
247-
248- // End label for the exception region
249- addInstr (new LabelInstr (ebi .end ));
259+ addInstr (new ThrowExceptionInstr (exc )); // rethrows the caught exception from the dummy ensure block
260+ addInstr (new LabelInstr (ebi .end )); // End label for the exception region
250261
251262 return isEnsureExpr ? ensureExprValue : rv ;
252263 }
253264
265+ private Operand processEnsureBody (U ensureNode , EnsureBlockInfo ebi ) {
266+ Operand ensureRetVal ;
267+ if (ensureNode != null ) {
268+ ensureBodyBuildStack .push (ebi );
269+ ensureRetVal = build (ensureNode );
270+ ensureBodyBuildStack .pop ();
271+ } else {
272+ ensureRetVal = nil ();
273+ }
274+ return ensureRetVal ;
275+ }
276+
254277 public InterpreterContext buildEvalRoot (ParseResult rootNode ) {
255278 executesOnce = false ;
256279 coverageMode = rootNode .getCoverageMode ();
@@ -1932,8 +1955,8 @@ protected Operand buildOpAsgnConstDecl(Y left, U right, RubySymbol operator) {
19321955 return copy (temp (), putConstant (left , result ));
19331956 }
19341957
1935- protected Operand buildOpAsgnConstDecl (Y left , RubySymbol name , U right , RubySymbol operator ) {
1936- Operand parent = buildColon2ForConstAsgnDeclNode (( U ) left , temp (), false );
1958+ protected Operand buildOpAsgnConstDecl (U left , RubySymbol name , U right , RubySymbol operator ) {
1959+ Operand parent = buildColon2ForConstAsgnDeclNode (left , temp (), false );
19371960 Operand lhs = searchModuleForConst (temp (), parent , name );
19381961 Operand rhs = build (right );
19391962 Variable result = call (temp (), lhs , operator , rhs );
@@ -2499,7 +2522,6 @@ protected Operand buildRedo(int line) {
24992522 protected void buildRescueBodyInternal (U [] exceptions , U body , X consequent , Variable rv , Variable exc , Label endLabel ,
25002523 U reference ) {
25012524 // Compare and branch as necessary!
2502- Label uncaughtLabel = getNewLabel ("MISSED" );
25032525 Label caughtLabel = getNewLabel ("RESCUE" );
25042526 if (exceptions == null || exceptions .length == 0 ) {
25052527 outputExceptionCheck (getManager ().getStandardError (), exc , caughtLabel );
@@ -2510,7 +2532,6 @@ protected void buildRescueBodyInternal(U[] exceptions, U body, X consequent, Var
25102532 }
25112533
25122534 // Uncaught exception -- build other rescue nodes or rethrow!
2513- addInstr (new LabelInstr (uncaughtLabel ));
25142535 if (consequent != null ) {
25152536 buildRescueBodyInternal (exceptionNodesFor (consequent ), bodyFor (consequent ), optRescueFor (consequent ), rv ,
25162537 exc , endLabel , referenceFor (consequent ));
@@ -2579,21 +2600,33 @@ protected Operand buildAttrAssign(Variable result, U receiver, U argsNode, U blo
25792600
25802601 protected abstract Operand [] buildAttrAssignCallArgs (U args , Operand [] rhs , boolean containsAssignment );
25812602
2603+ /**
2604+ * @param bodyNode the main statements to be executed
2605+ * @param elseNode else path in a begin
2606+ * @param exceptions possible list of excpetions which will jump to rescueBody
2607+ * @param rescueBody the statements executed if the matching exception(s) occurs
2608+ * @param optRescue possible other rescue in the same begin
2609+ * @param isModifier foo rescue bar
2610+ * @param ensure ensure block
2611+ * @param reference variable for the rescue (NameError => name)
2612+ * @return return last operand of begin execution
2613+ */
25822614 protected Operand buildRescueInternal (U bodyNode , U elseNode , U [] exceptions , U rescueBody ,
25832615 X optRescue , boolean isModifier , EnsureBlockInfo ensure , U reference ) {
25842616 boolean needsBacktrace = !canBacktraceBeRemoved (exceptions , rescueBody , optRescue , elseNode , isModifier );
25852617
25862618 // Labels marking start, else, end of the begin-rescue(-ensure)-end block
2587- Label rBeginLabel = getNewLabel ();
2588- Label rEndLabel = ensure .end ;
2589- Label rescueLabel = getNewLabel ("RESC_TEST" ); // Label marking start of the first rescue code.
2619+ int line = currentLine () + 1 ;
2620+ Label beginLabel = getNewLabel ("BEGIN_@" + line );
2621+ Label endLabel = ensure .end ;
2622+ Label rescueTestLabel = getNewLabel ("RESCUE_TEST_@" + line ); // Label marking start of the first rescue code.
25902623 ensure .needsBacktrace = needsBacktrace ;
25912624
2592- addInstr (new LabelInstr (rBeginLabel ));
2625+ addInstr (new LabelInstr (beginLabel ));
25932626
25942627 // Placeholder rescue instruction that tells rest of the compiler passes the boundaries of the rescue block.
2595- addInstr (new ExceptionRegionStartMarkerInstr (rescueLabel ));
2596- activeRescuers .push (rescueLabel );
2628+ addInstr (new ExceptionRegionStartMarkerInstr (rescueTestLabel ));
2629+ activeRescuers .push (rescueTestLabel );
25972630 addInstr (getManager ().needsBacktrace (needsBacktrace ));
25982631
25992632 // Body
@@ -2628,7 +2661,7 @@ protected Operand buildRescueInternal(U bodyNode, U elseNode, U[] exceptions, U
26282661 //
26292662 // The retry should jump to 1, not 2.
26302663 // If we push the rescue block before building the body, we will jump to 2.
2631- RescueBlockInfo rbi = new RescueBlockInfo (rBeginLabel , ensure .savedGlobalException );
2664+ RescueBlockInfo rbi = new RescueBlockInfo (beginLabel , ensure .savedGlobalException );
26322665 activeRescueBlockStack .push (rbi );
26332666
26342667 if (tmp != U_NIL ) {
@@ -2638,7 +2671,7 @@ protected Operand buildRescueInternal(U bodyNode, U elseNode, U[] exceptions, U
26382671 // - If we dont have any ensure blocks, simply jump to the end of the rescue block
26392672 // - If we do, execute the ensure code.
26402673 ensure .cloneIntoHostScope (this );
2641- addInstr (new JumpInstr (rEndLabel ));
2674+ addInstr (new JumpInstr (endLabel ));
26422675 } //else {
26432676 // If the body had an explicit return, the return instruction IR build takes care of setting
26442677 // up execution of all necessary ensure blocks. So, nothing to do here!
@@ -2650,7 +2683,7 @@ protected Operand buildRescueInternal(U bodyNode, U elseNode, U[] exceptions, U
26502683 //}
26512684
26522685 // Start of rescue logic
2653- addInstr (new LabelInstr (rescueLabel ));
2686+ addInstr (new LabelInstr (rescueTestLabel ));
26542687
26552688 // This is optimized no backtrace path so we need to reenable backtraces since we are
26562689 // exiting that region.
@@ -2660,7 +2693,7 @@ protected Operand buildRescueInternal(U bodyNode, U elseNode, U[] exceptions, U
26602693 Variable exc = addResultInstr (new ReceiveRubyExceptionInstr (temp ()));
26612694
26622695 // Build the actual rescue block(s)
2663- buildRescueBodyInternal (exceptions , rescueBody , optRescue , rv , exc , rEndLabel , reference );
2696+ buildRescueBodyInternal (exceptions , rescueBody , optRescue , rv , exc , endLabel , reference );
26642697
26652698 activeRescueBlockStack .pop ();
26662699 return rv ;
0 commit comments