/******************************************************************************* * Copyright (c) 2010-2012, Mark Czotter, Zoltan Ujhelyi, Istvan Rath and Daniel Varro * 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: * Mark Czotter, Zoltan Ujhelyi - initial API and implementation *******************************************************************************/ package org.eclipse.incquery.patternlanguage.validation; import static org.eclipse.xtext.util.Strings.equal; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.incquery.patternlanguage.annotations.IPatternAnnotationValidator; import org.eclipse.incquery.patternlanguage.annotations.PatternAnnotationProvider; import org.eclipse.incquery.patternlanguage.helper.CorePatternLanguageHelper; import org.eclipse.incquery.patternlanguage.patternLanguage.AggregatedValue; import org.eclipse.incquery.patternlanguage.patternLanguage.Annotation; import org.eclipse.incquery.patternlanguage.patternLanguage.AnnotationParameter; import org.eclipse.incquery.patternlanguage.patternLanguage.BoolValue; import org.eclipse.incquery.patternlanguage.patternLanguage.CheckConstraint; import org.eclipse.incquery.patternlanguage.patternLanguage.CompareConstraint; import org.eclipse.incquery.patternlanguage.patternLanguage.CompareFeature; import org.eclipse.incquery.patternlanguage.patternLanguage.Constraint; import org.eclipse.incquery.patternlanguage.patternLanguage.DoubleValue; import org.eclipse.incquery.patternlanguage.patternLanguage.IntValue; import org.eclipse.incquery.patternlanguage.patternLanguage.ListValue; import org.eclipse.incquery.patternlanguage.patternLanguage.ParameterRef; import org.eclipse.incquery.patternlanguage.patternLanguage.Pattern; import org.eclipse.incquery.patternlanguage.patternLanguage.PatternBody; import org.eclipse.incquery.patternlanguage.patternLanguage.PatternCall; import org.eclipse.incquery.patternlanguage.patternLanguage.PatternCompositionConstraint; import org.eclipse.incquery.patternlanguage.patternLanguage.PatternLanguagePackage; import org.eclipse.incquery.patternlanguage.patternLanguage.PatternModel; import org.eclipse.incquery.patternlanguage.patternLanguage.StringValue; import org.eclipse.incquery.patternlanguage.patternLanguage.ValueReference; import org.eclipse.incquery.patternlanguage.patternLanguage.Variable; import org.eclipse.incquery.patternlanguage.patternLanguage.VariableReference; import org.eclipse.incquery.patternlanguage.patternLanguage.VariableValue; import org.eclipse.incquery.patternlanguage.validation.VariableReferenceCount.ReferenceType; import org.eclipse.incquery.patternlanguage.validation.whitelist.XBasePureCheckerUtil; import org.eclipse.xtext.common.types.JvmIdentifiableElement; import org.eclipse.xtext.common.types.JvmOperation; import org.eclipse.xtext.common.types.JvmTypeReference; import org.eclipse.xtext.common.types.util.Primitives; import org.eclipse.xtext.common.types.util.TypeConformanceComputer; import org.eclipse.xtext.validation.Check; import org.eclipse.xtext.xbase.XExpression; import org.eclipse.xtext.xbase.XFeatureCall; import org.eclipse.xtext.xbase.typing.ITypeProvider; import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; /** * Validators for Core Pattern Language. * <p> * Validators implemented: * </p> * <ul> * <li>Duplicate parameter in pattern declaration</li> * <li>Duplicate pattern definition (name duplication only, better calculation is needed)</li> * <li>Pattern call parameter checking (only the number of the parameters, types not supported yet)</li> * <li>Empty PatternBody check</li> * </ul> * * @author Mark Czotter * */ @SuppressWarnings("restriction") public class PatternLanguageJavaValidator extends AbstractPatternLanguageJavaValidator implements IIssueCallback { public static final String DUPLICATE_VARIABLE_MESSAGE = "Duplicate parameter "; public static final String DUPLICATE_PATTERN_DEFINITION_MESSAGE = "Duplicate pattern "; public static final String UNKNOWN_ANNOTATION_ATTRIBUTE = "Undefined annotation attribute "; public static final String MISSING_ANNOTATION_ATTRIBUTE = "Required attribute missing "; public static final String ANNOTATION_PARAMETER_TYPE_ERROR = "Invalid parameter type %s. Expected %s"; public static final String TRANSITIVE_CLOSURE_ARITY_IN_PATTERNCALL = "The pattern %s is not of binary arity (it has %d parameters), therefore transitive closure is not supported."; public static final String TRANSITIVE_CLOSURE_ONLY_IN_POSITIVE_COMPOSITION = "Transitive closure of %s is currently only allowed in simple positive pattern calls (no negation or aggregation)."; public static final String UNUSED_PRIVATE_PATTERN_MESSAGE = "The pattern '%s' is never used locally."; @Inject private PatternAnnotationProvider annotationProvider; @Inject private ITypeProvider provider; @Inject private TypeConformanceComputer typeConformance; @Inject private Primitives primitives; @Check public void checkPatternParameters(Pattern pattern) { if (pattern.getParameters().size() == 0) { warning("Parameterless patterns can only be used to check for existence of a condition.", PatternLanguagePackage.Literals.PATTERN__NAME, IssueCodes.MISSING_PATTERN_PARAMETERS); // As no duplicate parameters are available, returning now return; } for (int i = 0; i < pattern.getParameters().size(); ++i) { String leftParameterName = pattern.getParameters().get(i).getName(); for (int j = i + 1; j < pattern.getParameters().size(); ++j) { if (equal(leftParameterName, pattern.getParameters().get(j).getName())) { error(DUPLICATE_VARIABLE_MESSAGE + leftParameterName, PatternLanguagePackage.Literals.PATTERN__PARAMETERS, i, IssueCodes.DUPLICATE_PATTERN_PARAMETER_NAME); error(DUPLICATE_VARIABLE_MESSAGE + leftParameterName, PatternLanguagePackage.Literals.PATTERN__PARAMETERS, j, IssueCodes.DUPLICATE_PATTERN_PARAMETER_NAME); } } } } @Check public void checkPrivatePatternUsage(Pattern pattern) { if (CorePatternLanguageHelper.isPrivate(pattern) && !isLocallyUsed(pattern, pattern.eContainer())) { String message = String.format(UNUSED_PRIVATE_PATTERN_MESSAGE, pattern.getName()); warning(message, PatternLanguagePackage.Literals.PATTERN__NAME, IssueCodes.UNUSED_PRIVATE_PATTERN); } } @Check public void checkPatternCallParameters(PatternCall call) { if (call.getPatternRef() != null && call.getPatternRef().getName() != null && call.getParameters() != null) { final int definitionParameterSize = call.getPatternRef().getParameters().size(); final int callParameterSize = call.getParameters().size(); if (definitionParameterSize != callParameterSize) { error("The pattern " + getFormattedPattern(call.getPatternRef()) + " is not applicable for the arguments(" + getFormattedArgumentsList(call) + ")", PatternLanguagePackage.Literals.PATTERN_CALL__PATTERN_REF, IssueCodes.WRONG_NUMBER_PATTERNCALL_PARAMETER); } } } @Check public void checkApplicabilityOfTransitiveClosureInPatternCall(PatternCall call) { final Pattern patternRef = call.getPatternRef(); final EObject eContainer = call.eContainer(); if (patternRef != null && call.isTransitive()) { if (patternRef.getParameters() != null) { final int arity = patternRef.getParameters().size(); if (2 != arity) { error(String .format(TRANSITIVE_CLOSURE_ARITY_IN_PATTERNCALL, getFormattedPattern(patternRef), arity), PatternLanguagePackage.Literals.PATTERN_CALL__TRANSITIVE, IssueCodes.TRANSITIVE_PATTERNCALL_ARITY); } else { JvmTypeReference type1 = provider.getTypeForIdentifiable(patternRef.getParameters().get(0)); JvmTypeReference type2 = provider.getTypeForIdentifiable(patternRef.getParameters().get(1)); if (!typeConformance.isConformant(type1, type2) && !typeConformance.isConformant(type2, type1)) { error(String.format( "The parameter types %s and %s are not compatible, so no transitive references can exist in instance models.", type1.getSimpleName(), type2.getSimpleName()), PatternLanguagePackage.Literals.PATTERN_CALL__PARAMETERS, IssueCodes.TRANSITIVE_PATTERNCALL_TYPE); } } } if (eContainer != null && (!(eContainer instanceof PatternCompositionConstraint) || ((PatternCompositionConstraint) eContainer) .isNegative())) { error(String.format(TRANSITIVE_CLOSURE_ONLY_IN_POSITIVE_COMPOSITION, getFormattedPattern(patternRef)), PatternLanguagePackage.Literals.PATTERN_CALL__TRANSITIVE, IssueCodes.TRANSITIVE_PATTERNCALL_NOT_APPLICABLE); } } } @Check public void checkPatterns(PatternModel model) { if (model.getPatterns() != null && !model.getPatterns().isEmpty()) { // TODO: more precise calculation is needed for duplicate patterns // (number and type of pattern parameters) for (int i = 0; i < model.getPatterns().size(); ++i) { Pattern leftPattern = model.getPatterns().get(i); String leftPatternName = leftPattern.getName(); for (int j = i + 1; j < model.getPatterns().size(); ++j) { Pattern rightPattern = model.getPatterns().get(j); String rightPatternName = rightPattern.getName(); if (equal(leftPatternName, rightPatternName)) { error(DUPLICATE_PATTERN_DEFINITION_MESSAGE + leftPatternName, leftPattern, PatternLanguagePackage.Literals.PATTERN__NAME, IssueCodes.DUPLICATE_PATTERN_DEFINITION); error(DUPLICATE_PATTERN_DEFINITION_MESSAGE + rightPatternName, rightPattern, PatternLanguagePackage.Literals.PATTERN__NAME, IssueCodes.DUPLICATE_PATTERN_DEFINITION); } } } } } @Check public void checkPatternBody(PatternBody body) { if (body.getConstraints().isEmpty()) { String bodyName = getName(body); if (bodyName == null) { Pattern pattern = ((Pattern) body.eContainer()); String patternName = pattern.getName(); error("A patternbody of " + patternName + " is empty", body, PatternLanguagePackage.Literals.PATTERN_BODY__CONSTRAINTS, IssueCodes.PATTERN_BODY_EMPTY); } else { error("The patternbody " + bodyName + " cannot be empty", body, PatternLanguagePackage.Literals.PATTERN_BODY__NAME, IssueCodes.PATTERN_BODY_EMPTY); } } } @Check public void checkAnnotation(Annotation annotation) { if (annotationProvider.hasValidator(annotation.getName())) { IPatternAnnotationValidator validator = annotationProvider.getValidator(annotation.getName()); // Check for unknown annotation attributes for (AnnotationParameter unknownParameter : validator.getUnknownAttributes(annotation)) { error(UNKNOWN_ANNOTATION_ATTRIBUTE + unknownParameter.getName(), unknownParameter, PatternLanguagePackage.Literals.ANNOTATION_PARAMETER__NAME, annotation.getParameters().indexOf(unknownParameter), IssueCodes.UNKNOWN_ANNOTATION_PARAMETER); } // Check for missing mandatory attributes for (String missingAttribute : validator.getMissingMandatoryAttributes(annotation)) { error(MISSING_ANNOTATION_ATTRIBUTE + missingAttribute, annotation, PatternLanguagePackage.Literals.ANNOTATION__PARAMETERS, IssueCodes.MISSING_REQUIRED_ANNOTATION_PARAMETER); } // Check for annotation parameter types for (AnnotationParameter parameter : annotation.getParameters()) { Class<? extends ValueReference> expectedParameterType = validator.getExpectedParameterType(parameter); if (expectedParameterType != null && parameter.getValue() != null && !expectedParameterType.isAssignableFrom(parameter.getValue().getClass())) { error(String.format(ANNOTATION_PARAMETER_TYPE_ERROR, getTypeName(parameter.getValue().getClass()), getTypeName(expectedParameterType)), parameter, PatternLanguagePackage.Literals.ANNOTATION_PARAMETER__NAME, annotation.getParameters() .indexOf(parameter), IssueCodes.MISTYPED_ANNOTATION_PARAMETER); } } // Execute extra validation if (validator.getAdditionalValidator() != null) { validator.getAdditionalValidator().executeAdditionalValidation(annotation, this); } } else { warning("Unknown annotation " + annotation.getName(), PatternLanguagePackage.Literals.ANNOTATION__NAME, IssueCodes.UNKNOWN_ANNOTATION); } } @Check public void checkCompareConstraints(CompareConstraint constraint) { ValueReference op1 = constraint.getLeftOperand(); ValueReference op2 = constraint.getRightOperand(); if (op1 == null || op2 == null) { return; } boolean op1Constant = PatternLanguagePackage.Literals.LITERAL_VALUE_REFERENCE.isSuperTypeOf(op1.eClass()); boolean op2Constant = PatternLanguagePackage.Literals.LITERAL_VALUE_REFERENCE.isSuperTypeOf(op2.eClass()); boolean op1Variable = PatternLanguagePackage.Literals.VARIABLE_VALUE.isSuperTypeOf(op1.eClass()); boolean op2Variable = PatternLanguagePackage.Literals.VARIABLE_VALUE.isSuperTypeOf(op2.eClass()); // If both operands are constant literals, issue a warning if (op1Constant && op2Constant) { warning("Both operands are constants - constraint is always true or always false.", PatternLanguagePackage.Literals.COMPARE_CONSTRAINT__LEFT_OPERAND, IssueCodes.CONSTANT_COMPARE_CONSTRAINT); warning("Both operands are constants - constraint is always true or always false.", PatternLanguagePackage.Literals.COMPARE_CONSTRAINT__RIGHT_OPERAND, IssueCodes.CONSTANT_COMPARE_CONSTRAINT); } // If both operands are the same, issues a warning if (op1Variable && op2Variable) { VariableValue op1v = (VariableValue) op1; VariableValue op2v = (VariableValue) op2; if (op1v.getValue().getVar().equals(op2v.getValue().getVar())) { warning("Comparing a variable with itself.", PatternLanguagePackage.Literals.COMPARE_CONSTRAINT__LEFT_OPERAND, IssueCodes.SELF_COMPARE_CONSTRAINT); warning("Comparing a variable with itself.", PatternLanguagePackage.Literals.COMPARE_CONSTRAINT__RIGHT_OPERAND, IssueCodes.SELF_COMPARE_CONSTRAINT); } } } private String getName(PatternBody body) { if (body.getName() != null && !body.getName().isEmpty()) { return "'" + body.getName() + "'"; } return null; } private String getTypeName(Class<? extends ValueReference> typeClass) { if (IntValue.class.isAssignableFrom(typeClass)) { return "Integer"; } else if (DoubleValue.class.isAssignableFrom(typeClass)) { return "Double"; } else if (BoolValue.class.isAssignableFrom(typeClass)) { return "Boolean"; } else if (StringValue.class.isAssignableFrom(typeClass)) { return "String"; } else if (ListValue.class.isAssignableFrom(typeClass)) { return "List"; } else if (VariableValue.class.isAssignableFrom(typeClass)) { return "Variable"; } return "UNDEFINED"; } private String getConstantAsString(ValueReference ref) { if (ref instanceof IntValue) { return Integer.toString(((IntValue) ref).getValue()); } else if (ref instanceof DoubleValue) { return Double.toString(((DoubleValue) ref).getValue()); } else if (ref instanceof BoolValue) { return Boolean.toString(((BoolValue) ref).isValue()); } else if (ref instanceof StringValue) { return "\"" + ((StringValue) ref).getValue() + "\""; } else if (ref instanceof ListValue) { StringBuilder sb = new StringBuilder(); sb.append("{ "); for (Iterator<ValueReference> iter = ((ListValue) ref).getValues().iterator(); iter.hasNext();) { sb.append(getConstantAsString(iter.next())); if (iter.hasNext()) { sb.append(", "); } } sb.append("}"); return sb.toString(); } else if (ref instanceof VariableValue) { return ((VariableValue) ref).getValue().getVar(); } return "UNDEFINED"; } private String getFormattedPattern(Pattern pattern) { StringBuilder builder = new StringBuilder(); builder.append(pattern.getName()); builder.append("("); for (Iterator<Variable> iter = pattern.getParameters().iterator(); iter.hasNext();) { builder.append(iter.next().getName()); if (iter.hasNext()) { builder.append(", "); } } builder.append(")"); return builder.toString(); } protected String getFormattedArgumentsList(PatternCall call) { StringBuilder builder = new StringBuilder(); for (Iterator<ValueReference> iter = call.getParameters().iterator(); iter.hasNext();) { ValueReference parameter = iter.next(); builder.append(getConstantAsString(parameter)); if (iter.hasNext()) { builder.append(", "); } } return builder.toString(); } @Check public void checkPackageDeclaration(PatternModel model) { String packageName = model.getPackageName(); if (packageName != null && !packageName.equals(packageName.toLowerCase())) { error("Only lowercase package names supported", PatternLanguagePackage.Literals.PATTERN_MODEL__PACKAGE_NAME, IssueCodes.LOWERCASE_PATTERN_NAME); } } @Check public void checkReturnTypeOfCheckConstraints(CheckConstraint checkConstraint) { XExpression xExpression = checkConstraint.getExpression(); if (xExpression != null) { JvmTypeReference type = provider.getCommonReturnType(xExpression, true); String simpleName = primitives.asPrimitiveIfWrapperType(type).getSimpleName(); if (!simpleName.equals("boolean")) { error("Check expressions must return boolean instead of " + type.getSimpleName(), checkConstraint, PatternLanguagePackage.Literals.CHECK_CONSTRAINT__EXPRESSION, IssueCodes.CHECK_MUST_BE_BOOLEAN); } } } @Check public void checkVariableNames(PatternBody body) { for (Variable var1 : body.getVariables()) { Variable otherVar = null; for (Variable var2 : body.getVariables()) { if (isNamedSingleUse(var1) && var1.getSimpleName().substring(1).equals(var2.getName())) { otherVar = var2; } } if (otherVar != null) { if (var1.eContainer() instanceof PatternBody && !var1.getReferences().isEmpty()) { // Local variables do not have source location warning(String.format( "Dubius variable naming: Single use variable %s shares its name with the variable %s", var1.getSimpleName(), otherVar.getSimpleName()), var1.getReferences().get(0), PatternLanguagePackage.Literals.VARIABLE_REFERENCE__VARIABLE, IssueCodes.DUBIUS_VARIABLE_NAME); } else { warning(String.format( "Dubius variable naming: Single use variable %s shares its name with the variable %s", var1.getSimpleName(), otherVar.getSimpleName()), var1, PatternLanguagePackage.Literals.VARIABLE__NAME, IssueCodes.DUBIUS_VARIABLE_NAME); } } } } @Check public void checkVariableUsageCounters(PatternBody body) { Map<Variable, VariableReferenceCount> refCounters = calculateUsageCounts(body); UnionFindForVariables variableUnions = calculateEqualVariables(body); for (Variable var : body.getVariables()) { if (var instanceof ParameterRef) { checkParameterUsageCounter((ParameterRef) var, refCounters, variableUnions, body); } else { checkLocalVariableUsageCounter(var, refCounters, variableUnions); } } } private void checkParameterUsageCounter(ParameterRef var, Map<Variable, VariableReferenceCount> refCounters, UnionFindForVariables variableUnions, PatternBody body) { Variable parameter = var.getReferredParam(); if (refCounters.get(var).getReferenceCount() == 0) { error(String.format("Parameter '%s' is never referenced in body '%s'.", parameter.getName(), getPatternBodyName(body)), parameter, PatternLanguagePackage.Literals.VARIABLE__NAME, IssueCodes.SYMBOLIC_VARIABLE_NEVER_REFERENCED); } else if (refCounters.get(var).getReferenceCount(ReferenceType.POSITIVE) == 0 && getReferenceCount(var, ReferenceType.POSITIVE, refCounters, variableUnions) == 0) { error(String.format("Parameter '%s' has no positive reference in body '%s'.", var.getName(), getPatternBodyName(body)), parameter, PatternLanguagePackage.Literals.VARIABLE__NAME, IssueCodes.SYMBOLIC_VARIABLE_NO_POSITIVE_REFERENCE); } } private void checkLocalVariableUsageCounter(Variable var, Map<Variable, VariableReferenceCount> refCounters, UnionFindForVariables variableUnions) { if (refCounters.get(var).getReferenceCount(ReferenceType.POSITIVE) == 1 && refCounters.get(var).getReferenceCount() == 1 && !isNamedSingleUse(var) && !isUnnamedSingleUseVariable(var)) { warning(String.format( "Local variable '%s' is referenced only once. Is it mistyped? Start its name with '_' if intentional.", var.getName()), var.getReferences().get(0), PatternLanguagePackage.Literals.VARIABLE_REFERENCE__VAR, IssueCodes.LOCAL_VARIABLE_REFERENCED_ONCE); } else if (refCounters.get(var).getReferenceCount() > 1 && isNamedSingleUse(var)) { for (VariableReference ref : var.getReferences()) { error(String.format("Named single-use variable %s used multiple times.", var.getName()), ref, PatternLanguagePackage.Literals.VARIABLE_REFERENCE__VAR, IssueCodes.ANONYM_VARIABLE_MULTIPLE_REFERENCE); } } else if (refCounters.get(var).getReferenceCount(ReferenceType.POSITIVE) == 0) { if (refCounters.get(var).getReferenceCount(ReferenceType.NEGATIVE) == 0) { error(String.format( "Local variable '%s' appears in read-only context(s) only, thus its value cannot be determined.", var.getName()), var, PatternLanguagePackage.Literals.VARIABLE__NAME, IssueCodes.LOCAL_VARIABLE_READONLY); } else if (refCounters.get(var).getReferenceCount(ReferenceType.NEGATIVE) == 1 && refCounters.get(var).getReferenceCount() == 1 && !isNamedSingleUse(var) && !isUnnamedSingleUseVariable(var)) { warning(String.format( "Local variable '%s' will be quantified because it is used only here. Acknowledge this by prefixing its name with '_'.", var.getName()), var.getReferences().get(0), PatternLanguagePackage.Literals.VARIABLE_REFERENCE__VAR, IssueCodes.LOCAL_VARIABLE_QUANTIFIED_REFERENCE); } else if (refCounters.get(var).getReferenceCount() > 1) { error(String.format( "Local variable '%s' has no positive reference, thus its value cannot be determined.", var.getName()), var.getReferences().get(0), PatternLanguagePackage.Literals.VARIABLE_REFERENCE__VAR, IssueCodes.LOCAL_VARIABLE_NO_POSITIVE_REFERENCE); } } } private int getReferenceCount(Variable var, ReferenceType type, Map<Variable, VariableReferenceCount> refCounters, UnionFindForVariables variableUnions) { int sum = 0; for (Variable unionVar : variableUnions.getPartitionOfVariable(var)) { sum += refCounters.get(unionVar).getReferenceCount(type); } return sum; } private Map<Variable, VariableReferenceCount> calculateUsageCounts(PatternBody body) { Map<Variable, VariableReferenceCount> refCounters = new Hashtable<Variable, VariableReferenceCount>(); EList<Variable> variables = body.getVariables(); for (Variable var : variables) { boolean isParameter = var instanceof ParameterRef; refCounters.put(var, new VariableReferenceCount(var, isParameter)); } TreeIterator<EObject> it = body.eAllContents(); while (it.hasNext()) { EObject obj = it.next(); if (obj instanceof CheckConstraint) { CheckConstraint constraint = (CheckConstraint) obj; for (Variable var : CorePatternLanguageHelper.getReferencedPatternVariablesOfXExpression(constraint .getExpression())) { refCounters.get(var).incrementCounter(ReferenceType.READ_ONLY); } it.prune(); } if (obj instanceof VariableReference) { refCounters.get(((VariableReference) obj).getVariable()).incrementCounter( classifyReference((VariableReference) obj)); } } return refCounters; } private UnionFindForVariables calculateEqualVariables(PatternBody body) { UnionFindForVariables unions = new UnionFindForVariables(body.getVariables()); TreeIterator<EObject> it = body.eAllContents(); while (it.hasNext()) { EObject obj = it.next(); if (obj instanceof CompareConstraint) { CompareConstraint constraint = (CompareConstraint) obj; if (constraint.getFeature() == CompareFeature.EQUALITY) { ValueReference left = constraint.getLeftOperand(); ValueReference right = constraint.getRightOperand(); if (left instanceof VariableValue && right instanceof VariableValue) { unions.unite(ImmutableSet.of(((VariableValue) left).getValue().getVariable(), ((VariableValue) right).getValue().getVariable())); } } it.prune(); } else if (obj instanceof Constraint) { it.prune(); } } return unions; } private String getPatternBodyName(PatternBody patternBody) { return (patternBody.getName() != null) ? patternBody.getName() : String.format("#%d", ((Pattern) patternBody.eContainer()).getBodies().indexOf(patternBody) + 1); } private ReferenceType classifyReference(VariableReference ref) { EObject parent = ref; while (parent != null && !(parent instanceof Constraint || parent instanceof AggregatedValue)) { parent = parent.eContainer(); } if (parent instanceof CheckConstraint) { return ReferenceType.READ_ONLY; } else if (parent instanceof CompareConstraint) { CompareConstraint constraint = (CompareConstraint) parent; if (constraint.getFeature() == CompareFeature.EQUALITY) { if (constraint.getLeftOperand() instanceof VariableValue && !(constraint.getRightOperand() instanceof VariableValue)) { if (ref.equals(((VariableValue) constraint.getLeftOperand()).getValue())) { return ReferenceType.POSITIVE; } } if (constraint.getRightOperand() instanceof VariableValue && !(constraint.getLeftOperand() instanceof VariableValue)) { if (ref.equals(((VariableValue) constraint.getRightOperand()).getValue())) { return ReferenceType.POSITIVE; } } } return ReferenceType.READ_ONLY; } else if (parent instanceof PatternCompositionConstraint && ((PatternCompositionConstraint) parent).isNegative()) { return ReferenceType.NEGATIVE; } else if (parent instanceof AggregatedValue) { return ReferenceType.NEGATIVE; } // Other constraints use positive references return ReferenceType.POSITIVE; } /** * @return true if the variable is single-use a named variable */ public boolean isNamedSingleUse(Variable variable) { String name = variable.getName(); return name != null && name.startsWith("_") && !name.contains("<"); } /** * @return true if the variable is an unnamed single-use variable */ public boolean isUnnamedSingleUseVariable(Variable variable) { String name = variable.getName(); return name != null && name.startsWith("_") && name.contains("<"); } @Check public void checkForImpureJavaCallsInCheckConstraints(CheckConstraint checkConstraint) { XExpression xExpression = checkConstraint.getExpression(); Set<String> elementsWithWarnings = new HashSet<String>(); if (xExpression != null) { TreeIterator<EObject> eAllContents = xExpression.eAllContents(); while (eAllContents.hasNext()) { EObject nextEObject = eAllContents.next(); if (nextEObject instanceof XFeatureCall) { XFeatureCall xFeatureCall = (XFeatureCall) nextEObject; JvmIdentifiableElement jvmIdentifiableElement = xFeatureCall.getFeature(); if (jvmIdentifiableElement instanceof JvmOperation) { JvmOperation jvmOperation = (JvmOperation) jvmIdentifiableElement; if (XBasePureCheckerUtil.isImpureElement(jvmOperation)) { elementsWithWarnings.add(jvmOperation.getQualifiedName()); } } } } } if (!elementsWithWarnings.isEmpty()) { if (elementsWithWarnings.size() > 1) { warning("There are potentially problematic java calls in the check expression. Custom java calls without @Pure annotations " + "considered unsafe in IncQuery, for more information see. The possible errorneous calls are the following: " + elementsWithWarnings + ".", checkConstraint, PatternLanguagePackage.Literals.CHECK_CONSTRAINT__EXPRESSION, IssueCodes.CHECK_WITH_IMPURE_JAVA_CALLS); } else { warning("There is a potentially problematic java call in the check expression. Custom java calls without @Pure annotations " + "considered unsafe in IncQuery, for more information see. The possible errorneous call is the following: " + elementsWithWarnings + ".", checkConstraint, PatternLanguagePackage.Literals.CHECK_CONSTRAINT__EXPRESSION, IssueCodes.CHECK_WITH_IMPURE_JAVA_CALLS); } } } @Override public void warning(String message, EObject source, EStructuralFeature feature, String code, String... issueData) { super.warning(message, source, feature, code, issueData); } @Override public void error(String message, EObject source, EStructuralFeature feature, String code, String... issueData) { super.error(message, source, feature, code, issueData); } }