diff --git a/src/org/rascalmpl/exceptions/RascalStackOverflowError.java b/src/org/rascalmpl/exceptions/RascalStackOverflowError.java new file mode 100644 index 00000000000..cecee677b0d --- /dev/null +++ b/src/org/rascalmpl/exceptions/RascalStackOverflowError.java @@ -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 - Jurgen.Vinju@cwi.nl - 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; + } +} diff --git a/src/org/rascalmpl/library/lang/rascal/tests/basic/Functions.rsc b/src/org/rascalmpl/library/lang/rascal/tests/basic/Functions.rsc index fec17b3fa7d..3e5d6930ff4 100644 --- a/src/org/rascalmpl/library/lang/rascal/tests/basic/Functions.rsc +++ b/src/org/rascalmpl/library/lang/rascal/tests/basic/Functions.rsc @@ -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; +} \ No newline at end of file diff --git a/src/org/rascalmpl/repl/RascalInterpreterREPL.java b/src/org/rascalmpl/repl/RascalInterpreterREPL.java index 17febce23d5..8d5fc877626 100644 --- a/src/org/rascalmpl/repl/RascalInterpreterREPL.java +++ b/src/org/rascalmpl/repl/RascalInterpreterREPL.java @@ -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; @@ -152,6 +153,10 @@ public IRascalResult evalStatement(String statement, String lastLine) throws Int parseErrorMessage(eval.getErrorPrinter(), lastLine, "prompt", pe, indentedPrettyPrinter); return null; } + catch (RascalStackOverflowError e) { + throwMessage(eval.getErrorPrinter(), e.makeThrow(), indentedPrettyPrinter); + return null; + } catch (StaticError e) { staticErrorMessage(eval.getErrorPrinter(),e, indentedPrettyPrinter); return null; diff --git a/src/org/rascalmpl/semantics/dynamic/Expression.java b/src/org/rascalmpl/semantics/dynamic/Expression.java index 9d4d6d9db59..a7f7b5e6d12 100644 --- a/src/org/rascalmpl/semantics/dynamic/Expression.java +++ b/src/org/rascalmpl/semantics/dynamic/Expression.java @@ -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; @@ -542,12 +543,14 @@ public Result interpret(IEvaluator> 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 diff --git a/src/org/rascalmpl/semantics/dynamic/Statement.java b/src/org/rascalmpl/semantics/dynamic/Statement.java index 8bedcc2bcd5..d9c472817de 100644 --- a/src/org/rascalmpl/semantics/dynamic/Statement.java +++ b/src/org/rascalmpl/semantics/dynamic/Statement.java @@ -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; @@ -957,13 +958,50 @@ static public Result evalStatementTry(IEvaluator> eval, o 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; + } + } + } + + if (!handled) { + // we rethrow because higher up the stack may be another catch block + throw e; + } + } + 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()); + } + 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; + } } static public class TryFinally extends