Skip to content

Commit 12dcdfa

Browse files
committed
Detect invalid returns in module bodies
* Such as `class C; proc { return 42 }.call; end`.
1 parent ff64a87 commit 12dcdfa

File tree

5 files changed

+59
-18
lines changed

5 files changed

+59
-18
lines changed

spec/tags/language/return_tags.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ slow:The return keyword at top level stops file execution
33
slow:The return keyword at top level within a begin fires ensure block before returning
44
slow:The return keyword at top level return with argument does not affect exit status
55
fails:The return keyword at top level within a class is allowed
6-
fails:The return keyword at top level file loading stops file loading and execution
7-
fails:The return keyword at top level file requiring stops file loading and execution
86
fails:The return keyword at top level return with argument does not affect exit status
97
fails:The return keyword at top level within a block within a class is allowed
10-
fails(escapes the interpreter):The return keyword at top level within a block within a class is not allowed
118
slow:The return keyword at top level return with argument warns but does not affect exit status
129
fails:The return keyword at top level return with argument warns but does not affect exit status
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. This
3+
* code is released under a tri EPL/GPL/LGPL license. You can use it,
4+
* redistribute it and/or modify it under the terms of the:
5+
*
6+
* Eclipse Public License version 2.0, or
7+
* GNU General Public License version 2, or
8+
* GNU Lesser General Public License version 2.1.
9+
*/
10+
package org.truffleruby.language.control;
11+
12+
import org.truffleruby.language.RubyContextSourceNode;
13+
import org.truffleruby.language.RubyNode;
14+
15+
import com.oracle.truffle.api.frame.VirtualFrame;
16+
17+
public class InvalidReturnNode extends RubyContextSourceNode {
18+
19+
@Child public RubyNode value;
20+
21+
public InvalidReturnNode(RubyNode value) {
22+
this.value = value;
23+
}
24+
25+
@Override
26+
public Object execute(VirtualFrame frame) {
27+
value.doExecuteVoid(frame);
28+
throw new RaiseException(getContext(), coreExceptions().unexpectedReturn(this));
29+
}
30+
31+
}

src/main/java/org/truffleruby/language/control/ReturnID.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
*/
1010
package org.truffleruby.language.control;
1111

12-
public class ReturnID {
12+
public final class ReturnID {
13+
14+
/** Returning to or through class/module bodies is a LocalJumpError. However, the surrounding block might be turned
15+
* into a lambda, in which case it becomes valid and returns from the lambda. */
16+
public static final ReturnID MODULE_BODY = new ReturnID();
1317

1418
}

src/main/java/org/truffleruby/parser/BodyTranslator.java

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
import org.truffleruby.language.control.FrameOnStackNode;
8686
import org.truffleruby.language.control.IfElseNode;
8787
import org.truffleruby.language.control.IfNode;
88+
import org.truffleruby.language.control.InvalidReturnNode;
8889
import org.truffleruby.language.control.LocalReturnNode;
8990
import org.truffleruby.language.control.NextNode;
9091
import org.truffleruby.language.control.NotNode;
@@ -846,18 +847,10 @@ private RubyNode openModule(SourceIndexLength sourceSection, RubyNode defineOrGe
846847
null,
847848
null);
848849

849-
final ReturnID returnId;
850-
851-
if (type == OpenModule.SINGLETON_CLASS) {
852-
returnId = environment.getReturnID();
853-
} else {
854-
returnId = environment.getParseEnvironment().allocateReturnID();
855-
}
856-
857850
final TranslatorEnvironment newEnvironment = new TranslatorEnvironment(
858851
environment,
859852
environment.getParseEnvironment(),
860-
returnId,
853+
ReturnID.MODULE_BODY,
861854
true,
862855
true,
863856
true,
@@ -2872,7 +2865,12 @@ public RubyNode visitReturnNode(ReturnParseNode node) {
28722865
final RubyNode ret;
28732866

28742867
if (environment.isBlock()) {
2875-
ret = new DynamicReturnNode(environment.getReturnID(), translatedChild);
2868+
final ReturnID returnID = environment.getReturnID();
2869+
if (returnID == ReturnID.MODULE_BODY) {
2870+
ret = new InvalidReturnNode(translatedChild);
2871+
} else {
2872+
ret = new DynamicReturnNode(returnID, translatedChild);
2873+
}
28762874
} else {
28772875
ret = new LocalReturnNode(translatedChild);
28782876
}

src/main/java/org/truffleruby/parser/MethodTranslator.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
import org.truffleruby.language.arguments.ReadPreArgumentNode;
2525
import org.truffleruby.language.arguments.ShouldDestructureNode;
2626
import org.truffleruby.language.control.AndNode;
27+
import org.truffleruby.language.control.DynamicReturnNode;
2728
import org.truffleruby.language.control.IfElseNode;
29+
import org.truffleruby.language.control.InvalidReturnNode;
2830
import org.truffleruby.language.control.NotNode;
2931
import org.truffleruby.language.control.SequenceNode;
3032
import org.truffleruby.language.locals.FlipFlopStateNode;
@@ -172,9 +174,9 @@ public BlockDefinitionNode compileBlockNode(SourceIndexLength sourceSection, Par
172174
body = new ExceptionTranslatingNode(body, UnsupportedOperationBehavior.TYPE_ERROR);
173175

174176
// Procs
175-
final RubyNode bodyProc = new CatchForProcNode(
176-
composeBody(sourceSection, preludeProc, NodeUtil.cloneNode(body)));
177-
bodyProc.unsafeSetSourceSection(enclosing(sourceSection, body));
177+
final RubyNode bodyCopyForProc = NodeUtil.cloneNode(body);
178+
final RubyNode bodyProc = new CatchForProcNode(composeBody(sourceSection, preludeProc, bodyCopyForProc));
179+
bodyProc.unsafeSetSourceSection(enclosing(sourceSection, bodyCopyForProc));
178180

179181
final RubyRootNode newRootNodeForProcs = new RubyRootNode(
180182
language,
@@ -185,7 +187,8 @@ public BlockDefinitionNode compileBlockNode(SourceIndexLength sourceSection, Par
185187
Split.HEURISTIC);
186188

187189
// Lambdas
188-
RubyNode composed = composeBody(sourceSection, preludeLambda, body /* no copy, last usage */);
190+
RubyNode bodyForLambda = body; // no copy, last usage
191+
RubyNode composed = composeBody(sourceSection, preludeLambda, bodyForLambda);
189192

190193
composed = new CatchForLambdaNode(environment.getReturnID(), environment.getBreakID(), composed);
191194

@@ -201,6 +204,14 @@ public BlockDefinitionNode compileBlockNode(SourceIndexLength sourceSection, Par
201204
final RootCallTarget callTargetAsLambda = Truffle.getRuntime().createCallTarget(newRootNodeForLambdas);
202205
final RootCallTarget callTargetAsProc = Truffle.getRuntime().createCallTarget(newRootNodeForProcs);
203206

207+
if (isProc) {
208+
// If we end up executing this block as a lambda, but don't know it statically, e.g., `lambda {}` or
209+
// `define_method(:foo, proc {})`), then returns are always valid and return from that lambda.
210+
// This needs to run after nodes are adopted for replace() to work and nodes to know their parent.
211+
for (InvalidReturnNode returnNode : NodeUtil.findAllNodeInstances(bodyForLambda, InvalidReturnNode.class)) {
212+
returnNode.replace(new DynamicReturnNode(environment.getReturnID(), returnNode.value));
213+
}
214+
}
204215

205216
Object frameOnStackMarkerSlot;
206217

0 commit comments

Comments
 (0)