blob: 00cc26b0589cb6a907f1d783d0992a3d9c62bb19 [file] [log] [blame]
package org.checkerframework.framework.util.dependenttypes;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.expression.FormalParameter;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.expression.JavaExpressionConverter;
import org.checkerframework.dataflow.expression.LocalVariable;
import org.checkerframework.dataflow.expression.Unknown;
import org.checkerframework.framework.source.SourceChecker;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.AnnotatedTypeParameterBounds;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner;
import org.checkerframework.framework.type.visitor.DoubleAnnotatedTypeScanner;
import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner;
import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
import org.checkerframework.framework.util.StringToJavaExpression;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.plumelib.util.CollectionsPlume;
/**
* A class that helps checkers use qualifiers that are represented by annotations with Java
* expression strings. This class performs the following main functions:
*
* <ol>
* <li>Converts the expression strings in an {@link AnnotationMirror} {@code am}, by creating a
* new annotation whose Java expression elements are the result of the conversion. See {@link
* #convertAnnotationMirror(StringToJavaExpression, AnnotationMirror)}. Subclasses can
* specialize this process by overriding methods in this class. Methods in this class always
* standardize Java expressions and may additionally viewpoint-adapt or delocalize
* expressions. Below is an explanation of each kind of conversion.
* <ul>
* <li>Standardization: the expressions in the annotations are converted such that two
* expression strings that are equivalent are made to be equal. For example, an instance
* field f may appear in an expression string as "f" or "this.f"; this class
* standardizes both strings to "this.f". All dependent type annotations must be
* standardized so that the implementation of {@link
* org.checkerframework.framework.type.QualifierHierarchy#isSubtype(AnnotationMirror,
* AnnotationMirror)} can assume that two expressions are equivalent if their string
* representations are {@code equals()}.
* <li>Viewpoint-adaption: converts an expression to some use site. For example, in method
* bodies, formal parameter references such as "#2" are converted to the name of the
* formal parameter. Another example, is at method call site, "this" is converted to the
* receiver of the method invocation.
* <li>Delocalization: removes all expressions with references to local variables that are
* not parameters and changes parameters to the "#1" syntax.
* </ul>
* <li>If any of the conversions above results in an invalid expression, this class changes
* invalid expression strings to an error string that includes the reason why the expression
* is invalid. For example, {@code @KeyFor("m")} would be changed to {@code @KeyFor("[error
* for expression: m error: m: identifier not found]")} if m is not a valid identifier. This
* allows subtyping checks to assume that if two strings are equal and not errors, they
* reference the same valid Java expression.
* <li>Checks annotated types for error strings that have been added by this class and issues an
* error if any are found.
* </ol>
*
* <p>Steps 2 and 3 are separated so that an error is issued only once per invalid expression string
* rather than every time the expression string is parsed. (The expression string is parsed multiple
* times because annotated types are created multiple times.)
*/
public class DependentTypesHelper {
/** AnnotatedTypeFactory */
protected final AnnotatedTypeFactory factory;
/**
* Maps from an annotation name, the fully-qualified name of its class, to its elements that are
* Java expressions.
*/
private final Map<String, List<ExecutableElement>> annoToElements;
/** This scans an annotated type and returns a list of {@link DependentTypesError}. */
private final ExpressionErrorCollector expressionErrorCollector = new ExpressionErrorCollector();
/**
* A scanner that applies a function to each {@link AnnotationMirror} and replaces it in the given
* {@code AnnotatedTypeMirror}. (This side-effects the {@code AnnotatedTypeMirror}.)
*/
private final AnnotatedTypeReplacer annotatedTypeReplacer = new AnnotatedTypeReplacer();
/**
* Copies annotations that might have been viewpoint adapted from the visited type (the first
* formal parameter of {@code ViewpointAdaptedCopier#visit}) to the second formal parameter.
*/
private final ViewpointAdaptedCopier viewpointAdaptedCopier = new ViewpointAdaptedCopier();
/** The type mirror for java.lang.Object. */
protected final TypeMirror objectTM;
/**
* Creates a {@code DependentTypesHelper}.
*
* @param factory annotated type factory
*/
public DependentTypesHelper(AnnotatedTypeFactory factory) {
this.factory = factory;
this.annoToElements = new HashMap<>();
for (Class<? extends Annotation> expressionAnno : factory.getSupportedTypeQualifiers()) {
List<ExecutableElement> elementList =
getExpressionElements(expressionAnno, factory.getProcessingEnv());
if (!elementList.isEmpty()) {
annoToElements.put(expressionAnno.getCanonicalName(), elementList);
}
}
this.objectTM =
TypesUtils.typeFromClass(Object.class, factory.types, factory.getElementUtils());
}
/**
* Returns true if any qualifier in the type system is a dependent type annotation.
*
* @return true if any qualifier in the type system is a dependent type annotation
*/
public boolean hasDependentAnnotations() {
return !annoToElements.isEmpty();
}
/**
* Returns a list of the elements in the annotation class that should be interpreted as Java
* expressions, namely those annotated with {@code @}{@link JavaExpression}.
*
* @param clazz annotation class
* @param env processing environment for getting the ExecutableElement
* @return a list of the elements in the annotation class that should be interpreted as Java
* expressions
*/
private static List<ExecutableElement> getExpressionElements(
Class<? extends Annotation> clazz, ProcessingEnvironment env) {
Method[] methods = clazz.getMethods();
if (methods == null) {
return Collections.emptyList();
}
List<ExecutableElement> elements = new ArrayList<>();
for (Method method : methods) {
org.checkerframework.framework.qual.JavaExpression javaExpressionAnno =
method.getAnnotation(org.checkerframework.framework.qual.JavaExpression.class);
if (javaExpressionAnno != null) {
elements.add(TreeUtils.getMethod(clazz, method.getName(), method.getParameterCount(), env));
}
}
return elements;
}
/**
* Returns the elements of the annotation that are Java expressions.
*
* @param am AnnotationMirror
* @return the elements of the annotation that are Java expressions
*/
private List<ExecutableElement> getListOfExpressionElements(AnnotationMirror am) {
return annoToElements.getOrDefault(AnnotationUtils.annotationName(am), Collections.emptyList());
}
/**
* Creates a TreeAnnotator that viewpoint-adapts dependent type annotations.
*
* @return a new TreeAnnotator that viewpoint-adapts dependent type annotations
*/
public TreeAnnotator createDependentTypesTreeAnnotator() {
assert hasDependentAnnotations();
return new DependentTypesTreeAnnotator(factory, this);
}
///
/// Methods that convert annotations
///
/** If true, log information about where lambdas are created. */
private static boolean debugStringToJavaExpression = false;
/**
* Viewpoint-adapts the dependent type annotations on the bounds of the type parameters of the
* declaration of {@code typeUse} to {@code typeUse}.
*
* @param bounds annotated types of the bounds of the type parameters; its elements are
* side-effected by this method (but the list itself is not side-effected)
* @param typeUse a use of a type with type parameter bounds {@code bounds}
*/
public void atParameterizedTypeUse(
List<AnnotatedTypeParameterBounds> bounds, TypeElement typeUse) {
if (!hasDependentAnnotations()) {
return;
}
StringToJavaExpression stringToJavaExpr =
stringExpr -> StringToJavaExpression.atTypeDecl(stringExpr, typeUse, factory.getChecker());
if (debugStringToJavaExpression) {
System.out.printf(
"atParameterizedTypeUse(%s, %s) created %s%n", bounds, typeUse, stringToJavaExpr);
}
for (AnnotatedTypeParameterBounds bound : bounds) {
convertAnnotatedTypeMirror(stringToJavaExpr, bound.getUpperBound());
convertAnnotatedTypeMirror(stringToJavaExpr, bound.getLowerBound());
}
}
/**
* Viewpoint-adapts the dependent type annotations in the methodType to the methodInvocationTree.
*
* <p>{@code methodType} has been viewpoint-adapted to the call site, except for any dependent
* type annotations. This method viewpoint-adapts the dependent type annotations.
*
* @param methodType type of the method invocation; is side-effected by this method
* @param methodInvocationTree use of the method
*/
public void atMethodInvocation(
AnnotatedExecutableType methodType, MethodInvocationTree methodInvocationTree) {
if (!hasDependentAnnotations()) {
return;
}
atInvocation(methodType, methodInvocationTree);
}
/**
* Viewpoint-adapts the dependent type annotations in the constructorType to the newClassTree.
*
* <p>{@code constructorType} has been viewpoint-adapted to the call site, except for any
* dependent type annotations. This method viewpoint-adapts the dependent type annotations.
*
* @param constructorType type of the constructor invocation; is side-effected by this method
* @param newClassTree invocation of the constructor
*/
public void atConstructorInvocation(
AnnotatedExecutableType constructorType, NewClassTree newClassTree) {
if (!hasDependentAnnotations()) {
return;
}
atInvocation(constructorType, newClassTree);
}
/**
* Viewpoint-adapts a method or constructor invocation.
*
* <p>{@code methodType} has been viewpoint-adapted to the call site, except for any dependent
* type annotations. (For example, type variables have been substituted and polymorphic qualifiers
* have been resolved.) This method viewpoint-adapts the dependent type annotations.
*
* @param methodType type of the method or constructor invocation; is side-effected by this method
* @param tree invocation of the method or constructor
*/
private void atInvocation(AnnotatedExecutableType methodType, ExpressionTree tree) {
assert hasDependentAnnotations();
Element methodElt = TreeUtils.elementFromUse(tree);
// Because methodType is the type post type variable substitution, it has annotations from
// both the method declaration and the type arguments at the use of the method. Annotations
// from type arguments must not be viewpoint-adapted to the call site. For example:
// Map<String, String> map = ...;
// List<@KeyFor("this.map") String> list = ...;
// list.get(0)
//
// methodType is @KeyFor("this.map") String get(int)
// "this.map" must not be viewpoint-adapted to the invocation because it is not from
// the method declaration, but added during type variable substitution.
//
// So this implementation gets the declared type of the method, declaredMethodType,
// viewpoint-adapts all dependent type annotations in declaredMethodType to the call site,
// and then copies the viewpoint-adapted annotations from methodType except for types that
// are replaced by type variable substitution. (Those annotations are viewpoint-adapted
// before type variable substitution.)
// The annotations on `declaredMethodType` will be copied to `methodType`.
AnnotatedExecutableType declaredMethodType =
(AnnotatedExecutableType) factory.getAnnotatedType(methodElt);
if (!hasDependentType(declaredMethodType)) {
return;
}
StringToJavaExpression stringToJavaExpr;
if (tree instanceof MethodInvocationTree) {
stringToJavaExpr =
stringExpr ->
StringToJavaExpression.atMethodInvocation(
stringExpr, (MethodInvocationTree) tree, factory.getChecker());
if (debugStringToJavaExpression) {
System.out.printf(
"atInvocation(%s, %s) 1 created %s%n",
methodType, TreeUtils.toStringTruncated(tree, 65), stringToJavaExpr);
}
} else if (tree instanceof NewClassTree) {
stringToJavaExpr =
stringExpr ->
StringToJavaExpression.atConstructorInvocation(
stringExpr, (NewClassTree) tree, factory.getChecker());
if (debugStringToJavaExpression) {
System.out.printf(
"atInvocation(%s, %s) 2 created %s%n",
methodType, TreeUtils.toStringTruncated(tree, 65), stringToJavaExpr);
}
} else {
throw new BugInCF("Unexpected tree: %s kind: %s", tree, tree.getKind());
}
convertAnnotatedTypeMirror(stringToJavaExpr, declaredMethodType);
this.viewpointAdaptedCopier.visit(declaredMethodType, methodType);
}
/**
* Viewpoint-adapts the Java expressions in annotations written on a field declaration to the use
* at {@code fieldAccess}.
*
* @param type its type; is side-effected by this method
* @param fieldAccess a field access
*/
public void atFieldAccess(AnnotatedTypeMirror type, MemberSelectTree fieldAccess) {
if (!hasDependentType(type)) {
return;
}
StringToJavaExpression stringToJavaExpr =
stringExpr ->
StringToJavaExpression.atFieldAccess(stringExpr, fieldAccess, factory.getChecker());
if (debugStringToJavaExpression) {
System.out.printf(
"atFieldAccess(%s, %s) created %s%n",
type, TreeUtils.toStringTruncated(fieldAccess, 65), stringToJavaExpr);
}
convertAnnotatedTypeMirror(stringToJavaExpr, type);
}
/**
* Viewpoint-adapts the Java expressions in annotations written on the signature of the method
* declaration (for example, a return type) to the body of the method. This means the parameter
* syntax, e.g. "#2", is converted to the names of the parameter.
*
* @param atm a type at the method signature; is side-effected by this method
* @param methodDeclTree a method declaration
*/
public void atMethodBody(AnnotatedTypeMirror atm, MethodTree methodDeclTree) {
if (!hasDependentType(atm)) {
return;
}
StringToJavaExpression stringToJavaExpr =
stringExpr ->
StringToJavaExpression.atMethodBody(stringExpr, methodDeclTree, factory.getChecker());
if (debugStringToJavaExpression) {
System.out.printf(
"atMethodBody(%s, %s) 1 created %s%n",
atm, TreeUtils.toStringTruncated(methodDeclTree, 65), stringToJavaExpr);
}
convertAnnotatedTypeMirror(stringToJavaExpr, atm);
}
/**
* Standardizes the Java expressions in annotations to a type declaration.
*
* @param type the type of the type declaration; is side-effected by this method
* @param typeElt the element of the type declaration
*/
public void atTypeDecl(AnnotatedTypeMirror type, TypeElement typeElt) {
if (!hasDependentType(type)) {
return;
}
StringToJavaExpression stringToJavaExpr =
stringExpr -> StringToJavaExpression.atTypeDecl(stringExpr, typeElt, factory.getChecker());
if (debugStringToJavaExpression) {
System.out.printf("atTypeDecl(%s, %s) created %s%n", type, typeElt, stringToJavaExpr);
}
convertAnnotatedTypeMirror(stringToJavaExpr, type);
}
/** A set containing {@link Tree.Kind#METHOD} and {@link Tree.Kind#LAMBDA_EXPRESSION}. */
private static final Set<Tree.Kind> METHOD_OR_LAMBDA =
EnumSet.of(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION);
/**
* Standardize the Java expressions in annotations in a variable declaration. Converts the
* parameter syntax, e.g "#1", to the parameter name.
*
* @param type the type of the variable declaration; is side-effected by this method
* @param declarationTree the variable declaration
* @param variableElt the element of the variable declaration
*/
public void atVariableDeclaration(
AnnotatedTypeMirror type, Tree declarationTree, VariableElement variableElt) {
if (!hasDependentType(type)) {
return;
}
TreePath pathToVariableDecl = factory.getPath(declarationTree);
if (pathToVariableDecl == null) {
// If this is a synthetic created by dataflow, the path will be null.
return;
}
switch (variableElt.getKind()) {
case PARAMETER:
TreePath pathTillEnclTree =
TreePathUtil.pathTillOfKind(pathToVariableDecl, METHOD_OR_LAMBDA);
if (pathTillEnclTree == null) {
throw new BugInCF("no enclosing method or lambda found for " + variableElt);
}
Tree enclTree = pathTillEnclTree.getLeaf();
if (enclTree.getKind() == Kind.METHOD) {
MethodTree methodDeclTree = (MethodTree) enclTree;
StringToJavaExpression stringToJavaExpr =
stringExpr ->
StringToJavaExpression.atMethodBody(
stringExpr, methodDeclTree, factory.getChecker());
if (debugStringToJavaExpression) {
System.out.printf(
"atVariableDeclaration(%s, %s, %s) 1 created %s%n",
type,
TreeUtils.toStringTruncated(declarationTree, 65),
variableElt,
stringToJavaExpr);
}
convertAnnotatedTypeMirror(stringToJavaExpr, type);
} else {
// Lambdas can use local variables defined in the enclosing method, so allow
// identifiers to be locals in scope at the location of the lambda.
StringToJavaExpression stringToJavaExpr =
stringExpr ->
StringToJavaExpression.atLambdaParameter(
stringExpr,
(LambdaExpressionTree) enclTree,
pathToVariableDecl.getParentPath(),
factory.getChecker());
if (debugStringToJavaExpression) {
System.out.printf(
"atVariableDeclaration(%s, %s, %s) 2 created %s%n",
type,
TreeUtils.toStringTruncated(declarationTree, 65),
variableElt,
stringToJavaExpr);
}
convertAnnotatedTypeMirror(stringToJavaExpr, type);
}
break;
case LOCAL_VARIABLE:
case RESOURCE_VARIABLE:
case EXCEPTION_PARAMETER:
StringToJavaExpression stringToJavaExprVar =
stringExpr ->
StringToJavaExpression.atPath(stringExpr, pathToVariableDecl, factory.getChecker());
if (debugStringToJavaExpression) {
System.out.printf(
"atVariableDeclaration(%s, %s, %s) 3 created %s%n",
type,
TreeUtils.toStringTruncated(declarationTree, 65),
variableElt,
stringToJavaExprVar);
}
convertAnnotatedTypeMirror(stringToJavaExprVar, type);
break;
case FIELD:
case ENUM_CONSTANT:
StringToJavaExpression stringToJavaExprField =
stringExpr ->
StringToJavaExpression.atFieldDecl(stringExpr, variableElt, factory.getChecker());
if (debugStringToJavaExpression) {
System.out.printf(
"atVariableDeclaration(%s, %s, %s) 4 created %s%n",
type,
TreeUtils.toStringTruncated(declarationTree, 65),
variableElt,
stringToJavaExprField);
}
convertAnnotatedTypeMirror(stringToJavaExprField, type);
break;
default:
throw new BugInCF(
"unexpected element kind " + variableElt.getKind() + " for " + variableElt);
}
}
/**
* Standardize the Java expressions in annotations in written in the {@code expressionTree}. Also,
* converts the parameter syntax, e.g. "#1", to the parameter name.
*
* <p>{@code expressionTree} must be an expressions which can contain explicitly written
* annotations, namely a {@link NewClassTree}, {@link com.sun.source.tree.NewArrayTree}, or {@link
* com.sun.source.tree.TypeCastTree}. For example, this method standardizes the {@code KeyFor}
* annotation in {@code (@KeyFor("map") String) key }.
*
* @param annotatedType its type; is side-effected by this method
* @param expressionTree a {@link NewClassTree}, {@link com.sun.source.tree.NewArrayTree}, or
* {@link com.sun.source.tree.TypeCastTree}
*/
public void atExpression(AnnotatedTypeMirror annotatedType, ExpressionTree expressionTree) {
if (!hasDependentType(annotatedType)) {
return;
}
TreePath path = factory.getPath(expressionTree);
if (path == null) {
return;
}
StringToJavaExpression stringToJavaExpr =
stringExpr -> StringToJavaExpression.atPath(stringExpr, path, factory.getChecker());
if (debugStringToJavaExpression) {
System.out.printf(
"atExpression(%s, %s) created %s%n",
annotatedType, TreeUtils.toStringTruncated(expressionTree, 65), stringToJavaExpr);
}
convertAnnotatedTypeMirror(stringToJavaExpr, annotatedType);
}
/**
* Standardize the Java expressions in annotations in a type. Converts the parameter syntax, e.g.
* "#2", to the parameter name.
*
* @param type the type to standardize; is side-effected by this method
* @param elt the element whose type is {@code type}
*/
public void atLocalVariable(AnnotatedTypeMirror type, Element elt) {
if (!hasDependentType(type)) {
return;
}
switch (elt.getKind()) {
case PARAMETER:
case LOCAL_VARIABLE:
case RESOURCE_VARIABLE:
case EXCEPTION_PARAMETER:
Tree declarationTree = factory.declarationFromElement(elt);
if (declarationTree == null) {
if (elt.getKind() == ElementKind.PARAMETER) {
// The tree might be null when
// org.checkerframework.framework.flow.CFAbstractTransfer.getValueFromFactory() gets the
// assignment context for a pseudo assignment of an argument to a method parameter.
return;
}
throw new BugInCF(this.getClass() + ": tree not found");
} else if (TreeUtils.typeOf(declarationTree) == null) {
// org.checkerframework.framework.flow.CFAbstractTransfer.getValueFromFactory() gets the
// assignment context for a pseudo assignment of an argument to a method parameter.
return;
}
atVariableDeclaration(type, declarationTree, (VariableElement) elt);
return;
default:
// It's not a local variable (it might be METHOD, CONSTRUCTOR, CLASS, or INTERFACE,
// for example), so there is nothing to do.
break;
}
}
/** Thrown when a non-parameter local variable is found. */
@SuppressWarnings("serial")
private static class FoundLocalException extends RuntimeException {}
/**
* Viewpoint-adapt all dependent type annotations to the method declaration, {@code
* methodDeclTree}. This method changes occurrences of formal parameter names to the "#2" syntax,
* and it removes expressions that contain other local variables.
*
* <p>If a Java expression in {@code atm} references local variables (other than formal
* parameters), the expression is removed from the annotation. This could result in dependent type
* annotations with empty lists of expressions. If this is a problem, a subclass can override
* {@link #buildAnnotation(AnnotationMirror, Map)} to do something besides creating an annotation
* with a empty list.
*
* @param atm type to viewpoint-adapt; is side-effected by this method
* @param methodDeclTree the method declaration to which the annotations are viewpoint-adapted
*/
public void delocalize(AnnotatedTypeMirror atm, MethodTree methodDeclTree) {
if (!hasDependentType(atm)) {
return;
}
TreePath pathToMethodDecl = factory.getPath(methodDeclTree);
ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodDeclTree);
List<FormalParameter> parameters = JavaExpression.getFormalParameters(methodElement);
List<JavaExpression> paramsAsLocals =
JavaExpression.getParametersAsLocalVariables(methodElement);
StringToJavaExpression stringToJavaExpr =
expression -> {
JavaExpression javaExpr;
try {
javaExpr =
StringToJavaExpression.atPath(expression, pathToMethodDecl, factory.getChecker());
} catch (JavaExpressionParseException ex) {
return null;
}
JavaExpressionConverter jec =
new JavaExpressionConverter() {
@Override
protected JavaExpression visitLocalVariable(
LocalVariable localVarExpr, Void unused) {
int index = paramsAsLocals.indexOf(localVarExpr);
if (index == -1) {
throw new FoundLocalException();
}
return parameters.get(index);
}
};
try {
return jec.convert(javaExpr);
} catch (FoundLocalException ex) {
return null;
}
};
if (debugStringToJavaExpression) {
System.out.printf(
"delocalize(%s, %s) created %s%n",
atm, TreeUtils.toStringTruncated(methodDeclTree, 65), stringToJavaExpr);
}
convertAnnotatedTypeMirror(stringToJavaExpr, atm);
}
/**
* Calls {@link #convertAnnotationMirror(StringToJavaExpression, AnnotationMirror)} on each
* annotation mirror on type with {@code stringToJavaExpr}. And replaces the annotation with the
* one created by {@code convertAnnotationMirror}, if it's not null. If it is null, the original
* annotation is used. See {@link #convertAnnotationMirror(StringToJavaExpression,
* AnnotationMirror)} for more details.
*
* @param stringToJavaExpr function to convert a string to a {@link JavaExpression}
* @param type the type that is side-effected by this method
*/
protected void convertAnnotatedTypeMirror(
StringToJavaExpression stringToJavaExpr, AnnotatedTypeMirror type) {
this.annotatedTypeReplacer.visit(type, anno -> convertAnnotationMirror(stringToJavaExpr, anno));
}
/**
* Given an annotation {@code anno}, this method builds a new annotation with the Java expressions
* transformed according to {@code stringToJavaExpr}. If {@code anno} is not a dependent type
* annotation, {@code null} is returned.
*
* <p>If {@code stringToJavaExpr} returns {@code null}, then that expression is removed from the
* returned annotation.
*
* <p>Instead of overriding this method, subclasses can override the following methods to change
* the behavior of this class:
*
* <ul>
* <li>{@link #shouldPassThroughExpression(String)}: to control which expressions are skipped.
* If this method returns true, then the expression string is not parsed and is included in
* the new annotation unchanged.
* <li>{@link #transform(JavaExpression)}: make changes to the JavaExpression produced by {@code
* stringToJavaExpr}.
* <li>{@link #buildAnnotation(AnnotationMirror, Map)}: to change the annotation returned by
* this method.
* </ul>
*
* @param stringToJavaExpr function that converts strings to {@code JavaExpression}s
* @param anno annotation mirror
* @return an annotation created by applying {@code stringToJavaExpr} to all expression strings in
* {@code anno}, or null if there would be no effect
*/
public @Nullable AnnotationMirror convertAnnotationMirror(
StringToJavaExpression stringToJavaExpr, AnnotationMirror anno) {
if (!isExpressionAnno(anno)) {
return null;
}
Map<ExecutableElement, List<JavaExpression>> newElements = new HashMap<>();
for (ExecutableElement element : getListOfExpressionElements(anno)) {
List<String> expressionStrings =
AnnotationUtils.getElementValueArray(
anno, element, String.class, Collections.emptyList());
List<JavaExpression> javaExprs = new ArrayList<>(expressionStrings.size());
newElements.put(element, javaExprs);
for (String expression : expressionStrings) {
JavaExpression result;
if (shouldPassThroughExpression(expression)) {
result = new PassThroughExpression(objectTM, expression);
} else {
try {
result = stringToJavaExpr.toJavaExpression(expression);
} catch (JavaExpressionParseException e) {
result = createError(expression, e);
}
}
if (result != null) {
result = transform(result);
javaExprs.add(result);
}
}
}
return buildAnnotation(anno, newElements);
}
/**
* This method is for subclasses to override to change JavaExpressions in some way before they are
* inserted into new annotations. This method is called after parsing and viewpoint-adaptation
* have occurred. {@code javaExpr} may be a {@link PassThroughExpression}.
*
* <p>If {@code null} is returned then the expression is not added to the new annotation.
*
* <p>The default implementation returns the argument, but subclasses may override it.
*
* @param javaExpr a JavaExpression
* @return a transformed JavaExpression or {@code null} if no transformation exists
*/
protected @Nullable JavaExpression transform(JavaExpression javaExpr) {
return javaExpr;
}
/**
* Whether or not {@code expression} should be passed to the new annotation unchanged. If this
* method returns true, the {@code expression} is not parsed.
*
* <p>The default implementation returns true if the {@code expression} is an expression error
* according to {@link DependentTypesError#isExpressionError(String)}. Subclasses may override
* this method to add additional logic.
*
* @param expression an expression string in a dependent types annotation
* @return whether or not {@code expression} should be passed through unchanged to the new
* annotation
*/
protected boolean shouldPassThroughExpression(String expression) {
return DependentTypesError.isExpressionError(expression);
}
/**
* Create a new annotation of the same type as {@code originalAnno} using the provided {@code
* elementMap}.
*
* @param originalAnno the annotation passed to {@link
* #convertAnnotationMirror(StringToJavaExpression, AnnotationMirror)} (this method is a
* helper method for {@link #convertAnnotationMirror(StringToJavaExpression,
* AnnotationMirror)})
* @param elementMap a mapping from element of {@code originalAnno} to {@code JavaExpression}s
* @return an annotation created from {@code elementMap}
*/
protected AnnotationMirror buildAnnotation(
AnnotationMirror originalAnno, Map<ExecutableElement, List<JavaExpression>> elementMap) {
AnnotationBuilder builder =
new AnnotationBuilder(
factory.getProcessingEnv(), AnnotationUtils.annotationName(originalAnno));
builder.copyElementValuesFromAnnotation(originalAnno, elementMap.keySet());
for (Map.Entry<ExecutableElement, List<JavaExpression>> entry : elementMap.entrySet()) {
List<String> strings = CollectionsPlume.mapList(JavaExpression::toString, entry.getValue());
builder.setValue(entry.getKey(), strings);
}
return builder.build();
}
/**
* A {@link JavaExpression} that does not represent a {@link JavaExpression}, but rather allows an
* expression string to be converted to a JavaExpression and then to a string without parsing.
*/
static class PassThroughExpression extends Unknown {
/** Some string. */
public final String string;
/**
* Creates a PassThroughExpression.
*
* @param type some type
* @param string the string to convert to a JavaExpression
*/
public PassThroughExpression(TypeMirror type, String string) {
super(type);
this.string = string;
}
@Override
public String toString() {
return string;
}
}
/**
* Creates a {@link JavaExpression} representing the exception thrown when parsing {@code
* expression}.
*
* @param expression an expression that caused {@code e} when parsed
* @param e the exception thrown when parsing {@code expression}
* @return a java expression
*/
protected PassThroughExpression createError(String expression, JavaExpressionParseException e) {
return new PassThroughExpression(objectTM, new DependentTypesError(expression, e).toString());
}
/**
* Creates a {@link JavaExpression} representing the error caused when parsing {@code expression}
*
* @param expression an expression that caused {@code error} when parsed
* @param error the error message caused by {@code expression}
* @return a java expression
*/
protected PassThroughExpression createError(String expression, String error) {
return new PassThroughExpression(
objectTM, new DependentTypesError(expression, error).toString());
}
/**
* Applies the passed function to each annotation in the given {@link AnnotatedTypeMirror}. If the
* function returns a non-null annotation, then the original annotation is replaced with the
* result. If the function returns null, the original annotation is retained.
*/
private static class AnnotatedTypeReplacer
extends AnnotatedTypeScanner<Void, Function<AnnotationMirror, AnnotationMirror>> {
@Override
public Void visitTypeVariable(
AnnotatedTypeMirror.AnnotatedTypeVariable type,
Function<AnnotationMirror, AnnotationMirror> func) {
if (visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
visitedNodes.put(type, null);
// If the type variable has a primary annotation, then it is viewpoint-adapted before this
// method is called. The viewpoint-adapted primary annotation was already copied to the upper
// and lower bounds. These annotations cannot be viewpoint-adapted again, so remove them,
// viewpoint-adapt any other annotations in the bound, and then add them back.
Set<AnnotationMirror> primarys = type.getAnnotations();
type.getLowerBound().removeAnnotations(primarys);
Void r = scan(type.getLowerBound(), func);
type.getLowerBound().addAnnotations(primarys);
visitedNodes.put(type, r);
type.getUpperBound().removeAnnotations(primarys);
r = scanAndReduce(type.getUpperBound(), func, r);
type.getUpperBound().addAnnotations(primarys);
visitedNodes.put(type, r);
return r;
}
@Override
protected Void scan(
AnnotatedTypeMirror type, Function<AnnotationMirror, AnnotationMirror> func) {
for (AnnotationMirror anno : AnnotationUtils.createAnnotationSet(type.getAnnotations())) {
AnnotationMirror newAnno = func.apply(anno);
if (newAnno != null) {
// This code must remove and then add, rather than call `replace`, because a
// type may have multiple annotations with the same class, but different
// elements. (This is a bug; see
// https://github.com/typetools/checker-framework/issues/4451.)
// AnnotatedTypeMirror#replace only removes one annotation that is in the same
// hierarchy as the passed argument.
type.removeAnnotation(anno);
type.addAnnotation(newAnno);
}
}
return super.scan(type, func);
}
}
///
/// Methods that check and report errors
///
/**
* Reports an expression.unparsable error for each Java expression in the given type that is an
* expression error string.
*
* @param atm annotated type to check for expression errors
* @param errorTree the tree at which to report any found errors
*/
public void checkTypeForErrorExpressions(AnnotatedTypeMirror atm, Tree errorTree) {
if (!hasDependentAnnotations()) {
return;
}
List<DependentTypesError> errors = expressionErrorCollector.visit(atm);
if (errors.isEmpty()) {
return;
}
if (errorTree.getKind() == Kind.VARIABLE) {
ModifiersTree modifiers = ((VariableTree) errorTree).getModifiers();
errorTree = ((VariableTree) errorTree).getType();
for (AnnotationTree annoTree : modifiers.getAnnotations()) {
String annoString = annoTree.toString();
for (String annoName : annoToElements.keySet()) {
// TODO: Simple string containment seems too simplistic. At least check for a word
// boundary.
if (annoString.contains(annoName)) {
errorTree = annoTree;
break;
}
}
}
}
reportErrors(errorTree, errors);
}
/**
* Report the given errors as "expression.unparsable".
*
* @param errorTree where to report the errors
* @param errors the errors to report
*/
protected void reportErrors(Tree errorTree, List<DependentTypesError> errors) {
SourceChecker checker = factory.getChecker();
for (DependentTypesError dte : errors) {
checker.reportError(errorTree, "expression.unparsable", dte.format());
}
}
/**
* Returns a list of {@link DependentTypesError}s for all the Java expression elements of the
* annotation that are an error string as specified by DependentTypesError#isExpressionError.
*
* @param am an annotation
* @return a list of {@link DependentTypesError}s for the error strings in the given annotation
*/
private List<DependentTypesError> errorElements(AnnotationMirror am) {
assert hasDependentAnnotations();
List<DependentTypesError> errors = new ArrayList<>();
for (ExecutableElement element : getListOfExpressionElements(am)) {
// It's always an array, not a single value, because @JavaExpression may only be written
// on an annotation element of type String[].
List<String> value =
AnnotationUtils.getElementValueArray(am, element, String.class, Collections.emptyList());
for (String v : value) {
if (DependentTypesError.isExpressionError(v)) {
errors.add(DependentTypesError.unparse(v));
}
}
}
return errors;
}
/**
* Reports a flowexpr.parse.error error for each Java expression in the given annotation that is
* an expression error string.
*
* @param annotation annotation to check
* @param errorTree location at which to issue errors
*/
public void checkAnnotationForErrorExpressions(AnnotationMirror annotation, Tree errorTree) {
if (!hasDependentAnnotations()) {
return;
}
List<DependentTypesError> errors = errorElements(annotation);
if (errors.isEmpty()) {
return;
}
SourceChecker checker = factory.getChecker();
for (DependentTypesError error : errors) {
checker.reportError(errorTree, "flowexpr.parse.error", error);
}
}
/**
* Reports an expression.unparsable error for each Java expression in the given class declaration
* AnnotatedTypeMirror that is an expression error string. Note that this reports errors in the
* class declaration itself, not the body or extends/implements clauses.
*
* @param classTree class to check
* @param type annotated type of the class
*/
public void checkClassForErrorExpressions(ClassTree classTree, AnnotatedDeclaredType type) {
if (!hasDependentAnnotations()) {
return;
}
// TODO: check that invalid annotations in type variable bounds are properly
// formatted. They are part of the type, but the output isn't nicely formatted.
checkTypeForErrorExpressions(type, classTree);
}
/**
* Reports an expression.unparsable error for each Java expression in the method declaration
* AnnotatedTypeMirror that is an expression error string.
*
* @param methodDeclTree method to check
* @param type annotated type of the method
*/
public void checkMethodForErrorExpressions(
MethodTree methodDeclTree, AnnotatedExecutableType type) {
if (!hasDependentAnnotations()) {
return;
}
// Parameters and receivers are checked by visitVariable
// So only type parameters and return type need to be checked here.
checkTypeVariablesForErrorExpressions(methodDeclTree, type);
// Check return type
if (type.getReturnType().getKind() != TypeKind.VOID) {
AnnotatedTypeMirror returnType = factory.getMethodReturnType(methodDeclTree);
Tree treeForError =
TreeUtils.isConstructor(methodDeclTree) ? methodDeclTree : methodDeclTree.getReturnType();
checkTypeForErrorExpressions(returnType, treeForError);
}
}
/**
* Reports an expression.unparsable error for each Java expression in the given type variables
* that is an expression error string.
*
* @param node a method declaration
* @param methodType annotated type of the method
*/
private void checkTypeVariablesForErrorExpressions(
MethodTree node, AnnotatedExecutableType methodType) {
for (int i = 0; i < methodType.getTypeVariables().size(); i++) {
AnnotatedTypeMirror atm = methodType.getTypeVariables().get(i);
StringToJavaExpression stringToJavaExpr =
stringExpr -> StringToJavaExpression.atMethodBody(stringExpr, node, factory.getChecker());
if (debugStringToJavaExpression) {
System.out.printf(
"checkTypeVariablesForErrorExpressions(%s, %s) created %s%n",
node, methodType, stringToJavaExpr);
}
convertAnnotatedTypeMirror(stringToJavaExpr, atm);
checkTypeForErrorExpressions(atm, node.getTypeParameters().get(i));
}
}
/**
* Returns true if {@code am} is an expression annotation, that is, an annotation whose element is
* a Java expression.
*
* @param am an annotation
* @return true if {@code am} is an expression annotation
*/
private boolean isExpressionAnno(AnnotationMirror am) {
if (!hasDependentAnnotations()) {
return false;
}
return annoToElements.containsKey(AnnotationUtils.annotationName(am));
}
/**
* Checks all dependent type annotations in the given annotated type to see if the expression
* string is an error string as specified by DependentTypesError#isExpressionError. If the
* annotated type has any errors, then a non-empty list of {@link DependentTypesError} is
* returned.
*/
private class ExpressionErrorCollector
extends SimpleAnnotatedTypeScanner<List<DependentTypesError>, Void> {
/** Create ExpressionErrorCollector. */
private ExpressionErrorCollector() {
super(
(AnnotatedTypeMirror type, Void aVoid) -> {
List<DependentTypesError> errors = new ArrayList<>();
for (AnnotationMirror am : type.getAnnotations()) {
if (isExpressionAnno(am)) {
errors.addAll(errorElements(am));
}
}
return errors;
},
DependentTypesHelper::concatenate,
Collections.emptyList());
}
}
/**
* Appends list2 to list1 in a new list. If either list is empty, returns the other. Thus, the
* result may be aliased to one of the arguments and the client should only read, not write into,
* the result.
*
* @param list1 a list
* @param list2 a list
* @return the lists, concatenated
*/
private static List<DependentTypesError> concatenate(
List<DependentTypesError> list1, List<DependentTypesError> list2) {
if (list1.isEmpty()) {
return list2;
} else if (list2.isEmpty()) {
return list1;
}
List<DependentTypesError> newList = new ArrayList<>(list1.size() + list2.size());
newList.addAll(list1);
newList.addAll(list2);
return newList;
}
/**
* The underlying type of the second parameter is the result of applying type variable
* substitution to the visited type (the first parameter). This class copies annotations from the
* visited type to the second formal parameter except for annotations on types that have been
* substituted.
*/
private class ViewpointAdaptedCopier extends DoubleAnnotatedTypeScanner<Void> {
@Override
protected Void scan(AnnotatedTypeMirror from, AnnotatedTypeMirror to) {
if (from == null || to == null) {
return null;
}
Set<AnnotationMirror> replacements = AnnotationUtils.createAnnotationSet();
for (String vpa : annoToElements.keySet()) {
AnnotationMirror anno = from.getAnnotation(vpa);
if (anno != null) {
// Only replace annotations that might have been changed.
replacements.add(anno);
}
}
to.replaceAnnotations(replacements);
if (from.getKind() != to.getKind()) {
// If the underlying types don't match, then this from has been substituted for a
// from variable, so don't recur. The primary annotation was copied because
// the from variable might have had a primary annotation at a use.
// For example:
// <T> void method(@KeyFor("a") T t) {...}
// void use(@KeyFor("b") String s) {
// method(s); // the from of the parameter should be @KeyFor("a") String
// }
return null;
}
return super.scan(from, to);
}
@Override
protected Void defaultAction(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) {
if (type1 == null || type2 == null) {
return null;
}
if (type1.getKind() != type2.getKind()) {
throw new BugInCF("Should be the same. type: %s p: %s ", type1, type2);
}
return null;
}
}
/**
* Returns true if {@code atm} has any dependent type annotations. If an annotated type does not
* have a dependent type annotation, then no standardization or viewpoint adaption is performed.
* (This check avoids calling time-intensive methods unless required.)
*
* @param atm a type
* @return true if {@code atm} has any dependent type annotations
*/
private boolean hasDependentType(AnnotatedTypeMirror atm) {
if (atm == null) {
return false;
}
// This is a test about the type system.
if (!hasDependentAnnotations()) {
return false;
}
// This is a test about this specific type.
return hasDependentTypeScanner.visit(atm);
}
/** Returns true if the passed AnnotatedTypeMirror has any dependent type annotations. */
private final AnnotatedTypeScanner<Boolean, Void> hasDependentTypeScanner =
new SimpleAnnotatedTypeScanner<>(
(type, __) -> {
for (AnnotationMirror annotationMirror : type.getAnnotations()) {
if (isExpressionAnno(annotationMirror)) {
return true;
}
}
return false;
},
Boolean::logicalOr,
false);
}