Java tutorial
/******************************************************************************* * 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.viatra.query.patternlanguage.validation; import static org.eclipse.xtext.util.Strings.equal; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.viatra.query.patternlanguage.annotations.IPatternAnnotationValidator; import org.eclipse.viatra.query.patternlanguage.annotations.PatternAnnotationProvider; import org.eclipse.viatra.query.patternlanguage.helper.CorePatternLanguageHelper; import org.eclipse.viatra.query.patternlanguage.patternLanguage.AggregatedValue; import org.eclipse.viatra.query.patternlanguage.patternLanguage.Annotation; import org.eclipse.viatra.query.patternlanguage.patternLanguage.AnnotationParameter; import org.eclipse.viatra.query.patternlanguage.patternLanguage.BoolValue; import org.eclipse.viatra.query.patternlanguage.patternLanguage.CheckConstraint; import org.eclipse.viatra.query.patternlanguage.patternLanguage.CompareConstraint; import org.eclipse.viatra.query.patternlanguage.patternLanguage.CompareFeature; import org.eclipse.viatra.query.patternlanguage.patternLanguage.Constraint; import org.eclipse.viatra.query.patternlanguage.patternLanguage.DoubleValue; import org.eclipse.viatra.query.patternlanguage.patternLanguage.FunctionEvaluationValue; import org.eclipse.viatra.query.patternlanguage.patternLanguage.IntValue; import org.eclipse.viatra.query.patternlanguage.patternLanguage.ListValue; import org.eclipse.viatra.query.patternlanguage.patternLanguage.Modifiers; import org.eclipse.viatra.query.patternlanguage.patternLanguage.ParameterRef; import org.eclipse.viatra.query.patternlanguage.patternLanguage.Pattern; import org.eclipse.viatra.query.patternlanguage.patternLanguage.PatternBody; import org.eclipse.viatra.query.patternlanguage.patternLanguage.PatternCall; import org.eclipse.viatra.query.patternlanguage.patternLanguage.PatternCompositionConstraint; import org.eclipse.viatra.query.patternlanguage.patternLanguage.PatternLanguagePackage; import org.eclipse.viatra.query.patternlanguage.patternLanguage.PatternModel; import org.eclipse.viatra.query.patternlanguage.patternLanguage.StringValue; import org.eclipse.viatra.query.patternlanguage.patternLanguage.ValueReference; import org.eclipse.viatra.query.patternlanguage.patternLanguage.Variable; import org.eclipse.viatra.query.patternlanguage.patternLanguage.VariableReference; import org.eclipse.viatra.query.patternlanguage.patternLanguage.VariableValue; import org.eclipse.viatra.query.patternlanguage.typing.ITypeInferrer; import org.eclipse.viatra.query.patternlanguage.typing.ITypeSystem; import org.eclipse.viatra.query.patternlanguage.validation.VariableReferenceCount.ReferenceType; import org.eclipse.viatra.query.patternlanguage.validation.whitelist.PureWhitelistExtensionLoader; import org.eclipse.viatra.query.patternlanguage.validation.whitelist.PurityChecker; import org.eclipse.viatra.query.runtime.matchers.algorithms.UnionFind; import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; import org.eclipse.xtext.common.types.JvmIdentifiableElement; import org.eclipse.xtext.common.types.JvmOperation; import org.eclipse.xtext.common.types.util.Primitives.Primitive; import org.eclipse.xtext.naming.IQualifiedNameProvider; import org.eclipse.xtext.naming.QualifiedName; import org.eclipse.xtext.resource.IContainer; import org.eclipse.xtext.resource.IEObjectDescription; import org.eclipse.xtext.resource.IResourceDescription; import org.eclipse.xtext.resource.impl.LiveShadowedResourceDescriptions; import org.eclipse.xtext.util.IResourceScopeCache; import org.eclipse.xtext.validation.Check; import org.eclipse.xtext.validation.CheckType; import org.eclipse.xtext.xbase.XExpression; import org.eclipse.xtext.xbase.XMemberFeatureCall; import org.eclipse.xtext.xbase.jvmmodel.IJvmModelAssociations; import org.eclipse.xtext.xbase.lib.Pure; import org.eclipse.xtext.xbase.typesystem.IBatchTypeResolver; import org.eclipse.xtext.xbase.typesystem.IResolvedTypes; import org.eclipse.xtext.xbase.typesystem.references.LightweightTypeReference; import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Sets; import com.google.inject.Inject; import com.google.inject.Provider; /** * 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> * <li>Check for recursive pattern calls</li> * </ul> * * @author Mark Czotter * @author Tamas Szabo (itemis AG) */ @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 %s (the shadowing pattern is in %s)"; 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."; public static final String RECURSIVE_PATTERN_CALL = "Recursive pattern call: %s"; @Inject private PatternAnnotationProvider annotationProvider; @Inject private IJvmModelAssociations associations; @Inject private ITypeSystem typeSystem; @Inject private ITypeInferrer typeInferrer; @Inject private IBatchTypeResolver typeResolver; @Inject private IResourceScopeCache cache; @Inject private LiveShadowedResourceDescriptions resourceDescriptions; @Inject private IQualifiedNameProvider nameProvider; @Inject private IContainer.Manager containerManager; @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 checkPrivatePatternCall(PatternCall call) { final Pattern calledPattern = call.getPatternRef(); if (calledPattern != null) { if (Iterables.any(calledPattern.getModifiers(), new Predicate<Modifiers>() { @Override public boolean apply(Modifiers input) { return input.isPrivate(); } }) && calledPattern.eResource() != call.eResource()) { error(String.format("The pattern %s is not visible.", getFormattedPattern(calledPattern)), PatternLanguagePackage.Literals.PATTERN_CALL__PATTERN_REF, IssueCodes.PRIVATE_PATTERN_CALLED); } } } @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 { IInputKey type1 = typeInferrer.getVariableType(patternRef.getParameters().get(0)); IInputKey type2 = typeInferrer.getVariableType(patternRef.getParameters().get(1)); if (!typeSystem.isConformant(type1, type2) && !typeSystem.isConformant(type2, type1)) { error(String.format( "The parameter types %s and %s are not compatible, so no transitive references can exist in instance models.", typeSystem.typeString(type1), typeSystem.typeString(type2)), 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) { resourceDescriptions.setContext(model); if (model.getPatterns() != null) { // TODO: more precise calculation is needed for duplicate patterns // (number and type of pattern parameters) for (Pattern pattern : model.getPatterns()) { QualifiedName fullyQualifiedName = nameProvider.getFullyQualifiedName(pattern); final Iterable<IEObjectDescription> shadowingPatternDescriptions = resourceDescriptions .getExportedObjects(PatternLanguagePackage.Literals.PATTERN, fullyQualifiedName, true); for (IEObjectDescription shadowingPatternDescription : shadowingPatternDescriptions) { EObject shadowingPattern = shadowingPatternDescription.getEObjectOrProxy(); if (shadowingPattern != pattern) { URI resourceUri = pattern.eResource().getURI(); URI otherResourceUri = shadowingPatternDescription.getEObjectURI().trimFragment(); // not using shadowingPattern because it might be proxy IResourceDescription resourceDescription = resourceDescriptions .getResourceDescription(resourceUri); IResourceDescription otherResourceDescription = resourceDescriptions .getResourceDescription(otherResourceUri); List<IContainer> visible = containerManager.getVisibleContainers(resourceDescription, resourceDescriptions); List<IContainer> visibleFromOther = containerManager .getVisibleContainers(otherResourceDescription, resourceDescriptions); if (Iterables.any(visible, contains(otherResourceDescription)) || Iterables.any(visibleFromOther, contains(resourceDescription))) { String otherResourcePath = Objects.firstNonNull(otherResourceUri.toPlatformString(true), otherResourceUri.toFileString()); error(String.format(DUPLICATE_PATTERN_DEFINITION_MESSAGE, fullyQualifiedName, otherResourcePath), pattern, PatternLanguagePackage.Literals.PATTERN__NAME, IssueCodes.DUPLICATE_PATTERN_DEFINITION); } } } } } } private static Predicate<IContainer> contains(final IResourceDescription resourceDescription) { return new Predicate<IContainer>() { @Override public boolean apply(IContainer container) { return Iterables.contains(container.getResourceDescriptions(), resourceDescription); } }; } @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(CheckType.NORMAL) 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); } else if (parameter.getValue() instanceof VariableValue) { VariableValue value = (VariableValue) parameter.getValue(); if (value.getValue().getVariable() == null) { error(String.format("Unknown variable %s", value.getValue().getVar()), parameter, PatternLanguagePackage.Literals.ANNOTATION_PARAMETER__VALUE, annotation.getParameters().indexOf(parameter), IssueCodes.MISTYPED_ANNOTATION_PARAMETER); } } else if (parameter.getValue() instanceof ListValue) { ListValue listValue = (ListValue) (parameter.getValue()); for (VariableValue value : Iterables.filter(listValue.getValues(), VariableValue.class)) { if (value.getValue().getVariable() == null) { error(String.format("Unknown variable %s", value.getValue().getVar()), listValue, PatternLanguagePackage.Literals.LIST_VALUE__VALUES, listValue.getValues().indexOf(value), 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); } } } @Check public void checkRecursivePatternCall(PatternCall call) { Map<PatternCall, Set<PatternCall>> graph = cache.get(call.eResource(), call.eResource(), new CallGraphProvider(call.eResource())); LinkedList<PatternCall> result = dfsCheckCycle(call, graph); if (result != null) { StringBuffer buffer = new StringBuffer(); boolean first = true; for (PatternCall elem : result) { if (first) { first = false; } else { buffer.append(" -> "); } buffer.append(prettyPrintPatternCall(elem)); } if (isNegativePatternCall(call)) { error(String.format(RECURSIVE_PATTERN_CALL, buffer.toString()), call, PatternLanguagePackage.Literals.PATTERN_CALL__PATTERN_REF, IssueCodes.RECURSIVE_PATTERN_CALL); } else { warning(String.format(RECURSIVE_PATTERN_CALL, buffer.toString()), call, PatternLanguagePackage.Literals.PATTERN_CALL__PATTERN_REF, IssueCodes.RECURSIVE_PATTERN_CALL); } } } private LinkedList<PatternCall> dfsCheckCycle(PatternCall source, Map<PatternCall, Set<PatternCall>> graph) { LinkedList<PatternCall> path = new LinkedList<PatternCall>(); path.add(source); return dfsCheckCycle(source, path, new HashSet<PatternCall>(), graph); } /** * Contract: * (1) path is the current path from source to the last element in path * (2) the first element of path is source * (3) seen is maintained globally within the recursive calls of dfsCheckCycle */ private LinkedList<PatternCall> dfsCheckCycle(PatternCall source, LinkedList<PatternCall> path, Set<PatternCall> seen, Map<PatternCall, Set<PatternCall>> graph) { PatternCall current = path.getLast(); if (!seen.contains(current)) { seen.add(current); for (PatternCall target : graph.get(current)) { path.add(target); if (target == source) { return path; } LinkedList<PatternCall> intermediate = dfsCheckCycle(source, path, seen, graph); if (intermediate != null) { return intermediate; } else { path.removeLast(); } } } // this means that no cycle has been found return null; } private boolean isNegativePatternCall(PatternCall call) { return (call.eContainer() instanceof PatternCompositionConstraint && ((PatternCompositionConstraint) call.eContainer()).isNegative()); } private String prettyPrintPatternCall(PatternCall call) { return (isNegativePatternCall(call) ? "neg " : "") + call.getPatternRef().getName(); } private static final Comparator<PatternCall> patternCallComparator = new Comparator<PatternCall>() { @Override public int compare(PatternCall p1, PatternCall p2) { return p1.getPatternRef().getName().compareTo(p2.getPatternRef().getName()); } }; private static class CallGraphProvider implements Provider<Map<PatternCall, Set<PatternCall>>> { private Resource resource; public CallGraphProvider(Resource resource) { this.resource = resource; } @Override public Map<PatternCall, Set<PatternCall>> get() { Map<PatternCall, Set<PatternCall>> graph = new HashMap<PatternCall, Set<PatternCall>>(); TreeIterator<EObject> resourceIterator = resource.getAllContents(); Set<PatternCall> knownCalls = Sets.newHashSet(Iterators.filter(resourceIterator, PatternCall.class)); Set<PatternCall> unprocessedCalls = Sets.difference(knownCalls, graph.keySet()); while (!unprocessedCalls.isEmpty()) { PatternCall source = unprocessedCalls.iterator().next(); Set<PatternCall> targets = new TreeSet<PatternCall>(patternCallComparator); graph.put(source, targets); TreeIterator<EObject> headIterator = source.getPatternRef().eAllContents(); while (headIterator.hasNext()) { EObject headContent = headIterator.next(); if (headContent instanceof PatternCall) { PatternCall target = (PatternCall) headContent; targets.add(target); } } knownCalls.addAll(targets); } return graph; } } 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.isEmpty()) { error("The package declaration must not be empty", PatternLanguagePackage.Literals.PATTERN_MODEL__PACKAGE_NAME, IssueCodes.PACKAGE_NAME_EMPTY); } if (packageName != null && !packageName.equals(packageName.toLowerCase())) { error("Only lowercase package names supported", PatternLanguagePackage.Literals.PATTERN_MODEL__PACKAGE_NAME, IssueCodes.PACKAGE_NAME_MISMATCH); } } @Check public void checkReturnTypeOfCheckConstraints(CheckConstraint checkConstraint) { XExpression xExpression = checkConstraint.getExpression(); if (xExpression != null) { final IResolvedTypes resolvedType = typeResolver.resolveTypes(xExpression); LightweightTypeReference type = resolvedType.getReturnType(xExpression); if (type.getPrimitiveIfWrapperType().getPrimitiveKind() != Primitive.Boolean) { error("Check expressions must return boolean instead of " + type.getSimpleName(), checkConstraint, PatternLanguagePackage.Literals.CHECK_CONSTRAINT__EXPRESSION, IssueCodes.CHECK_MUST_BE_BOOLEAN); } } } @Check(CheckType.NORMAL) 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(CheckType.NORMAL) public void checkVariableUsageCounters(PatternBody body) { UnionFind<Variable> variableUnions = calculateEqualVariables(body); Map<Set<Variable>, VariableReferenceCount> unifiedRefCounters = new HashMap<Set<Variable>, VariableReferenceCount>(); Map<Variable, VariableReferenceCount> individualRefCounters = new HashMap<Variable, VariableReferenceCount>(); calculateUsageCounts(body, variableUnions, individualRefCounters, unifiedRefCounters); for (Variable var : body.getVariables()) { if (var instanceof ParameterRef) { checkParameterUsageCounter((ParameterRef) var, individualRefCounters, unifiedRefCounters, variableUnions, body); } else { checkLocalVariableUsageCounter(var, individualRefCounters, unifiedRefCounters, variableUnions); } } } private void checkParameterUsageCounter(ParameterRef var, Map<Variable, VariableReferenceCount> individualCounters, Map<Set<Variable>, VariableReferenceCount> unifiedRefCounters, UnionFind<Variable> variableUnions, PatternBody body) { Variable parameter = var.getReferredParam(); VariableReferenceCount individualCounter = individualCounters.get(var); VariableReferenceCount unifiedCounter = unifiedRefCounters.get(variableUnions.getPartition(var)); if (individualCounter.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 (unifiedCounter.getReferenceCount(ReferenceType.POSITIVE) == 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> individualCounters, Map<Set<Variable>, VariableReferenceCount> unifiedRefCounters, UnionFind<Variable> variableUnions) { VariableReferenceCount individualCounter = individualCounters.get(var); VariableReferenceCount unifiedCounter = unifiedRefCounters.get(variableUnions.getPartition(var)); if (individualCounter.getReferenceCount(ReferenceType.POSITIVE) == 1 && individualCounter.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 (individualCounter.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 (unifiedCounter.getReferenceCount(ReferenceType.POSITIVE) == 0) { if (unifiedCounter.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 (individualCounter.getReferenceCount(ReferenceType.NEGATIVE) == 1 && individualCounter.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 (unifiedCounter.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, // UnionFind<Variable> variableUnions) { // int sum = 0; // for (Variable unionVar : variableUnions.getPartition(var)) { // sum += refCounters.get(unionVar).getReferenceCount(type); // } // return sum; // } private void calculateUsageCounts(PatternBody body, UnionFind<Variable> variableUnions, Map<Variable, VariableReferenceCount> individualRefCounters, Map<Set<Variable>, VariableReferenceCount> unifiedRefCounters) { for (Variable var : body.getVariables()) { boolean isParameter = var instanceof ParameterRef; individualRefCounters.put(var, new VariableReferenceCount(Collections.singleton(var), isParameter)); } for (Set<Variable> partition : variableUnions.getPartitions()) { boolean isParameter = false; for (Variable var : partition) { if (var instanceof ParameterRef) { isParameter = true; break; } } unifiedRefCounters.put(partition, new VariableReferenceCount(partition, isParameter)); } TreeIterator<EObject> it = body.eAllContents(); while (it.hasNext()) { EObject obj = it.next(); if (obj instanceof XExpression) { XExpression expression = (XExpression) obj; for (Variable var : CorePatternLanguageHelper.getReferencedPatternVariablesOfXExpression(expression, associations)) { individualRefCounters.get(var).incrementCounter(ReferenceType.READ_ONLY); unifiedRefCounters.get(variableUnions.getPartition(var)) .incrementCounter(ReferenceType.READ_ONLY); } it.prune(); } if (obj instanceof VariableReference) { final VariableReference ref = (VariableReference) obj; final Variable var = ref.getVariable(); final ReferenceType referenceClass = classifyReference(ref); individualRefCounters.get(var).incrementCounter(referenceClass); unifiedRefCounters.get(variableUnions.getPartition(var)).incrementCounter(referenceClass); } } } private UnionFind<Variable> calculateEqualVariables(PatternBody body) { UnionFind<Variable> unions = new UnionFind<Variable>(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.union(((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 instanceof FunctionEvaluationValue)) { parent = parent.eContainer(); } if (parent instanceof CheckConstraint) { return ReferenceType.READ_ONLY; } else if (parent instanceof FunctionEvaluationValue) { // this should not be a variableReference, so probably // this will not happen return ReferenceType.READ_ONLY; } else if (parent instanceof CompareConstraint) { CompareConstraint constraint = (CompareConstraint) parent; if (constraint.getFeature() == CompareFeature.EQUALITY) { final boolean leftIsVariable = constraint.getLeftOperand() instanceof VariableValue; final boolean rightIsVariable = constraint.getRightOperand() instanceof VariableValue; if (leftIsVariable && rightIsVariable) { // A==A equivalence between unified variables... // should be ignored in reference counting, except that it spoils quantification return ReferenceType.READ_ONLY; } else if (leftIsVariable && !rightIsVariable) { if (ref.equals(((VariableValue) constraint.getLeftOperand()).getValue())) { // this should always be // true return ReferenceType.POSITIVE; } else reportStrangeVariableRef(ref, constraint); } else if (rightIsVariable && !leftIsVariable) { if (ref.equals(((VariableValue) constraint.getRightOperand()).getValue())) { // this should always // be true return ReferenceType.POSITIVE; } else reportStrangeVariableRef(ref, constraint); } else reportStrangeVariableRef(ref, constraint); } else if (constraint.getFeature() == CompareFeature.INEQUALITY) { return ReferenceType.READ_ONLY; } else reportStrangeVariableRef(ref, constraint); } 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; } private void reportStrangeVariableRef(VariableReference ref, CompareConstraint constraint) { throw new IllegalStateException( // this should never come up "Strange reference to variable " + ref.getVar() + " in " + constraint.getClass().getName()); } /** * @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(CheckType.NORMAL) public void checkForImpureJavaCallsInCheckConstraints(CheckConstraint checkConstraint) { checkForImpureJavaCallsInternal(checkConstraint.getExpression(), PatternLanguagePackage.Literals.CHECK_CONSTRAINT__EXPRESSION); } @Check(CheckType.NORMAL) public void checkForImpureJavaCallsInEvalExpressions(FunctionEvaluationValue eval) { checkForImpureJavaCallsInternal(eval.getExpression(), PatternLanguagePackage.Literals.FUNCTION_EVALUATION_VALUE__EXPRESSION); } private void checkForImpureJavaCallsInternal(XExpression xExpression, EStructuralFeature feature) { Set<String> elementsWithWarnings = new HashSet<String>(); if (xExpression != null) { TreeIterator<EObject> eAllContents = xExpression.eAllContents(); while (eAllContents.hasNext()) { EObject nextEObject = eAllContents.next(); if (nextEObject instanceof XMemberFeatureCall) { XMemberFeatureCall xFeatureCall = (XMemberFeatureCall) nextEObject; JvmIdentifiableElement jvmIdentifiableElement = xFeatureCall.getFeature(); if (jvmIdentifiableElement instanceof JvmOperation) { JvmOperation jvmOperation = (JvmOperation) jvmIdentifiableElement; if (!PurityChecker.isPure(jvmOperation) && !jvmOperation.eIsProxy()) { elementsWithWarnings.add(jvmOperation.getQualifiedName()); } } } } } if (!elementsWithWarnings.isEmpty()) { warning("There is at least one potentially problematic java call in the check()/eval() expression. Custom java calls " + "are considered unsafe in VIATRA Query unless they are annotated with @" + Pure.class.getSimpleName() + " or registered with the " + PureWhitelistExtensionLoader.EXTENSION_ID + " extension point. The possible erroneous calls are the following: " + elementsWithWarnings + ".", xExpression.eContainer(), feature, IssueCodes.CHECK_WITH_IMPURE_JAVA_CALLS); } } @Check(CheckType.NORMAL) public void checkNegativeCallParameters(PatternCompositionConstraint constraint) { Predicate<ValueReference> isSingleUseVariable = new Predicate<ValueReference>() { @Override public boolean apply(ValueReference input) { if (input instanceof VariableValue) { VariableValue variableValue = (VariableValue) input; return variableValue.getValue().getVar().startsWith("_"); } else { return false; } } }; if (constraint.isNegative() && Iterables.all(constraint.getCall().getParameters(), isSingleUseVariable)) { warning("This negative pattern call is a global constraint: " + "it expresses that there are no matches of the called pattern at all. " + "Make sure this is intentional!", PatternLanguagePackage.Literals.PATTERN_COMPOSITION_CONSTRAINT__CALL, IssueCodes.NEGATIVE_PATTERN_CALL_WITH_ONLY_SINGLE_USE_VARIABLES); } } @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); } }