Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

This fixes problem #1920 with a failure to throw a StackOverflow exception in case of a real stack overflow in Java code. #1921

Merged
merged 9 commits into from
Nov 22, 2024
49 changes: 49 additions & 0 deletions src/org/rascalmpl/exceptions/RascalStackOverflowError.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*******************************************************************************
* Copyright (c) 2024 CWI
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:

* * Jurgen J. Vinju - [email protected] - CWI
*******************************************************************************/
package org.rascalmpl.exceptions;

import org.rascalmpl.ast.AbstractAST;
import org.rascalmpl.interpreter.env.Environment;

/**
* This class captures the runtime state of the interpreter at the moment
* we detect a java StackOverflowError. Since we can not use any stack depth
* at that moment to create a real Throw exception, we only copy the deepest
* environment and wrap it in here. Later on the level of the REPL, when the
* stack is completely unrolled, we can convert the stack trace to a Rascal trace.
*/
public class RascalStackOverflowError extends RuntimeException {
private static final long serialVersionUID = -3947588548271683963L;
private final Environment deepestEnvironment;
private final AbstractAST currentAST;

public RascalStackOverflowError(AbstractAST current, Environment deepest) {
this.deepestEnvironment = deepest;
this.currentAST = current;
}

public Throw makeThrow() {
StackTrace trace = new StackTrace();
Environment env = deepestEnvironment;

while (env != null) {
trace.add(env.getLocation(), env.getName());
env = env.getCallerScope();
}

return RuntimeExceptionFactory.stackOverflow(currentAST, trace);
}

public Environment getEnvironment() {
return deepestEnvironment;

Check warning on line 47 in src/org/rascalmpl/exceptions/RascalStackOverflowError.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/exceptions/RascalStackOverflowError.java#L47

Added line #L47 was not covered by tests
}
}
11 changes: 11 additions & 0 deletions src/org/rascalmpl/library/lang/rascal/tests/basic/Functions.rsc
Original file line number Diff line number Diff line change
Expand Up @@ -588,3 +588,14 @@ test bool innerAndOuterFunctionUseSameParameterName4(){

return outer("a") == 3;
}

test bool stackoverflow() {
int f(int i) = f(i);

try {
f(1);
return false;
}
catch StackOverflow():
return true;
}
5 changes: 5 additions & 0 deletions src/org/rascalmpl/repl/RascalInterpreterREPL.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.function.Function;
import java.util.stream.Collectors;

import org.rascalmpl.exceptions.RascalStackOverflowError;
import org.rascalmpl.exceptions.StackTrace;
import org.rascalmpl.exceptions.Throw;
import org.rascalmpl.ideservices.IDEServices;
Expand Down Expand Up @@ -152,6 +153,10 @@
parseErrorMessage(eval.getErrorPrinter(), lastLine, "prompt", pe, indentedPrettyPrinter);
return null;
}
catch (RascalStackOverflowError e) {
throwMessage(eval.getErrorPrinter(), e.makeThrow(), indentedPrettyPrinter);
return null;

Check warning on line 158 in src/org/rascalmpl/repl/RascalInterpreterREPL.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/RascalInterpreterREPL.java#L156-L158

Added lines #L156 - L158 were not covered by tests
}
catch (StaticError e) {
staticErrorMessage(eval.getErrorPrinter(),e, indentedPrettyPrinter);
return null;
Expand Down
11 changes: 7 additions & 4 deletions src/org/rascalmpl/semantics/dynamic/Expression.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.rascalmpl.ast.Parameters;
import org.rascalmpl.ast.Statement;
import org.rascalmpl.exceptions.ImplementationError;
import org.rascalmpl.exceptions.RascalStackOverflowError;
import org.rascalmpl.exceptions.RuntimeExceptionFactory;
import org.rascalmpl.exceptions.Throw;
import org.rascalmpl.interpreter.IEvaluator;
Expand Down Expand Up @@ -542,12 +543,14 @@ public Result<IValue> interpret(IEvaluator<Result<IValue>> eval) {
catch (Failure | MatchFailed e) {
throw RuntimeExceptionFactory.callFailed(eval.getValueFactory().list(actuals), eval.getCurrentAST(), eval.getStackTrace());
}
catch (StackOverflowError e) {
// this should not use so much stack as to trigger another StackOverflowError
throw new RascalStackOverflowError(this, eval.getCurrentEnvt());
}
return res;
}
catch (StackOverflowError e) {
e.printStackTrace();
throw RuntimeExceptionFactory.stackOverflow(this, eval.getStackTrace());
}
finally {}

}

@Override
Expand Down
40 changes: 39 additions & 1 deletion src/org/rascalmpl/semantics/dynamic/Statement.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.rascalmpl.ast.QualifiedName;
import org.rascalmpl.ast.Target;
import org.rascalmpl.ast.Type;
import org.rascalmpl.exceptions.RascalStackOverflowError;
import org.rascalmpl.interpreter.Accumulator;
import org.rascalmpl.interpreter.AssignableEvaluator;
import org.rascalmpl.interpreter.IEvaluator;
Expand Down Expand Up @@ -957,13 +958,50 @@

if (!handled)
throw e;
} finally {
}
catch (RascalStackOverflowError e) {
// and now we pretend as if a real Stackoverflow() value has been thrown, such that
// it can be caugt in this catch block if necessary:
boolean handled = false;

for (Catch c : handlers) {
if (c.hasPattern() && isCatchStackOverflow(c.getPattern())) {
IValue pseudo = e.makeThrow().getException();

if (Cases.matchAndEval(makeResult(pseudo.getType(), pseudo, eval), c.getPattern().buildMatcher(eval, false), c.getBody(), eval)) {
handled = true;
break;

Check warning on line 973 in src/org/rascalmpl/semantics/dynamic/Statement.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/semantics/dynamic/Statement.java#L972-L973

Added lines #L972 - L973 were not covered by tests
}
}
}

Check warning on line 976 in src/org/rascalmpl/semantics/dynamic/Statement.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/semantics/dynamic/Statement.java#L976

Added line #L976 was not covered by tests

if (!handled) {
// we rethrow because higher up the stack may be another catch block
throw e;

Check warning on line 980 in src/org/rascalmpl/semantics/dynamic/Statement.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/semantics/dynamic/Statement.java#L980

Added line #L980 was not covered by tests
}
}
finally {
if (finallyBody != null) {
finallyBody.interpret(eval);
}
}
return res;
}

private static boolean isCatchStackOverflow(org.rascalmpl.ast.Expression pattern) {
if (pattern.isVariableBecomes() || pattern.isTypedVariableBecomes()) {
return isCatchStackOverflow(pattern.getPattern());

Check warning on line 993 in src/org/rascalmpl/semantics/dynamic/Statement.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/semantics/dynamic/Statement.java#L993

Added line #L993 was not covered by tests
}
else if (pattern.isCallOrTree()) {
var called = pattern.getExpression();
if (called.isQualifiedName()) {
var qname = called.getQualifiedName();
return pattern.getArguments().isEmpty() && "StackOverflow".equals(Names.consName(qname));
}
}

return false;

Check warning on line 1003 in src/org/rascalmpl/semantics/dynamic/Statement.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/semantics/dynamic/Statement.java#L1003

Added line #L1003 was not covered by tests
}
}

static public class TryFinally extends
Expand Down
Loading