[449946] - Control flow is manipulated using exceptions
diff --git a/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/QvtOperationalEvaluationVisitorImpl.java b/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/QvtOperationalEvaluationVisitorImpl.java
index 028c30f..151f015 100644
--- a/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/QvtOperationalEvaluationVisitorImpl.java
+++ b/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/QvtOperationalEvaluationVisitorImpl.java
@@ -1113,25 +1113,28 @@
if(valueExp != null) {
value = visitExpression(valueExp);
}
-
- OperationBody body = QvtOperationalParserUtil.findParentElement(returnExp, OperationBody.class);
- if(body != null) {
- EList<org.eclipse.ocl.ecore.OCLExpression> content = body.getContent();
- if(!content.isEmpty() && content.get(content.size() - 1) == returnExp) {
- // return is the last expression in the body, simply return the value
- return value;
- }
- }
-
- // TODO - analyze more complex structured execution flow and
- // avoid the cost of exception throwing
- throw new ReturnExpEvent(value, getOperationalEvaluationEnv());
+ // Wrap the result of the expression in an OperationCallResult object.
+ // This type breaks control flow, so the value arrives directly at its
+ // calling operation, where it is unwrapped again and returned.
+ return new OperationCallResult(value, getOperationalEvaluationEnv());
}
public Object visitOperationBody(OperationBody operationBody) {
Object result = null;
for (OCLExpression<EClassifier> exp : operationBody.getContent()) {
result = visitExpression(exp);
+
+ // If control flow was broken (by means of a return statement,
+ // stop executing the next lines and return this result.
+ if(result instanceof BreakingResult) {
+ if(result instanceof OperationCallResult) {
+ result = ((OperationCallResult)result).myResult;
+ }
+ else {
+ result = null;
+ }
+ break;
+ }
}
ImperativeOperation operation = operationBody.getOperation();
@@ -1175,6 +1178,8 @@
private Object visitBlockExpImpl(EList<org.eclipse.ocl.ecore.OCLExpression> expList, boolean isInImperativeOper) {
List<String> scopeVars = null;
+ Object result = null;
+
for (OCLExpression<EClassifier> exp : expList) {
if((exp instanceof VariableInitExp) && !isInImperativeOper) {
if(scopeVars == null) {
@@ -1184,7 +1189,13 @@
scopeVars.add(varInitExp.getName());
}
- visitExpression(exp);
+ result = visitExpression(exp);
+ if(result instanceof BreakingResult) {
+ break;
+ }
+ // Return null, unless control flow is broken (by break, continue or return).
+ // When control flow is broken, propagate this upward in the AST.
+ result = null;
}
if(scopeVars != null) {
@@ -1194,7 +1205,7 @@
}
}
- return null;
+ return result;
}
public Object visitComputeExp(ComputeExp computeExp) {
@@ -1206,32 +1217,47 @@
}
replaceInEnv(returnedElement.getName(), initExpressionValue, returnedElement.getType());
- visitExpression(computeExp.getBody());
+ Object result = visitExpression(computeExp.getBody());
+
+ Object returnedValue = getEvaluationEnvironment().remove(returnedElement.getName());
+
+ if(result instanceof BreakingResult) {
+ // Control flow was broken (break or continue).
+ // Instead of returning the value, propagate this.
+ return result;
+ }
- return getEvaluationEnvironment().remove(returnedElement.getName());
+ return returnedValue;
}
public Object visitWhileExp(WhileExp whileExp) {
+ Object result = null;
while (true) {
Object condition = visitExpression(whileExp.getCondition());
if (Boolean.TRUE.equals(condition)) {
- try {
- visitExpression(whileExp.getBody());
- }
- catch (QvtTransitionReachedException ex) {
- if (ex.getReason() == QvtTransitionReachedException.REASON_BREAK) {
- break;
- }
- if (ex.getReason() == QvtTransitionReachedException.REASON_CONTINUE) {
- continue;
- }
+ result = visitExpression(whileExp.getBody());
+
+ if(result instanceof BreakingResult) {
+ // Control flow is being broken (break, continue, or return).
+
+ if(result instanceof BreakResult) {
+ // Result must be null, unless it comes from a return statement.
+ result = null;
+ break;
+ }
+ if(result instanceof ContinueResult) {
+ // Instead of breaking out of the loop, continue with the next iteration.
+ result = null;
+ continue;
+ }
+ break;
}
} else {
break;
}
}
- return null;
+ return result;
}
private static class SwitchAltExpResult {
@@ -1525,7 +1551,7 @@
}
public Object visitBreakExp(BreakExp astNode) {
- throw new QvtTransitionReachedException(QvtTransitionReachedException.REASON_BREAK);
+ return BREAK;
}
public Object visitCatchtExp(CatchExp astNode) {
@@ -1534,7 +1560,7 @@
}
public Object visitContinueExp(ContinueExp astNode) {
- throw new QvtTransitionReachedException(QvtTransitionReachedException.REASON_CONTINUE);
+ return CONTINUE;
}
public Object visitDictLiteralPart(DictLiteralPart astNode) {
@@ -1793,8 +1819,39 @@
}
}
+ /**
+ * Tag interface which represents an evaluation result which,
+ * when encountered, breaks control flow.
+ *
+ * Examples of this are break, continue, and return.
+ */
+ public static interface BreakingResult {
+
+ }
+
+ /**
+ * Type of result which represents the situation in which a break statement is encountered.
+ */
+ public static class BreakResult implements BreakingResult {
+ BreakResult() { }
+ }
+
+ protected final static BreakResult BREAK = new BreakResult();
+
+ /**
+ * Type of result which represents the situation in which a continue statement is encountered.
+ */
+ public static class ContinueResult implements BreakingResult {
+ ContinueResult() { }
+ }
+
+ protected final static ContinueResult CONTINUE = new ContinueResult();
- protected static class OperationCallResult {
+ /**
+ * The result of an operation call.
+ * Represents the situation where a return statement was encountered.
+ */
+ public static class OperationCallResult implements BreakingResult {
public Object myResult;
public QvtOperationalEvaluationEnv myEvalEnv;
@@ -1866,9 +1923,6 @@
callResult = (OperationCallResult)result;
}
- catch (ReturnExpEvent e) {
- callResult = e.getResult();
- }
catch (StackOverflowError e) {
throwQVTException(new QvtStackOverFlowError(e));
}
@@ -2489,26 +2543,6 @@
}
}
}
- }
-
-
- /**
- * TODO - Avoid using this exception return expression to interrupt execution flow for
- * immediate return from an operation.
- * Though, if the last statement in a body is return expression, no exception is thrown, we
- * should try to avoid this cost to be paid in general
- */
- private static class ReturnExpEvent extends QvtRuntimeException {
- private static final long serialVersionUID = 2971434369853642555L;
- private final OperationCallResult fResult;
-
- ReturnExpEvent(Object returnValue, QvtOperationalEvaluationEnv evalEnv) {
- fResult = new OperationCallResult(returnValue, evalEnv);
- }
-
- public OperationCallResult getResult() {
- return fResult;
- }
}
}
diff --git a/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/QvtTransitionReachedException.java b/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/QvtTransitionReachedException.java
deleted file mode 100644
index 5f797d8..0000000
--- a/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/QvtTransitionReachedException.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2007, 2009 Borland Software Corporation and others.
- *
- * 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:
- * Borland Software Corporation - initial API and implementation
- *******************************************************************************/
-
-package org.eclipse.m2m.internal.qvt.oml.evaluator;
-
-
-public class QvtTransitionReachedException extends QvtRuntimeException {
-
- private static final long serialVersionUID = 460249893891473965L;
-
- public static final int REASON_BREAK = 1;
-
- public static final int REASON_CONTINUE = 2;
-
- public QvtTransitionReachedException(int reason) {
- myReason = reason;
- }
-
- public int getReason() {
- return myReason;
- }
-
- private final int myReason;
-
-}
diff --git a/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/iterators/QvtImperativeIteratorTemplate.java b/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/iterators/QvtImperativeIteratorTemplate.java
index 6392757..3b22f86 100644
--- a/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/iterators/QvtImperativeIteratorTemplate.java
+++ b/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/iterators/QvtImperativeIteratorTemplate.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2008, 2009 Borland Software Corporation and others.
+ * Copyright (c) 2008, 2015 Borland Software Corporation and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -18,6 +18,7 @@
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.m2m.internal.qvt.oml.ast.env.QvtOperationalEvaluationEnv;
import org.eclipse.m2m.internal.qvt.oml.ast.parser.QvtOperationalUtil;
+import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtOperationalEvaluationVisitorImpl.BreakingResult;
import org.eclipse.ocl.EvaluationEnvironment;
import org.eclipse.ocl.EvaluationVisitor;
import org.eclipse.ocl.expressions.OCLExpression;
@@ -53,6 +54,11 @@
bodyVal = getEvalEnvironment().getValueOf(iterators.get(0).getName());
}
+ if(bodyVal instanceof BreakingResult) {
+ // Control flow was broken (break, continue, return); propagate this.
+ return bodyVal;
+ }
+
advanceTarget(target, bodyVal);
// get the new result value
diff --git a/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/iterators/QvtIterationTemplate.java b/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/iterators/QvtIterationTemplate.java
index c26ec3e..aa47e58 100644
--- a/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/iterators/QvtIterationTemplate.java
+++ b/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/iterators/QvtIterationTemplate.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2007, 2009 Borland Software Corporation and others.
+ * Copyright (c) 2007, 2015 Borland Software Corporation and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -22,7 +22,8 @@
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EParameter;
-import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtTransitionReachedException;
+import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtOperationalEvaluationVisitorImpl.BreakingResult;
+import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtOperationalEvaluationVisitorImpl.ContinueResult;
import org.eclipse.ocl.EvaluationEnvironment;
import org.eclipse.ocl.EvaluationVisitor;
import org.eclipse.ocl.expressions.OCLExpression;
@@ -80,17 +81,16 @@
while (true) {
Object resultVal = null;
boolean isUpdateResultVal = true;
- try {
- resultVal = evaluateResultTemplate(iterators, target, resultName, condition, body, isOne);
- }
- catch (QvtTransitionReachedException ex) {
- if (ex.getReason() == QvtTransitionReachedException.REASON_BREAK) {
- setDone(true);
- }
- if (ex.getReason() == QvtTransitionReachedException.REASON_CONTINUE) {
- }
+ resultVal = evaluateResultTemplate(iterators, target, resultName, condition, body, isOne);
+
+ if(resultVal instanceof BreakingResult) {
+ // Control flow was broken (break, continue, or return).
+ if(!(resultVal instanceof ContinueResult)) {
+ // No continue, so no more iterations.
+ setDone(true);
+ }
isUpdateResultVal = false;
- }
+ }
// set the result variable in the environment with the result value
if (isUpdateResultVal) {
diff --git a/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/iterators/QvtIterationTemplateForExp.java b/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/iterators/QvtIterationTemplateForExp.java
index 65d5474..4ef3a82 100644
--- a/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/iterators/QvtIterationTemplateForExp.java
+++ b/plugins/org.eclipse.m2m.qvt.oml/src/org/eclipse/m2m/internal/qvt/oml/evaluator/iterators/QvtIterationTemplateForExp.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2008 Borland Software Corporation and others.
+ * Copyright (c) 2008, 2015 Borland Software Corporation and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -15,6 +15,7 @@
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EParameter;
+import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtOperationalEvaluationVisitorImpl.BreakingResult;
import org.eclipse.ocl.EvaluationVisitor;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.expressions.Variable;
@@ -47,10 +48,13 @@
isConditionOk = (conditionVal instanceof Boolean) && (Boolean) conditionVal;
}
if (isConditionOk) {
- getEvaluationVisitor().visitExpression(body);
+ Object result = getEvaluationVisitor().visitExpression(body);
if (isOne) {
setDone(true);
}
+ if(result instanceof BreakingResult) {
+ return result;
+ }
}
return null;
}
diff --git a/tests/org.eclipse.m2m.tests.qvt.oml/parserTestData/models/continue_break/continue_break.qvto b/tests/org.eclipse.m2m.tests.qvt.oml/parserTestData/models/continue_break/continue_break.qvto
index c1bef6d..e2ee1fc 100644
--- a/tests/org.eclipse.m2m.tests.qvt.oml/parserTestData/models/continue_break/continue_break.qvto
+++ b/tests/org.eclipse.m2m.tests.qvt.oml/parserTestData/models/continue_break/continue_break.qvto
@@ -1,6 +1,6 @@
transformation continue_break();
-mapping main() {
+main() {
var index := 0;
while(true) {
@@ -22,6 +22,12 @@
};
assert fatal (index = 7);
+ col->forEach(i) {
+ if i = 1 then break endif;
+ index := index + 1;
+ };
+ assert fatal (index = 7);
+
var col1 := col->collectselect(i;
res = compute (x : String := i.toString()) {
if i > 2 then break else continue endif;