| /******************************************************************************* |
| * 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; |
| } |
| } |