blob: 99065d378cf35ee3351b86581dddf17faedb935e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2018 CEA LIST 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:
* E.D.Willink(CEA LIST) - Initial API and implementation
*******************************************************************************/
package org.eclipse.ocl.examples.codegen.analyzer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.ocl.examples.codegen.cgmodel.CGCatchExp;
import org.eclipse.ocl.examples.codegen.cgmodel.CGConstant;
import org.eclipse.ocl.examples.codegen.cgmodel.CGConstantExp;
import org.eclipse.ocl.examples.codegen.cgmodel.CGElement;
import org.eclipse.ocl.examples.codegen.cgmodel.CGIfExp;
import org.eclipse.ocl.examples.codegen.cgmodel.CGInvalid;
import org.eclipse.ocl.examples.codegen.cgmodel.CGIsEqual2Exp;
import org.eclipse.ocl.examples.codegen.cgmodel.CGIsEqualExp;
import org.eclipse.ocl.examples.codegen.cgmodel.CGIsInvalidExp;
import org.eclipse.ocl.examples.codegen.cgmodel.CGIsUndefinedExp;
import org.eclipse.ocl.examples.codegen.cgmodel.CGIterationCallExp;
import org.eclipse.ocl.examples.codegen.cgmodel.CGIterator;
import org.eclipse.ocl.examples.codegen.cgmodel.CGLetExp;
import org.eclipse.ocl.examples.codegen.cgmodel.CGLibraryIterateCallExp;
import org.eclipse.ocl.examples.codegen.cgmodel.CGModelFactory;
import org.eclipse.ocl.examples.codegen.cgmodel.CGNavigationCallExp;
import org.eclipse.ocl.examples.codegen.cgmodel.CGOperationCallExp;
import org.eclipse.ocl.examples.codegen.cgmodel.CGThrowExp;
import org.eclipse.ocl.examples.codegen.cgmodel.CGValuedElement;
import org.eclipse.ocl.examples.codegen.cgmodel.CGVariable;
import org.eclipse.ocl.examples.codegen.cgmodel.CGVariableExp;
import org.eclipse.ocl.examples.codegen.cgmodel.util.AbstractExtendingCGModelVisitor;
import org.eclipse.ocl.examples.codegen.utilities.CGUtil;
import org.eclipse.ocl.pivot.Operation;
import org.eclipse.ocl.pivot.OperationCallExp;
import org.eclipse.ocl.pivot.utilities.ClassUtil;
/**
* A FieldingAnalyzer identifies the necessary catches and throws.
* <p>
* <h2>Caught or Thrown Consumer Requirements</h2>
* <p>
* Most AST nodes just propagate invalid and so use of Java exception is convenient and efficient.
* <p>
* <h2>Caught Requirements</h2>
* <p>
* The only AST nodes that respond to invalid are validating operations, so it might seem appropriate
* to just catch all validating operation inputs.
* <p>
* However VariableExps enable a value computed elsewhere to be re-used. Therefore if the re-use is
* by a validating operation, the computation elsewhere must be caught in the sharing LetExp.
* <p>
* <h2>Algorithm</h2>
* <p>
* <h3>Pass 1</h3>
* <p>
* A tree descent/ascent maintains a set of external variable references from a node and its descendants. Wherever a validating operation is
* encountered on the ascent, all external references from source and parameters are marked as caught variables.
* <p>
* <h3>Pass 2</h3>
* <p>
* A tree descent/ascent computes the isCaught state of all nodes. On the ascent children with incompatible isCaught state are corrected by
* insertion of a CGCaughtExp to catch a not-isCaught or a CHThrowExp to throw an isCaught..
*/
public class FieldingAnalyzer
{
/*
* Perform a tree descent/ascent returning the external variables referenced by the visiting node tree.
* The returned set may be null, is transient and may be reused by the caller.
*/
public static class AnalysisVisitor extends AbstractExtendingCGModelVisitor<@Nullable Set<@NonNull CGVariable>, @NonNull FieldingAnalyzer>
{
public AnalysisVisitor(@NonNull FieldingAnalyzer context) {
super(context);
}
@Override
public @Nullable Set<@NonNull CGVariable> visiting(@NonNull CGElement visitable) {
throw new UnsupportedOperationException(getClass().getSimpleName() + ": " + visitable.getClass().getSimpleName());
}
/**
* By default all externals of all children are externals of this node.
*/
@Override
public @Nullable Set<@NonNull CGVariable> visitCGElement(@NonNull CGElement cgElement) {
Set<@NonNull CGVariable> childExternals = null;
for (@NonNull CGElement cgChild : cgElement.getChildren()) {
Set<CGVariable> childExternal = cgChild.accept(this);
if (childExternal != null) {
if (childExternals == null) {
childExternals = childExternal;
}
else {
childExternals.addAll(childExternal);
}
}
}
return childExternals;
}
/**
* All childExternals of a validating operation are marked as caught variables.
*
@Override
public @Nullable Set<@NonNull CGVariable> visitCGIsEqualExp(@NonNull CGIsEqualExp cgElement) {
Set<CGVariable> childExternals = super.visitCGIsEqualExp(cgElement);
context.setCaught(childExternals);
return childExternals;
} */
/**
* All childExternals of a validating operation are marked as caught variables.
*/
@Override
public @Nullable Set<@NonNull CGVariable> visitCGIsInvalidExp(@NonNull CGIsInvalidExp cgElement) {
Set<@NonNull CGVariable> childExternals = super.visitCGIsInvalidExp(cgElement);
context.setCaught(childExternals);
return childExternals;
}
/**
* All childExternals of a validating operation are marked as caught variables.
*/
@Override
public @Nullable Set<@NonNull CGVariable> visitCGIsUndefinedExp(@NonNull CGIsUndefinedExp cgElement) {
Set<@NonNull CGVariable> childExternals = super.visitCGIsUndefinedExp(cgElement);
context.setCaught(childExternals);
return childExternals;
}
/**
* The externals of a LetExp are the externals of the children less the let variable.
*/
@Override
public @Nullable Set<@NonNull CGVariable> visitCGLetExp(@NonNull CGLetExp cgElement) {
Set<@NonNull CGVariable> childExternals = super.visitCGLetExp(cgElement);
CGVariable cgInit = cgElement.getInit();
if (childExternals != null) {
childExternals.remove(cgInit);
}
return childExternals;
}
/**
* All childExternals of a validating operation are marked as caught variables.
*/
@Override
public @Nullable Set<@NonNull CGVariable> visitCGOperationCallExp(@NonNull CGOperationCallExp cgElement) {
Set<@NonNull CGVariable> childExternals = super.visitCGOperationCallExp(cgElement);
if (cgElement.isValidating()) {
context.setCaught(childExternals);
}
return childExternals;
}
/**
* The externals of a VariableExp are the externals of the referenced variable.
*/
@Override
public @Nullable Set<@NonNull CGVariable> visitCGVariable(@NonNull CGVariable cgElement) {
Set<@NonNull CGVariable> externals = super.visitCGVariable(cgElement);
context.allExternalVariables.put(cgElement, externals);
return externals;
}
/**
* The externals of a VariableExp are the externals of the referenced variable.
*/
@Override
public @Nullable Set<@NonNull CGVariable> visitCGVariableExp(@NonNull CGVariableExp cgElement) {
Set<@NonNull CGVariable> childExternal = null;
CGVariable cgVariable = cgElement.getReferredVariable();
if (cgVariable != null) {
childExternal = new HashSet<>();
childExternal.add(cgVariable);
}
return childExternal;
}
}
/*
* Perform a tree descent/ascent wrapping all child nodes in CGCatchExp or CGThrowExp to ensure that they satisfy
* the mustBeCaught/mustBeThrown requirement of their usage.
* The return from each child visit is true if isCaught, false if isThrown or isNonInvalid.
*/
public static class RewriteVisitor extends AbstractExtendingCGModelVisitor<@NonNull Boolean, @NonNull CodeGenAnalyzer>
{
protected final @NonNull Set<@NonNull CGVariable> externalVariables;
public RewriteVisitor(@NonNull CodeGenAnalyzer context, @NonNull Set<@NonNull CGVariable> externalVariables) {
super(context);
this.externalVariables = externalVariables;
}
protected void insertCatch(@NonNull CGValuedElement cgChild) {
assert !(cgChild instanceof CGCatchExp);
if (!cgChild.isNonInvalid()) {
CGCatchExp cgCatchExp = CGModelFactory.eINSTANCE.createCGCatchExp();
cgCatchExp.setCaught(true);
CGUtil.wrap(cgCatchExp, cgChild);
}
}
protected void insertThrow(@NonNull CGValuedElement cgChild) {
assert !(cgChild instanceof CGThrowExp);
if (!cgChild.isNonInvalid()) {
CGThrowExp cgThrowExp = CGModelFactory.eINSTANCE.createCGThrowExp();
CGUtil.wrap(cgThrowExp, cgChild);
}
}
protected void rewriteAsCaught(@Nullable CGValuedElement cgChild) {
if (cgChild != null) {
Boolean isCaught = cgChild.accept(this);
assert isCaught != null;
if (isCaught == Boolean.FALSE) {
insertCatch(cgChild);
}
}
}
protected void rewriteAsThrown(@Nullable CGValuedElement cgChild) {
if (cgChild != null) {
Boolean isCaught = cgChild.accept(this);
assert isCaught != null;
if (isCaught == Boolean.TRUE) {
insertThrow(cgChild);
}
}
}
@Override
public @NonNull Boolean safeVisit(@Nullable CGElement cgElement) {
return (cgElement != null) && visit(cgElement);
}
@Override
public @NonNull Boolean visiting(@NonNull CGElement visitable) {
throw new UnsupportedOperationException(getClass().getSimpleName() + ": " + visitable.getClass().getSimpleName());
}
@Override
public @NonNull Boolean visitCGConstant(@NonNull CGConstant cgConstant) {
return false;
}
@Override
public @NonNull Boolean visitCGConstantExp(@NonNull CGConstantExp cgElement) {
return safeVisit(cgElement.getReferredConstant());
}
@Override
public @NonNull Boolean visitCGElement(@NonNull CGElement cgElement) {
boolean isCaught = false;
for (@NonNull CGElement cgChild : cgElement.getChildren()) {
if (ClassUtil.nonNullState(cgChild.accept(this))) {
isCaught = true;
}
}
return isCaught;
}
@Override
public @NonNull Boolean visitCGIfExp(@NonNull CGIfExp cgElement) {
CGValuedElement cgCondition = cgElement.getCondition();
CGValuedElement cgThen = cgElement.getThenExpression();
CGValuedElement cgElse = cgElement.getElseExpression();
boolean conditionIsCaught = (cgCondition != null) && ClassUtil.nonNullState(cgCondition.accept(this));
boolean thenIsCaught = (cgThen != null) && ClassUtil.nonNullState(cgThen.accept(this));
boolean elseIsCaught = (cgElse != null) && ClassUtil.nonNullState(cgElse.accept(this));
// if works for caught or thrown condition
if (!conditionIsCaught || (thenIsCaught != elseIsCaught)) {
if ((cgThen != null) && thenIsCaught) {
insertThrow(cgThen);
}
if ((cgElse != null) && elseIsCaught) {
insertThrow(cgElse);
}
}
boolean isCaught = conditionIsCaught && thenIsCaught && elseIsCaught;
cgElement.setCaught(false);
return isCaught;
}
@Override
public @NonNull Boolean visitCGInvalid(@NonNull CGInvalid object) {
return true;
}
@Override
public @NonNull Boolean visitCGIsEqualExp(@NonNull CGIsEqualExp cgElement) {
rewriteAsThrown(cgElement.getSource());
rewriteAsThrown(cgElement.getArgument());
cgElement.setCaught(false);
return false;
}
@Override
public @NonNull Boolean visitCGIsEqual2Exp(@NonNull CGIsEqual2Exp cgElement) {
rewriteAsCaught(cgElement.getSource());
rewriteAsCaught(cgElement.getArgument());
cgElement.setCaught(true);
return false;
}
@Override
public @NonNull Boolean visitCGIsInvalidExp(@NonNull CGIsInvalidExp cgElement) {
rewriteAsCaught(cgElement.getSource());
cgElement.setCaught(false);
return false;
}
// @Override
// public @NonNull Boolean visitCGIsKindOfExp(@NonNull CGIsKindOfExp cgElement) {
// rewriteAsCaught(cgElement.getSource());
// cgElement.setCaught(true);
// return false;
// }
@Override
public @NonNull Boolean visitCGIsUndefinedExp(@NonNull CGIsUndefinedExp cgElement) {
rewriteAsCaught(cgElement.getSource());
cgElement.setCaught(false);
return false;
}
@Override
public @NonNull Boolean visitCGIterationCallExp(@NonNull CGIterationCallExp cgElement) {
rewriteAsThrown(cgElement.getSource());
for (CGIterator cgIterator : cgElement.getIterators()) {
cgIterator.accept(this);
}
for (CGIterator cgCoIterator : cgElement.getCoIterators()) {
cgCoIterator.accept(this);
}
if (cgElement.getReferredIteration().isIsValidating()) {
rewriteAsCaught(cgElement.getBody());
}
else {
rewriteAsThrown(cgElement.getBody());
}
cgElement.setCaught(false);
return false;
}
@Override
public @NonNull Boolean visitCGIterator(@NonNull CGIterator cgElement) {
rewriteAsThrown(cgElement.getInit());
cgElement.setCaught(false);
return false;
}
@Override
public @NonNull Boolean visitCGLetExp(@NonNull CGLetExp cgElement) {
safeVisit(cgElement.getInit());
boolean isCaught = safeVisit(cgElement.getIn());
cgElement.setCaught(false);
return isCaught;
}
@Override
public @NonNull Boolean visitCGLibraryIterateCallExp(@NonNull CGLibraryIterateCallExp cgElement) {
safeVisit(cgElement.getResult());
boolean isCaught = super.visitCGLibraryIterateCallExp(cgElement) == Boolean.TRUE;
return isCaught;
}
// @Override
// public @NonNull Boolean visitCGLibraryIterationCallExp(@NonNull CGLibraryIterationCallExp cgElement) {
// boolean isCaught = super.visitCGLibraryIterationCallExp(cgElement) == Boolean.TRUE;
// if (LibraryConstants.NULL_SATISFIES_INVOLUTION) {
// LibraryFeature implementation = cgElement.getReferredIteration().getImplementation();
// if ((implementation == ExistsIteration.INSTANCE) || (implementation == ForAllIteration.INSTANCE)) {
// rewriteAsCaught(cgElement.getBody());
// }
// }
// return isCaught;
// }
@Override
public @NonNull Boolean visitCGNavigationCallExp(@NonNull CGNavigationCallExp cgElement) {
rewriteAsThrown(cgElement.getSource());
cgElement.setCaught(false);
return false;
}
@Override
public @NonNull Boolean visitCGOperationCallExp(@NonNull CGOperationCallExp cgElement) {
// Operation referredOperation = cgElement.getReferredOperation();
// if (referredOperation != null) {
// String name = referredOperation.getName();
// System.out.println("visitCGOperationCallExp " + name);
// if ("implies".equals(name)) {
// System.out.println("Got it");
// }
// }
List<CGValuedElement> cgArguments = cgElement.getArguments();
int iSize = cgArguments.size();
if (cgElement.isValidating()) {
rewriteAsCaught(cgElement.getSource());
for (int i = 0; i < iSize; i++) { // Indexed to avoid CME
rewriteAsCaught(cgArguments.get(i));
}
}
else {
rewriteAsThrown(cgElement.getSource());
for (int i = 0; i < iSize; i++) { // Indexed to avoid CME
rewriteAsThrown(cgArguments.get(i));
}
}
cgElement.setCaught(false);
return false;
}
@Override
public @NonNull Boolean visitCGValuedElement(@NonNull CGValuedElement cgElement) {
boolean isCaught = super.visitCGValuedElement(cgElement) == Boolean.TRUE;
cgElement.setCaught(isCaught);
return isCaught;
}
@Override
public @NonNull Boolean visitCGVariable(@NonNull CGVariable cgElement) {
boolean isCaught = false;
CGValuedElement cgInit = cgElement.getInit();
if (cgInit != null) {
if (ClassUtil.nonNullState(cgInit.accept(this))) { // If explicitly caught
isCaught = true; // then just propagate caught
}
else if (externalVariables.contains(cgElement)) { // If not caught but needs to be
insertCatch(cgInit); // catch it
isCaught = true;
}
}
cgElement.setCaught(isCaught);
return isCaught;
}
@Override
public @NonNull Boolean visitCGVariableExp(@NonNull CGVariableExp cgElement) {
CGVariable referredVariable = cgElement.getReferredVariable();
boolean isCaught = referredVariable.isCaught() || externalVariables.contains(referredVariable);
cgElement.setCaught(isCaught);
return isCaught;
}
}
protected final @NonNull CodeGenAnalyzer analyzer;
/**
* The CGVariables that are accessed outside their defining tree.
*/
private final @NonNull Set<@NonNull CGVariable> externalVariables = new HashSet<>();
/**
* The CGVariables that are accessed by the init of a given CGVariable.
*/
private final @NonNull Map<@NonNull CGVariable, @Nullable Set<@NonNull CGVariable>> allExternalVariables = new HashMap<>();
public FieldingAnalyzer(@NonNull CodeGenAnalyzer analyzer) {
this.analyzer = analyzer;
}
public void analyze(@NonNull CGElement cgTree, boolean mustBeCaught) {
AnalysisVisitor analysisVisitor = createAnalysisVisitor();
cgTree.accept(analysisVisitor);
RewriteVisitor rewriteVisitor = createRewriteVisitor(externalVariables);
cgTree.accept(rewriteVisitor);
}
protected @NonNull AnalysisVisitor createAnalysisVisitor() {
return new AnalysisVisitor(this);
}
protected @NonNull RewriteVisitor createRewriteVisitor(@NonNull Set<@NonNull CGVariable> caughtVariables) {
return new RewriteVisitor(analyzer, caughtVariables);
}
protected boolean isValidating(EObject eObject) {
if (eObject instanceof OperationCallExp) {
OperationCallExp operationCall = (OperationCallExp)eObject;
Operation operation = operationCall.getReferredOperation();
if (operation.isIsValidating()) {
return true;
}
}
return false;
}
public void setCaught(@Nullable Set<@NonNull CGVariable> catchers) {
if (catchers != null) {
for (CGVariable catcher : catchers) {
if (!catcher.isNonInvalid()) {
externalVariables.add(catcher);
}
Set<CGVariable> indirectCatchers = allExternalVariables.get(catcher);
if (indirectCatchers != null) {
for (CGVariable indirectCatcher : indirectCatchers) {
if (!indirectCatcher.isNonInvalid()) {
externalVariables.add(indirectCatcher);
}
}
}
}
}
}
}