blob: 79be9aad4dcba388d8e03160c1871c67ab66c75a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2018 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* SAP AG - initial API and implementation
******************************************************************************/
package org.eclipse.ocl.examples.impactanalyzer.instanceScope;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.ocl.ecore.EcoreEnvironment;
import org.eclipse.ocl.ecore.IterateExp;
import org.eclipse.ocl.ecore.LetExp;
import org.eclipse.ocl.ecore.LoopExp;
import org.eclipse.ocl.ecore.OCLExpression;
import org.eclipse.ocl.ecore.OperationCallExp;
import org.eclipse.ocl.ecore.Variable;
import org.eclipse.ocl.ecore.VariableExp;
import org.eclipse.ocl.examples.impactanalyzer.impl.OperationBodyToCallMapper;
import org.eclipse.ocl.examples.impactanalyzer.util.OCLFactory;
import org.eclipse.ocl.utilities.PredefinedType;
/**
* Computes a {@link NavigationStep} for a {@link VariableExp} which, given an element constituting a value the variable
* shall assume, infers a set of elements which, when used as the value of the outermost expression's <code>self</code> variable,
* may lead the variable considered here to assume the expected value.<p>
*
* OCL knows occurrences of different "stereotypes" of variables, depending on where/how the variable is defined:
* <ul>
* <li>a <code>self</code> variable occurring within the body of an operation</li>
* <li>a <code>self</code> variable occurring outside the body of an operation</li>
* <li>an operation's formal parameter</li>
* <li>an iterator variable</li>
* <li>the result variable of an {@link IterateExp}</li>
* <li>the variable assigned by a {@link LetExp}</li>
* </ul>
*
* In all cases, traceback continues with the expression defining the possible values that the variable may assume. In all
* cases, this leaves the scope in which the variable is visible. This becomes important when considering how variable values
* travel through the computation of the <code>unused</code> function, trying to prove for each expression visited by traceback,
* whether or not it is used, given what is known about variable values.
*
* @author Axel Uhl (D043530)
*
*/
// TODO refactor into several subclasses, one for each is... case, and let tracer factory in InstanceScopeAnalysis.createTracer choose the appropriate one
public class VariableExpTracer extends AbstractTracer<VariableExp> {
public VariableExpTracer(VariableExp expression, Stack<String> tuplePartNames, OCLFactory oclFactory) {
super(expression, tuplePartNames, oclFactory);
}
private Variable getVariableDeclaration() {
return (Variable) getExpression().getReferredVariable();
}
private boolean isLetVariable() {
// let variables are contained in the letExp
Variable exp = getVariableDeclaration();
return exp.eContainer() instanceof LetExp && ((LetExp) exp.eContainer()).getVariable() == exp;
}
private boolean isOperationParameter() {
return getVariableDeclaration().getRepresentedParameter() != null;
}
private boolean isIterateResultVariable() {
// result variables are contained in iterateExp
Variable exp = getVariableDeclaration();
return (exp.eContainer() instanceof IterateExp && ((IterateExp) exp.eContainer()).getResult() == exp);
}
private boolean isIteratorVariable() {
Variable exp = getVariableDeclaration();
return (exp.eContainer() instanceof LoopExp && ((LoopExp) exp.eContainer()).getIterator().contains(exp));
}
private boolean isSelf() {
return getVariableDeclaration().getName().equals(EcoreEnvironment.SELF_VARIABLE_NAME);
}
/**
* The step produced will be invoked with the value for the variable. This knowledge can be helpful when trying to perform
* partial evaluations. Variables have an {@link OCLExpression} as their scope. For example, a let-variable has the
* {@link LetExp#getIn() in} expression as its static scope. Additionally, scopes are dynamically instantiated during
* expression evaluation. For example, during evaluation of an {@link IterateExp}, the body expression forms the static scope
* for the {@link IterateExp#getResult() result variable}. During each iteration, a new dynamic scope is created and the same
* static result variable may have a different value in each dynamic scope. It is important to understand that the navigation
* step learns about the value of the variable only in one particular dynamic scope.
* <p>
*
* Dynamic scopes are identified by the {@link OCLExpression} object forming the static scope, combined with a unique
* identifier, implemented as a simple counter.
* <p>
*
* It is also important to understand that variables of a collection type cannot have their value fully inferred by the
* traceback process as only single elements are visited during the trace.
*/
@Override
public NavigationStep traceback(EClass context, PathCache pathCache, OperationBodyToCallMapper operationBodyToCallMapper) {
NavigationStep result;
if (isSelf()) {
result = tracebackSelf(context, pathCache, operationBodyToCallMapper);
} else if (isIteratorVariable()) {
result = tracebackIteratorVariable(context, pathCache, operationBodyToCallMapper);
} else if (isIterateResultVariable()) {
result = tracebackIterateResultVariable(context, pathCache, operationBodyToCallMapper);
} else if (isLetVariable()) {
result = tracebackLetVariable(context, pathCache, operationBodyToCallMapper);
} else if (isOperationParameter()) {
result = tracebackOperationParameter(context, pathCache, operationBodyToCallMapper);
} else {
throw new RuntimeException("Unknown variable expression that is neither an iterator variable "
+ "nor an iterate result variable nor an operation parameter nor a let variable nor self: "
+ getExpression().getReferredVariable().getName());
}
applyScopesOnNavigationStep(result, operationBodyToCallMapper);
return result;
}
private NavigationStep tracebackOperationParameter(EClass context, PathCache pathCache,
OperationBodyToCallMapper operationBodyToCallMapper) {
OCLExpression rootExpression = getRootExpression();
// all operation bodies must have been reached through at least one call; all calls are
// recorded in the filter synthesizer's cache. Therefore, we can determine the relationship
// between body and EOperation.
EOperation op = operationBodyToCallMapper.getCallsOf(rootExpression).iterator().next().getReferredOperation();
int pos = getParameterPosition(op);
List<NavigationStep> stepsPerCall = new ArrayList<NavigationStep>();
IndirectingStep indirectingStep = pathCache.createIndirectingStepFor(getExpression(), getTupleLiteralPartNamesToLookFor());
// As new operation calls change the set of OperationCallExp returned here for existing operations,
// the PathCache cannot trivially be re-used across expression registrations. We would have to
// invalidate all cache entries that depend on this step. Or we add steps produced for new calls to this
// step as the calls get added; but that may require a re-assessment of the isAlwaysEmpty() calls.
// This may not pay off.
for (OperationCallExp call : operationBodyToCallMapper.getCallsOf(rootExpression)) {
OCLExpression argumentExpression = (OCLExpression) call.getArgument().get(pos);
NavigationStep stepForCall = pathCache.getOrCreateNavigationPath(argumentExpression, context, operationBodyToCallMapper,
getTupleLiteralPartNamesToLookFor(), oclFactory);
// leaves all variables currently in scope because it'll jump into a new expression context;
// the step enters into all scopes in which the argument expression in the operation call is nested,
// starting from the root expression for the argument expression
stepForCall.addLeavingScopes(getAllVariablesInScope(getExpression(), operationBodyToCallMapper));
stepForCall.addEnteringScopes(getAllVariablesInScope(argumentExpression, operationBodyToCallMapper));
stepsPerCall.add(stepForCall);
}
indirectingStep.setActualStep(pathCache.navigationStepForBranch(getInnermostElementType(getExpression().getType()),
context, getExpression(), getTupleLiteralPartNamesToLookFor(), /* requireExactMatchForSourceType */ false, stepsPerCall.toArray(new NavigationStep[0])));
return indirectingStep;
}
/**
* Determines the position of the parameter of operation <tt>op</tt> that is named like the variable referred to by this
* tracer's {@link #getExpression() variable expression).
*/
private int getParameterPosition(EOperation op) {
String variableName = getVariableDeclaration().getName();
// determine position of formal IN_DIR parameter named variableName
int pos = 0;
EList<EObject> pList = op.eContents();
for (EObject p : pList) {
if (p instanceof EParameter) {
if (variableName.equals(((EParameter) p).getName())) {
break;
} else {
pos++;
}
}
}
return pos;
}
private NavigationStep tracebackLetVariable(EClass context, PathCache pathCache,
OperationBodyToCallMapper operationBodyToCallMapper) {
NavigationStep result = pathCache.getOrCreateNavigationPath((OCLExpression) getVariableDeclaration().getInitExpression(),
context, operationBodyToCallMapper, getTupleLiteralPartNamesToLookFor(), oclFactory);
return result;
}
private NavigationStep tracebackIterateResultVariable(EClass context, PathCache pathCache,
OperationBodyToCallMapper operationBodyToCallMapper) {
// the init expression can't reference the result variable, therefore no recursive reference may occur here:
NavigationStep stepForInitExpression = pathCache.getOrCreateNavigationPath((OCLExpression) getVariableDeclaration()
.getInitExpression(), context, operationBodyToCallMapper, getTupleLiteralPartNamesToLookFor(), oclFactory);
// the body expression, however, may reference the result variable; computing the body's navigation step graph
// may therefore recursively look up the navigation step graph for the result variable. We therefore need to
// enter a placeholder into the cache before we start computing the navigation step graph for the body expression:
IndirectingStep indirectingStep = pathCache.createIndirectingStepFor(
getExpression(), getTupleLiteralPartNamesToLookFor());
NavigationStep stepForBodyExpression = pathCache.getOrCreateNavigationPath(
(OCLExpression) ((IterateExp) getVariableDeclaration().eContainer()).getBody(), context, operationBodyToCallMapper,
getTupleLiteralPartNamesToLookFor(), oclFactory);
NavigationStep actualStepForIterateResultVariableExp = pathCache.navigationStepForBranch(
getInnermostElementType(getExpression().getType()), context, getExpression(),
getTupleLiteralPartNamesToLookFor(), /* requireExactMatchForSourceType */ false, stepForInitExpression, stepForBodyExpression);
indirectingStep.setActualStep(actualStepForIterateResultVariableExp);
return indirectingStep;
}
private NavigationStep tracebackIteratorVariable(EClass context, PathCache pathCache,
OperationBodyToCallMapper operationBodyToCallMapper) {
LoopExp loopExp = (LoopExp) getVariableDeclaration().eContainer();
// the source expression can't reference the iterator variable, therefore no recursive reference may occur here:
NavigationStep stepForSource = pathCache.getOrCreateNavigationPath(
(OCLExpression) loopExp.getSource(), context, operationBodyToCallMapper,
getTupleLiteralPartNamesToLookFor(), oclFactory);
NavigationStep result;
if (PredefinedType.CLOSURE_NAME.equals(loopExp.getName())) {
// the body expression, however, may reference the iterator variable; computing the body's navigation step graph
// may therefore recursively look up the navigation step graph for the iterator variable. We therefore need to
// enter a placeholder into the cache before we start computing the navigation step graph for the body expression:
IndirectingStep indirectingStep = pathCache.createIndirectingStepFor(
getExpression(), getTupleLiteralPartNamesToLookFor());
NavigationStep stepForBodyExpression = pathCache.getOrCreateNavigationPath(
(OCLExpression) loopExp.getBody(), context, operationBodyToCallMapper,
getTupleLiteralPartNamesToLookFor(), oclFactory);
NavigationStep actualStepForIterateResultVariableExp = pathCache.navigationStepForBranch(
getInnermostElementType(getExpression().getType()), context, getExpression(),
getTupleLiteralPartNamesToLookFor(), /* requireExactMatchForSourceType */ false, stepForSource, stepForBodyExpression);
indirectingStep.setActualStep(actualStepForIterateResultVariableExp);
result = indirectingStep;
} else {
result = stepForSource;
}
return result;
}
private NavigationStep tracebackSelf(EClass context, PathCache pathCache,
OperationBodyToCallMapper operationBodyToCallMapper) {
IndirectingStep result;
EOperation op = getOperationOfWhichRootExpressionIsTheBody(operationBodyToCallMapper);
if (op != null) {
// in an operation, self needs to be traced back to all source expressions of
// calls to that operation
Collection<OperationCallExp> calls = operationBodyToCallMapper.getCallsOf(getRootExpression());
IndirectingStep indirectingStep = pathCache.createIndirectingStepFor(
getExpression(), getTupleLiteralPartNamesToLookFor());
List<NavigationStep> stepsForCalls = new ArrayList<NavigationStep>();
for (OperationCallExp call : calls) {
OCLExpression callSource = (OCLExpression) call.getSource();
NavigationStep stepForCall = pathCache.getOrCreateNavigationPath(callSource, context, operationBodyToCallMapper,
getTupleLiteralPartNamesToLookFor(), oclFactory);
// leaves all variables currently in scope because it'll jump into a new expression context;
// the step enters into all scopes in which the source expression in the operation call is nested,
// starting from the root expression for the source expression
stepForCall.addLeavingScopes(getAllVariablesInScope(getExpression(), operationBodyToCallMapper));
stepForCall.addEnteringScopes(getAllVariablesInScope(callSource, operationBodyToCallMapper));
stepsForCalls.add(stepForCall);
}
// the branching navigation step must not be cached or looked up in a cache because its
// associated expression is the same as the one used for the IndirectingStep created and cached
// above.
indirectingStep.setActualStep(pathCache.navigationStepForBranch(getInnermostElementType(getExpression().getType()),
context, getExpression(), getTupleLiteralPartNamesToLookFor(), /* requireExactMatchForSourceType */ false, stepsForCalls.toArray(new NavigationStep[0])));
result = indirectingStep;
} else {
// self occurred outside of an operation; it evaluates to s for s being the context
result = pathCache.createIndirectingStepFor(getExpression(),
getTupleLiteralPartNamesToLookFor());
result.setActualStep(new IdentityNavigationStep((EClass) getExpression().getType(), (EClass) getExpression()
.getType(), getExpression()));
}
return result;
}
private EOperation getOperationOfWhichRootExpressionIsTheBody(OperationBodyToCallMapper operationBodyToCallMapper) {
// all operation bodies must have been reached through at least one call; all calls are
// recorded in the filter synthesizer's cache. Therefore, we can determine the relationship
// between body and EOperation.
Set<OperationCallExp> filterSynthesizerCallCache = operationBodyToCallMapper.getCallsOf(getRootExpression());
EOperation op = null;
if (!filterSynthesizerCallCache.isEmpty()) {
op = filterSynthesizerCallCache.iterator().next().getReferredOperation();
}
return op;
}
@Override
protected Set<Variable> calculateLeavingScopes(OperationBodyToCallMapper operationBodyToCallMapper) {
// when tracing back a VariableExp, a number of scopes may be left when navigating to the variable definition
// leaving scopes are calculated by finding all scope creating expressions between the traced VariableExp
// and the common composition parent of the VariableExp and its definition
Set<Variable> result;
if (isLetVariable()) {
result = getVariablesIntroducedBetweenHereAnd((OCLExpression) ((LetExp) getVariableDeclaration().eContainer()).getIn(),
operationBodyToCallMapper);
} else if (isIteratorVariable() || isIterateResultVariable()) {
result = getVariablesIntroducedBetweenHereAnd((OCLExpression) ((LoopExp) getVariableDeclaration().eContainer()).getBody(),
operationBodyToCallMapper);
} else if (isOperationParameter() || (isSelf() && getOperationOfWhichRootExpressionIsTheBody(operationBodyToCallMapper) != null)) {
// for operation parameters or self inside an operation body, traceback continues with the call expression,
// leaving all scopes
result = getAllVariablesInScope(getExpression(), operationBodyToCallMapper);
} else {
result = Collections.emptySet();
}
return result;
}
}