org.checkerframework.framework.type.AnnotatedTypeFactory Maven / Gradle / Ivy
Show all versions of checker Show documentation
package org.checkerframework.framework.type;
// The imports from com.sun are all @jdk.Exported and therefore somewhat safe to use.
// Try to avoid using [email protected] classes.
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.util.Options;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Target;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
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.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.IntersectionType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import org.checkerframework.checker.interning.qual.FindDistinct;
import org.checkerframework.checker.interning.qual.InternedDistinct;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.CanonicalName;
import org.checkerframework.checker.signature.qual.FullyQualifiedName;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.common.reflection.DefaultReflectionResolver;
import org.checkerframework.common.reflection.MethodValAnnotatedTypeFactory;
import org.checkerframework.common.reflection.MethodValChecker;
import org.checkerframework.common.reflection.ReflectionResolver;
import org.checkerframework.common.reflection.qual.MethodVal;
import org.checkerframework.common.wholeprograminference.WholeProgramInference;
import org.checkerframework.common.wholeprograminference.WholeProgramInferenceImplementation;
import org.checkerframework.common.wholeprograminference.WholeProgramInferenceJavaParserStorage;
import org.checkerframework.common.wholeprograminference.WholeProgramInferenceScenesStorage;
import org.checkerframework.dataflow.qual.SideEffectFree;
import org.checkerframework.framework.qual.AnnotatedFor;
import org.checkerframework.framework.qual.EnsuresQualifier;
import org.checkerframework.framework.qual.EnsuresQualifierIf;
import org.checkerframework.framework.qual.FieldInvariant;
import org.checkerframework.framework.qual.FromStubFile;
import org.checkerframework.framework.qual.HasQualifierParameter;
import org.checkerframework.framework.qual.InheritedAnnotation;
import org.checkerframework.framework.qual.NoQualifierParameter;
import org.checkerframework.framework.qual.RequiresQualifier;
import org.checkerframework.framework.source.SourceChecker;
import org.checkerframework.framework.stub.AnnotationFileElementTypes;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.framework.type.visitor.AnnotatedTypeCombiner;
import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.AnnotationFormatter;
import org.checkerframework.framework.util.AnnotationMirrorSet;
import org.checkerframework.framework.util.CheckerMain;
import org.checkerframework.framework.util.DefaultAnnotationFormatter;
import org.checkerframework.framework.util.FieldInvariants;
import org.checkerframework.framework.util.TreePathCacher;
import org.checkerframework.framework.util.typeinference.DefaultTypeArgumentInference;
import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil;
import org.checkerframework.framework.util.typeinference.TypeArgumentInference;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationProvider;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.CollectionUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypeAnnotationUtils;
import org.checkerframework.javacutil.TypeKindUtils;
import org.checkerframework.javacutil.TypeSystemError;
import org.checkerframework.javacutil.TypesUtils;
import org.checkerframework.javacutil.UserError;
import org.checkerframework.javacutil.trees.DetachedVarSymbol;
import org.plumelib.util.CollectionsPlume;
import org.plumelib.util.ImmutableTypes;
import org.plumelib.util.StringsPlume;
import scenelib.annotations.el.AMethod;
import scenelib.annotations.el.ATypeElement;
/**
* The methods of this class take an element or AST node, and return the annotated type as an {@link
* AnnotatedTypeMirror}. The methods are:
*
*
* - {@link #getAnnotatedType(ClassTree)}
*
- {@link #getAnnotatedType(MethodTree)}
*
- {@link #getAnnotatedType(Tree)}
*
- {@link #getAnnotatedTypeFromTypeTree(Tree)}
*
- {@link #getAnnotatedType(TypeElement)}
*
- {@link #getAnnotatedType(ExecutableElement)}
*
- {@link #getAnnotatedType(Element)}
*
*
* This implementation only adds qualifiers explicitly specified by the programmer. Subclasses
* override {@link #addComputedTypeAnnotations} to add defaults, flow-sensitive refinement, and
* type-system-specific rules.
*
* Unless otherwise indicated, each public method in this class returns a "fully annotated" type,
* which is one that has an annotation in all positions.
*
*
Type system checker writers may need to subclass this class, to add default qualifiers
* according to the type system semantics. Subclasses should especially override {@link
* #addComputedTypeAnnotations(Element, AnnotatedTypeMirror)} and {@link
* #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)} to handle default annotations. (Also,
* {@link #addDefaultAnnotations(AnnotatedTypeMirror)} adds annotations, but that method is a
* workaround for Issue
* 979.)
*
* @checker_framework.manual #creating-a-checker How to write a checker plug-in
*/
public class AnnotatedTypeFactory implements AnnotationProvider {
/** Whether to print verbose debugging messages about stub files. */
private final boolean debugStubParser;
/** The {@link Trees} instance to use for tree node path finding. */
protected final Trees trees;
/** Optional! The AST of the source file being operated on. */
// TODO: when should root be null? What are the use cases?
// None of the existing test checkers has a null root.
// Should not be modified between calls to "visit".
protected @Nullable CompilationUnitTree root;
/** The processing environment to use for accessing compiler internals. */
protected final ProcessingEnvironment processingEnv;
/** Utility class for working with {@link Element}s. */
protected final Elements elements;
/** Utility class for working with {@link TypeMirror}s. */
public final Types types;
/**
* A TreePath to the current tree that an external "visitor" is visiting. The visitor is either a
* subclass of {@link BaseTypeVisitor} or {@link
* org.checkerframework.framework.flow.CFAbstractTransfer}.
*/
private @Nullable TreePath visitorTreePath;
/** The AnnotatedFor.value argument/element. */
private final ExecutableElement annotatedForValueElement;
/** The EnsuresQualifier.expression field/element. */
final ExecutableElement ensuresQualifierExpressionElement;
/** The EnsuresQualifier.List.value field/element. */
final ExecutableElement ensuresQualifierListValueElement;
/** The EnsuresQualifierIf.expression field/element. */
final ExecutableElement ensuresQualifierIfExpressionElement;
/** The EnsuresQualifierIf.result argument/element. */
final ExecutableElement ensuresQualifierIfResultElement;
/** The EnsuresQualifierIf.List.value field/element. */
final ExecutableElement ensuresQualifierIfListValueElement;
/** The FieldInvariant.field argument/element. */
private final ExecutableElement fieldInvariantFieldElement;
/** The FieldInvariant.qualifier argument/element. */
private final ExecutableElement fieldInvariantQualifierElement;
/** The HasQualifierParameter.value field/element. */
private final ExecutableElement hasQualifierParameterValueElement;
/** The MethodVal.className argument/element. */
public final ExecutableElement methodValClassNameElement;
/** The MethodVal.methodName argument/element. */
public final ExecutableElement methodValMethodNameElement;
/** The MethodVal.params argument/element. */
public final ExecutableElement methodValParamsElement;
/** The NoQualifierParameter.value field/element. */
private final ExecutableElement noQualifierParameterValueElement;
/** The RequiresQualifier.expression field/element. */
final ExecutableElement requiresQualifierExpressionElement;
/** The RequiresQualifier.List.value field/element. */
final ExecutableElement requiresQualifierListValueElement;
/** The RequiresQualifier type. */
TypeMirror requiresQualifierTM;
/** The RequiresQualifier.List type. */
TypeMirror requiresQualifierListTM;
/** The EnsuresQualifier type. */
TypeMirror ensuresQualifierTM;
/** The EnsuresQualifier.List type. */
TypeMirror ensuresQualifierListTM;
/** The EnsuresQualifierIf type. */
TypeMirror ensuresQualifierIfTM;
/** The EnsuresQualifierIf.List type. */
TypeMirror ensuresQualifierIfListTM;
/**
* ===== postInit initialized fields ==== Note: qualHierarchy and typeHierarchy are both
* initialized in the postInit.
*
* @see #postInit() This means, they cannot be final and cannot be referred to in any subclass
* constructor or method until after postInit is called
*/
/** Represent the annotation relations. */
protected QualifierHierarchy qualHierarchy;
/** Represent the type relations. */
protected TypeHierarchy typeHierarchy;
/** Performs whole-program inference. If null, whole-program inference is disabled. */
private final @Nullable WholeProgramInference wholeProgramInference;
/**
* This formatter is used for converting AnnotatedTypeMirrors to Strings. This formatter will be
* used by all AnnotatedTypeMirrors created by this factory in their toString methods.
*/
protected final AnnotatedTypeFormatter typeFormatter;
/**
* Annotation formatter is used to format AnnotationMirrors. It is primarily used by SourceChecker
* when generating error messages.
*/
private final AnnotationFormatter annotationFormatter;
/** Holds the qualifier upper bounds for type uses. */
protected QualifierUpperBounds qualifierUpperBounds;
/**
* Provides utility method to substitute arguments for their type variables. Field should be
* final, but can only be set in postInit, because subtypes might need other state to be
* initialized first.
*/
protected TypeVariableSubstitutor typeVarSubstitutor;
/** Provides utility method to infer type arguments. */
protected TypeArgumentInference typeArgumentInference;
/**
* Caches the supported type qualifier classes. Call {@link #getSupportedTypeQualifiers()} instead
* of using this field directly, as it may not have been initialized.
*/
private final Set> supportedQuals;
/**
* Caches the fully-qualified names of the classes in {@link #supportedQuals}. Call {@link
* #getSupportedTypeQualifierNames()} instead of using this field directly, as it may not have
* been initialized.
*/
private final Set<@CanonicalName String> supportedQualNames;
/** Parses stub files and stores annotations on public elements from stub files. */
public final AnnotationFileElementTypes stubTypes;
/** Parses ajava files and stores annotations on public elements from ajava files. */
public final AnnotationFileElementTypes ajavaTypes;
/**
* If type checking a Java file, stores annotations read from an ajava file for that class if one
* exists. Unlike {@link #ajavaTypes}, which only stores annotations on public elements, this
* stores annotations on all element locations such as in anonymous class bodies.
*/
public @Nullable AnnotationFileElementTypes currentFileAjavaTypes;
/**
* A cache used to store elements whose declaration annotations have already been stored by
* calling the method {@link #getDeclAnnotations(Element)}.
*/
private final Map> cacheDeclAnnos;
/**
* A set containing declaration annotations that should be inherited. A declaration annotation
* will be inherited if it is in this set, or if it has the meta-annotation @InheritedAnnotation.
*/
private final Set inheritedAnnotations = AnnotationUtils.createAnnotationSet();
/** The checker to use for option handling and resource management. */
protected final BaseTypeChecker checker;
/** Map keys are canonical names of aliased annotations. */
private final Map<@FullyQualifiedName String, Alias> aliases = new HashMap<>();
/**
* Scans all parts of the {@link AnnotatedTypeMirror} so that all of its fields are initialized.
*/
private SimpleAnnotatedTypeScanner atmInitializer =
new SimpleAnnotatedTypeScanner<>((type1, q) -> null);
/**
* Initializes all fields of {@code type}.
*
* @param type annotated type mirror
*/
public void initializeAtm(AnnotatedTypeMirror type) {
atmInitializer.visit(type);
}
/**
* Information about one annotation alias.
*
* The information is either an AnotationMirror that can be used directly, or information for a
* builder (name and fields not to copy); see checkRep.
*/
private static class Alias {
/** The canonical annotation (or null if copyElements == true). */
AnnotationMirror canonical;
/** Whether elements should be copied over when translating to the canonical annotation. */
boolean copyElements;
/** The canonical annotation name (or null if copyElements == false). */
@CanonicalName String canonicalName;
/** Which elements should not be copied over (or null if copyElements == false). */
String[] ignorableElements;
/**
* Create an Alias with the given components.
*
* @param aliasName the alias name; only used for debugging
* @param canonical the canonical annotation
* @param copyElements whether elements should be copied over when translating to the canonical
* annotation
* @param canonicalName the canonical annotation name (or null if copyElements == false)
* @param ignorableElements elements that should not be copied over
*/
Alias(
String aliasName,
AnnotationMirror canonical,
boolean copyElements,
@CanonicalName String canonicalName,
String[] ignorableElements) {
this.canonical = canonical;
this.copyElements = copyElements;
this.canonicalName = canonicalName;
this.ignorableElements = ignorableElements;
checkRep(aliasName);
}
/**
* Throw an exception if this object is malformed.
*
* @param aliasName the alias name; only used for diagnostic messages
*/
void checkRep(String aliasName) {
if (copyElements) {
if (!(canonical == null && canonicalName != null && ignorableElements != null)) {
throw new BugInCF(
"Bad Alias for %s: [canonical=%s] copyElements=%s canonicalName=%s"
+ " ignorableElements=%s",
aliasName, canonical, copyElements, canonicalName, ignorableElements);
}
} else {
if (!(canonical != null && canonicalName == null && ignorableElements == null)) {
throw new BugInCF(
"Bad Alias for %s: canonical=%s copyElements=%s [canonicalName=%s"
+ " ignorableElements=%s]",
aliasName, canonical, copyElements, canonicalName, ignorableElements);
}
}
}
}
/**
* A map from the class of an annotation to the set of classes for annotations with the same
* meaning, as well as the annotation mirror that should be used.
*/
private final Map<
Class, Pair>>>
declAliases = new HashMap<>();
/** Unique ID counter; for debugging purposes. */
private static int uidCounter = 0;
/** Unique ID of the current object; for debugging purposes. */
public final int uid;
/**
* Object that is used to resolve reflective method calls, if reflection resolution is turned on.
*/
protected ReflectionResolver reflectionResolver;
/** AnnotationClassLoader used to load type annotation classes via reflective lookup. */
protected AnnotationClassLoader loader;
/**
* Which whole-program inference output format to use, if doing whole-program inference. This
* variable would be final, but it is not set unless WPI is enabled.
*/
public WholeProgramInference.OutputFormat wpiOutputFormat;
/**
* Should results be cached? This means that ATM.deepCopy() will be called. ATM.deepCopy() used to
* (and perhaps still does) side effect the ATM being copied. So setting this to false is not
* equivalent to setting shouldReadCache to false.
*/
public boolean shouldCache;
/** Size of LRU cache if one isn't specified using the atfCacheSize option. */
private static final int DEFAULT_CACHE_SIZE = 300;
/** Mapping from a Tree to its annotated type; defaults have been applied. */
private final Map classAndMethodTreeCache;
/**
* Mapping from an expression tree to its annotated type; before defaults are applied, just what
* the programmer wrote.
*/
protected final Map fromExpressionTreeCache;
/**
* Mapping from a member tree to its annotated type; before defaults are applied, just what the
* programmer wrote.
*/
protected final Map fromMemberTreeCache;
/**
* Mapping from a type tree to its annotated type; before defaults are applied, just what the
* programmer wrote.
*/
protected final Map fromTypeTreeCache;
/**
* Mapping from an Element to its annotated type; before defaults are applied, just what the
* programmer wrote.
*/
private final Map elementCache;
/** Mapping from an Element to the source Tree of the declaration. */
private final Map elementToTreeCache;
/** Mapping from a Tree to its TreePath. Shared between all instances. */
private final TreePathCacher treePathCache;
/** Mapping from CFG-generated trees to their enclosing elements. */
protected final Map artificialTreeToEnclosingElementMap;
/**
* Whether to ignore uninferred type arguments. This is a temporary flag to work around Issue 979.
*/
public final boolean ignoreUninferredTypeArguments;
/** The Object.getClass method. */
protected final ExecutableElement objectGetClass;
/** Size of the annotationClassNames cache. */
private static final int ANNOTATION_CACHE_SIZE = 500;
/** Maps classes representing AnnotationMirrors to their canonical names. */
private final Map, @CanonicalName String> annotationClassNames;
/** An annotated type of the declaration of {@link Iterable} without any annotations. */
private AnnotatedDeclaredType iterableDeclType;
/**
* Constructs a factory from the given {@link ProcessingEnvironment} instance and syntax tree
* root. (These parameters are required so that the factory may conduct the appropriate
* annotation-gathering analyses on certain tree types.)
*
* Root can be {@code null} if the factory does not operate on trees.
*
*
A subclass must call postInit at the end of its constructor. postInit must be the last call
* in the constructor or else types from stub files may not be created as expected.
*
* @param checker the {@link SourceChecker} to which this factory belongs
* @throws IllegalArgumentException if either argument is {@code null}
*/
public AnnotatedTypeFactory(BaseTypeChecker checker) {
uid = ++uidCounter;
this.processingEnv = checker.getProcessingEnvironment();
// this.root = root;
this.checker = checker;
this.trees = Trees.instance(processingEnv);
this.elements = processingEnv.getElementUtils();
this.types = processingEnv.getTypeUtils();
this.supportedQuals = new HashSet<>();
this.supportedQualNames = new HashSet<>();
this.stubTypes = new AnnotationFileElementTypes(this);
this.ajavaTypes = new AnnotationFileElementTypes(this);
this.currentFileAjavaTypes = null;
this.cacheDeclAnnos = new HashMap<>();
this.artificialTreeToEnclosingElementMap = new HashMap<>();
// get the shared instance from the checker
this.treePathCache = checker.getTreePathCacher();
this.shouldCache = !checker.hasOption("atfDoNotCache");
if (shouldCache) {
int cacheSize = getCacheSize();
this.classAndMethodTreeCache = CollectionUtils.createLRUCache(cacheSize);
this.fromExpressionTreeCache = CollectionUtils.createLRUCache(cacheSize);
this.fromMemberTreeCache = CollectionUtils.createLRUCache(cacheSize);
this.fromTypeTreeCache = CollectionUtils.createLRUCache(cacheSize);
this.elementCache = CollectionUtils.createLRUCache(cacheSize);
this.elementToTreeCache = CollectionUtils.createLRUCache(cacheSize);
this.annotationClassNames =
Collections.synchronizedMap(CollectionUtils.createLRUCache(ANNOTATION_CACHE_SIZE));
} else {
this.classAndMethodTreeCache = null;
this.fromExpressionTreeCache = null;
this.fromMemberTreeCache = null;
this.fromTypeTreeCache = null;
this.elementCache = null;
this.elementToTreeCache = null;
this.annotationClassNames = null;
}
this.typeFormatter = createAnnotatedTypeFormatter();
this.annotationFormatter = createAnnotationFormatter();
if (checker.hasOption("infer")) {
checkInvalidOptionsInferSignatures();
String inferArg = checker.getOption("infer");
// No argument means "jaifs", for (temporary) backwards compatibility.
if (inferArg == null) {
inferArg = "jaifs";
}
switch (inferArg) {
case "stubs":
wpiOutputFormat = WholeProgramInference.OutputFormat.STUB;
break;
case "jaifs":
wpiOutputFormat = WholeProgramInference.OutputFormat.JAIF;
break;
case "ajava":
wpiOutputFormat = WholeProgramInference.OutputFormat.AJAVA;
break;
default:
throw new UserError(
"Bad argument -Ainfer="
+ inferArg
+ " should be one of: -Ainfer=jaifs, -Ainfer=stubs, -Ainfer=ajava");
}
boolean showWpiFailedInferences = checker.hasOption("showWpiFailedInferences");
if (wpiOutputFormat == WholeProgramInference.OutputFormat.AJAVA) {
wholeProgramInference =
new WholeProgramInferenceImplementation(
this, new WholeProgramInferenceJavaParserStorage(this), showWpiFailedInferences);
} else {
wholeProgramInference =
new WholeProgramInferenceImplementation(
this, new WholeProgramInferenceScenesStorage(this), showWpiFailedInferences);
}
if (!checker.hasOption("warns")) {
// Without -Awarns, the inference output may be incomplete, because javac halts
// after issuing an error.
checker.message(Diagnostic.Kind.ERROR, "Do not supply -Ainfer without -Awarns");
}
} else {
wholeProgramInference = null;
}
ignoreUninferredTypeArguments = !checker.hasOption("conservativeUninferredTypeArguments");
objectGetClass = TreeUtils.getMethod("java.lang.Object", "getClass", 0, processingEnv);
this.debugStubParser = checker.hasOption("stubDebug");
annotatedForValueElement = TreeUtils.getMethod(AnnotatedFor.class, "value", 0, processingEnv);
ensuresQualifierExpressionElement =
TreeUtils.getMethod(EnsuresQualifier.class, "expression", 0, processingEnv);
ensuresQualifierListValueElement =
TreeUtils.getMethod(EnsuresQualifier.List.class, "value", 0, processingEnv);
ensuresQualifierIfExpressionElement =
TreeUtils.getMethod(EnsuresQualifierIf.class, "expression", 0, processingEnv);
ensuresQualifierIfResultElement =
TreeUtils.getMethod(EnsuresQualifierIf.class, "result", 0, processingEnv);
ensuresQualifierIfListValueElement =
TreeUtils.getMethod(EnsuresQualifierIf.List.class, "value", 0, processingEnv);
fieldInvariantFieldElement =
TreeUtils.getMethod(FieldInvariant.class, "field", 0, processingEnv);
fieldInvariantQualifierElement =
TreeUtils.getMethod(FieldInvariant.class, "qualifier", 0, processingEnv);
hasQualifierParameterValueElement =
TreeUtils.getMethod(HasQualifierParameter.class, "value", 0, processingEnv);
methodValClassNameElement = TreeUtils.getMethod(MethodVal.class, "className", 0, processingEnv);
methodValMethodNameElement =
TreeUtils.getMethod(MethodVal.class, "methodName", 0, processingEnv);
methodValParamsElement = TreeUtils.getMethod(MethodVal.class, "params", 0, processingEnv);
noQualifierParameterValueElement =
TreeUtils.getMethod(NoQualifierParameter.class, "value", 0, processingEnv);
requiresQualifierExpressionElement =
TreeUtils.getMethod(RequiresQualifier.class, "expression", 0, processingEnv);
requiresQualifierListValueElement =
TreeUtils.getMethod(RequiresQualifier.List.class, "value", 0, processingEnv);
requiresQualifierTM =
ElementUtils.getTypeElement(processingEnv, RequiresQualifier.class).asType();
requiresQualifierListTM =
ElementUtils.getTypeElement(processingEnv, RequiresQualifier.List.class).asType();
ensuresQualifierTM =
ElementUtils.getTypeElement(processingEnv, EnsuresQualifier.class).asType();
ensuresQualifierListTM =
ElementUtils.getTypeElement(processingEnv, EnsuresQualifier.List.class).asType();
ensuresQualifierIfTM =
ElementUtils.getTypeElement(processingEnv, EnsuresQualifierIf.class).asType();
ensuresQualifierIfListTM =
ElementUtils.getTypeElement(processingEnv, EnsuresQualifierIf.List.class).asType();
}
/**
* @throws BugInCF If supportedQuals is empty or if any of the support qualifiers has a @Target
* meta-annotation that contain something besides TYPE_USE or TYPE_PARAMETER. (@Target({}) is
* allowed.)
*/
private void checkSupportedQuals() {
if (supportedQuals.isEmpty()) {
throw new TypeSystemError("Found no supported qualifiers.");
}
for (Class annotationClass : supportedQuals) {
// Check @Target values
ElementType[] targetValues = annotationClass.getAnnotation(Target.class).value();
List badTargetValues = new ArrayList<>();
for (ElementType element : targetValues) {
if (!(element == ElementType.TYPE_USE || element == ElementType.TYPE_PARAMETER)) {
// if there's an ElementType with an enumerated value of something other
// than TYPE_USE or TYPE_PARAMETER then it isn't a valid qualifier
badTargetValues.add(element);
}
}
if (!badTargetValues.isEmpty()) {
String msg =
"The @Target meta-annotation on type qualifier "
+ annotationClass.toString()
+ " must not contain "
+ StringsPlume.conjunction("or", badTargetValues)
+ ".";
throw new TypeSystemError(msg);
}
}
}
/**
* This method is called only when {@code -Ainfer} is passed as an option. It checks if another
* option that should not occur simultaneously with the whole-program inference is also passed as
* argument, and aborts the process if that is the case. For example, the whole-program inference
* process was not designed to work with conservative defaults.
*
* Subclasses may override this method to add more options.
*/
protected void checkInvalidOptionsInferSignatures() {
// See Issue 683
// https://github.com/typetools/checker-framework/issues/683
if (checker.useConservativeDefault("source") || checker.useConservativeDefault("bytecode")) {
throw new UserError(
"The option -Ainfer=... cannot be used together with conservative defaults.");
}
}
/**
* Actions that logically belong in the constructor, but need to run after the subclass
* constructor has completed. In particular, parseStubFiles() may try to do type resolution with
* this AnnotatedTypeFactory.
*/
protected void postInit() {
this.qualHierarchy = createQualifierHierarchy();
if (qualHierarchy == null) {
throw new TypeSystemError(
"AnnotatedTypeFactory with null qualifier hierarchy not supported.");
} else if (!qualHierarchy.isValid()) {
throw new TypeSystemError(
"AnnotatedTypeFactory: invalid qualifier hierarchy: %s %s ",
qualHierarchy.getClass(), qualHierarchy);
}
this.typeHierarchy = createTypeHierarchy();
this.typeVarSubstitutor = createTypeVariableSubstitutor();
this.typeArgumentInference = createTypeArgumentInference();
this.qualifierUpperBounds = createQualifierUpperBounds();
// TODO: is this the best location for declaring this alias?
addAliasedDeclAnnotation(
org.jmlspecs.annotation.Pure.class,
org.checkerframework.dataflow.qual.Pure.class,
AnnotationBuilder.fromClass(elements, org.checkerframework.dataflow.qual.Pure.class));
// Accommodate the inability to write @InheritedAnnotation on these annotations.
addInheritedAnnotation(
AnnotationBuilder.fromClass(elements, org.checkerframework.dataflow.qual.Pure.class));
addInheritedAnnotation(
AnnotationBuilder.fromClass(
elements, org.checkerframework.dataflow.qual.SideEffectFree.class));
addInheritedAnnotation(
AnnotationBuilder.fromClass(
elements, org.checkerframework.dataflow.qual.Deterministic.class));
addInheritedAnnotation(
AnnotationBuilder.fromClass(
elements, org.checkerframework.dataflow.qual.TerminatesExecution.class));
initializeReflectionResolution();
if (this.getClass() == AnnotatedTypeFactory.class) {
this.parseAnnotationFiles();
}
TypeMirror iterableTypeMirror =
ElementUtils.getTypeElement(processingEnv, Iterable.class).asType();
this.iterableDeclType =
(AnnotatedDeclaredType) AnnotatedTypeMirror.createType(iterableTypeMirror, this, true);
}
/**
* Returns the checker associated with this factory.
*
* @return the checker associated with this factory
*/
public BaseTypeChecker getChecker() {
return checker;
}
/**
* Returns the names of the annotation processors that are being run.
*
* @return the names of the annotation processors that are being run
*/
@SuppressWarnings("JdkObsolete") // ClassLoader.getResources returns an Enumeration
public String[] getCheckerNames() {
com.sun.tools.javac.util.Context context =
((JavacProcessingEnvironment) processingEnv).getContext();
String processorArg = Options.instance(context).get("-processor");
if (processorArg != null) {
return processorArg.split(",");
}
try {
String filename = "META-INF/services/javax.annotation.processing.Processor";
List lines = new ArrayList<>();
Enumeration urls = getClass().getClassLoader().getResources(filename);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
lines.addAll(in.lines().collect(Collectors.toList()));
}
String[] result = lines.toArray(new String[0]);
return result;
} catch (IOException e) {
throw new BugInCF(e);
}
}
/**
* Creates {@link QualifierUpperBounds} for this type factory.
*
* @return a new {@link QualifierUpperBounds} for this type factory
*/
protected QualifierUpperBounds createQualifierUpperBounds() {
return new QualifierUpperBounds(this);
}
/**
* Return {@link QualifierUpperBounds} for this type factory.
*
* @return {@link QualifierUpperBounds} for this type factory
*/
public QualifierUpperBounds getQualifierUpperBounds() {
return qualifierUpperBounds;
}
/**
* Returns the WholeProgramInference instance (may be null).
*
* @return the WholeProgramInference instance, or null
*/
public WholeProgramInference getWholeProgramInference() {
return wholeProgramInference;
}
protected void initializeReflectionResolution() {
if (checker.shouldResolveReflection()) {
boolean debug = "debug".equals(checker.getOption("resolveReflection"));
MethodValChecker methodValChecker = checker.getSubchecker(MethodValChecker.class);
assert methodValChecker != null
: "AnnotatedTypeFactory: reflection resolution was requested, but MethodValChecker isn't"
+ " a subchecker.";
MethodValAnnotatedTypeFactory methodValATF =
(MethodValAnnotatedTypeFactory) methodValChecker.getAnnotationProvider();
reflectionResolver = new DefaultReflectionResolver(checker, methodValATF, debug);
}
}
/**
* Set the CompilationUnitTree that should be used.
*
* @param root the new compilation unit to use
*/
public void setRoot(@Nullable CompilationUnitTree root) {
if (root != null && wholeProgramInference != null) {
for (Tree typeDecl : root.getTypeDecls()) {
if (typeDecl.getKind() == Tree.Kind.CLASS) {
ClassTree classTree = (ClassTree) typeDecl;
wholeProgramInference.preprocessClassTree(classTree);
}
}
}
this.root = root;
// Do not clear here. Only the primary checker should clear this cache.
// treePathCache.clear();
// setRoot in a GenericAnnotatedTypeFactory will clear this;
// if this isn't a GenericATF, then it must clear it itself.
if (!(this instanceof GenericAnnotatedTypeFactory)) {
artificialTreeToEnclosingElementMap.clear();
}
if (shouldCache) {
// Clear the caches with trees because once the compilation unit changes,
// the trees may be modified and lose type arguments.
elementToTreeCache.clear();
fromExpressionTreeCache.clear();
fromMemberTreeCache.clear();
fromTypeTreeCache.clear();
classAndMethodTreeCache.clear();
// There is no need to clear the following cache, it is limited by cache size and it
// contents won't change between compilation units.
// elementCache.clear();
}
if (root != null && checker.hasOption("ajava")) {
// Search for an ajava file with annotations for the current source file and the current
// checker. It will be in a directory specified by the "ajava" option in a subdirectory
// corresponding to this file's package. For example, a file in package a.b would be in a
// subdirectory a/b. The filename is ClassName-checker.qualified.name.ajava. If such a file
// exists, read its detailed annotation data, including annotations on private elements.
String packagePrefix =
root.getPackageName() != null
? TreeUtils.nameExpressionToString(root.getPackageName()) + "."
: "";
// The method getName() returns a path.
String className = root.getSourceFile().getName();
// Extract the basename.
int lastSeparator = className.lastIndexOf(File.separator);
if (lastSeparator != -1) {
className = className.substring(lastSeparator + 1);
}
// Drop the ".java" extension.
if (className.endsWith(".java")) {
className = className.substring(0, className.length() - ".java".length());
}
String qualifiedName = packagePrefix + className;
for (String ajavaLocation : checker.getOption("ajava").split(File.pathSeparator)) {
String ajavaPath =
ajavaLocation
+ File.separator
+ qualifiedName.replaceAll("\\.", "/")
+ "-"
+ checker.getClass().getCanonicalName()
+ ".ajava";
File ajavaFile = new File(ajavaPath);
if (ajavaFile.exists()) {
currentFileAjavaTypes = new AnnotationFileElementTypes(this);
currentFileAjavaTypes.parseAjavaFileWithTree(ajavaPath, root);
break;
}
}
} else {
currentFileAjavaTypes = null;
}
}
@SideEffectFree
@Override
public String toString() {
return getClass().getSimpleName() + "#" + uid;
}
/**
* Returns the {@link QualifierHierarchy} to be used by this checker.
*
* The implementation builds the type qualifier hierarchy for the {@link
* #getSupportedTypeQualifiers()} using the meta-annotations found in them. The current
* implementation returns an instance of {@code NoElementQualifierHierarchy}.
*
*
Subclasses must override this method if their qualifiers have elements; the method must
* return an implementation of {@link QualifierHierarchy}, such as {@link
* ElementQualifierHierarchy}.
*
* @return a QualifierHierarchy for this type system
*/
protected QualifierHierarchy createQualifierHierarchy() {
return new NoElementQualifierHierarchy(this.getSupportedTypeQualifiers(), elements);
}
/**
* Returns the type qualifier hierarchy graph to be used by this processor.
*
* @see #createQualifierHierarchy()
* @return the {@link QualifierHierarchy} for this checker
*/
public final QualifierHierarchy getQualifierHierarchy() {
return qualHierarchy;
}
/**
* Creates the type hierarchy to be used by this factory.
*
*
Subclasses may override this method to specify new type-checking rules beyond the typical
* Java subtyping rules.
*
* @return the type relations class to check type subtyping
*/
protected TypeHierarchy createTypeHierarchy() {
return new DefaultTypeHierarchy(
checker,
getQualifierHierarchy(),
checker.getBooleanOption("ignoreRawTypeArguments", true),
checker.hasOption("invariantArrays"));
}
public final TypeHierarchy getTypeHierarchy() {
return typeHierarchy;
}
/** TypeVariableSubstitutor provides a method to replace type parameters with their arguments. */
protected TypeVariableSubstitutor createTypeVariableSubstitutor() {
return new TypeVariableSubstitutor();
}
public TypeVariableSubstitutor getTypeVarSubstitutor() {
return typeVarSubstitutor;
}
/**
* TypeArgumentInference infers the method type arguments when they are not explicitly written.
*/
protected TypeArgumentInference createTypeArgumentInference() {
return new DefaultTypeArgumentInference(this);
}
public TypeArgumentInference getTypeArgumentInference() {
return typeArgumentInference;
}
/**
* Factory method to easily change what {@link AnnotationClassLoader} is created to load type
* annotation classes. Subclasses can override this method and return a custom
* AnnotationClassLoader subclass to customize loading logic.
*/
protected AnnotationClassLoader createAnnotationClassLoader() {
return new AnnotationClassLoader(checker);
}
/**
* Returns a mutable set of annotation classes that are supported by a checker.
*
*
Subclasses may override this method to return a mutable set of their supported type
* qualifiers through one of the 5 approaches shown below.
*
*
Subclasses should not call this method; they should call {@link #getSupportedTypeQualifiers}
* instead.
*
*
By default, a checker supports all annotations located in a subdirectory called {@literal
* qual} that's located in the same directory as the checker. Note that only annotations defined
* with the {@code @Target({ElementType.TYPE_USE})} meta-annotation (and optionally with the
* additional value of {@code ElementType.TYPE_PARAMETER}, but no other {@code ElementType}
* values) are automatically considered as supported annotations.
*
*
To support a different set of annotations than those in the {@literal qual} subdirectory, or
* that have other {@code ElementType} values, see examples below.
*
*
In total, there are 5 ways to indicate annotations that are supported by a checker:
*
*
* - Only support annotations located in a checker's {@literal qual} directory:
*
This is the default behavior. Simply place those annotations within the {@literal
* qual} directory.
*
- Support annotations located in a checker's {@literal qual} directory and a list of other
* annotations:
*
Place those annotations within the {@literal qual} directory, and override {@link
* #createSupportedTypeQualifiers()} by calling {@link #getBundledTypeQualifiers(Class...)}
* with a varargs parameter list of the other annotations. Code example:
*
* {@code @Override protected Set> createSupportedTypeQualifiers() {
* return getBundledTypeQualifiers(Regex.class, PartialRegex.class, RegexBottom.class, UnknownRegex.class);
* } }
*
* - Supporting only annotations that are explicitly listed: Override {@link
* #createSupportedTypeQualifiers()} and return a mutable set of the supported annotations.
* Code example:
*
* {@code @Override protected Set> createSupportedTypeQualifiers() {
* return new HashSet>(
* Arrays.asList(A.class, B.class));
* } }
*
* The set of qualifiers returned by {@link #createSupportedTypeQualifiers()} must be a
* fresh, mutable set. The methods {@link #getBundledTypeQualifiers(Class...)} must return a
* fresh, mutable set
*
*
* @return the type qualifiers supported this processor, or an empty set if none
*/
protected Set> createSupportedTypeQualifiers() {
return getBundledTypeQualifiers();
}
/**
* Loads all annotations contained in the qual directory of a checker via reflection; if a
* polymorphic type qualifier exists, and an explicit array of annotations to the set of
* annotation classes.
*
* This method can be called in the overridden versions of {@link
* #createSupportedTypeQualifiers()} in each checker.
*
* @param explicitlyListedAnnotations a varargs array of explicitly listed annotation classes to
* be added to the returned set. For example, it is used frequently to add Bottom qualifiers.
* @return a mutable set of the loaded and listed annotation classes
*/
@SafeVarargs
protected final Set> getBundledTypeQualifiers(
Class... explicitlyListedAnnotations) {
return loadTypeAnnotationsFromQualDir(explicitlyListedAnnotations);
}
/**
* Instantiates the AnnotationClassLoader and loads all annotations contained in the qual
* directory of a checker via reflection, and has the option to include an explicitly stated list
* of annotations (eg ones found in a different directory than qual).
*
* The annotations that are automatically loaded must have the {@link
* java.lang.annotation.Target Target} meta-annotation with the value of {@link
* ElementType#TYPE_USE} (and optionally {@link ElementType#TYPE_PARAMETER}). If it has other
* {@link ElementType} values, it won't be loaded. Other annotation classes must be explicitly
* listed even if they are in the same directory as the checker's qual directory.
*
* @param explicitlyListedAnnotations a set of explicitly listed annotation classes to be added to
* the returned set, for example, it is used frequently to add Bottom qualifiers
* @return a set of annotation class instances
*/
@SafeVarargs
@SuppressWarnings("varargs")
private final Set> loadTypeAnnotationsFromQualDir(
Class... explicitlyListedAnnotations) {
loader = createAnnotationClassLoader();
Set> annotations = loader.getBundledAnnotationClasses();
// add in all explicitly Listed qualifiers
if (explicitlyListedAnnotations != null) {
annotations.addAll(Arrays.asList(explicitlyListedAnnotations));
}
return annotations;
}
/**
* Creates the AnnotatedTypeFormatter used by this type factory and all AnnotatedTypeMirrors it
* creates. The AnnotatedTypeFormatter is used in AnnotatedTypeMirror.toString and will affect the
* error messages printed for checkers that use this type factory.
*
* @return the AnnotatedTypeFormatter to pass to all instantiated AnnotatedTypeMirrors
*/
protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() {
boolean printVerboseGenerics = checker.hasOption("printVerboseGenerics");
return new DefaultAnnotatedTypeFormatter(
printVerboseGenerics,
// -AprintVerboseGenerics implies -AprintAllQualifiers
printVerboseGenerics || checker.hasOption("printAllQualifiers"));
}
public AnnotatedTypeFormatter getAnnotatedTypeFormatter() {
return typeFormatter;
}
protected AnnotationFormatter createAnnotationFormatter() {
return new DefaultAnnotationFormatter();
}
public AnnotationFormatter getAnnotationFormatter() {
return annotationFormatter;
}
/**
* Returns an immutable set of the classes corresponding to the type qualifiers supported by this
* checker.
*
* Subclasses cannot override this method; they should override {@link
* #createSupportedTypeQualifiers createSupportedTypeQualifiers} instead.
*
* @see #createSupportedTypeQualifiers()
* @return an immutable set of the supported type qualifiers, or an empty set if no qualifiers are
* supported
*/
public final Set> getSupportedTypeQualifiers() {
if (this.supportedQuals.isEmpty()) {
supportedQuals.addAll(createSupportedTypeQualifiers());
checkSupportedQuals();
}
return Collections.unmodifiableSet(supportedQuals);
}
/**
* Returns an immutable set of the fully qualified names of the type qualifiers supported by this
* checker.
*
* Subclasses cannot override this method; they should override {@link
* #createSupportedTypeQualifiers createSupportedTypeQualifiers} instead.
*
* @see #createSupportedTypeQualifiers()
* @return an immutable set of the supported type qualifiers, or an empty set if no qualifiers are
* supported
*/
public final Set<@CanonicalName String> getSupportedTypeQualifierNames() {
if (this.supportedQualNames.isEmpty()) {
for (Class clazz : getSupportedTypeQualifiers()) {
supportedQualNames.add(clazz.getCanonicalName());
}
}
return Collections.unmodifiableSet(supportedQualNames);
}
// **********************************************************************
// Factories for annotated types that account for default qualifiers
// **********************************************************************
/**
* Returns the size for LRU caches. It is either the value supplied via the {@code -AatfCacheSize}
* option or the default cache size.
*
* @return cache size passed as argument to checker or DEFAULT_CACHE_SIZE
*/
protected int getCacheSize() {
String option = checker.getOption("atfCacheSize");
if (option == null) {
return DEFAULT_CACHE_SIZE;
}
try {
return Integer.valueOf(option);
} catch (NumberFormatException ex) {
throw new UserError("atfCacheSize was not an integer: " + option);
}
}
/**
* Returns an AnnotatedTypeMirror representing the annotated type of {@code elt}.
*
* @param elt the element
* @return the annotated type of {@code elt}
*/
public AnnotatedTypeMirror getAnnotatedType(Element elt) {
if (elt == null) {
throw new BugInCF("AnnotatedTypeFactory.getAnnotatedType: null element");
}
// Annotations explicitly written in the source code,
// or obtained from bytecode.
AnnotatedTypeMirror type = fromElement(elt);
addComputedTypeAnnotations(elt, type);
return type;
}
/**
* Returns an AnnotatedTypeMirror representing the annotated type of {@code clazz}.
*
* @param clazz a class
* @return the annotated type of {@code clazz}
*/
public AnnotatedTypeMirror getAnnotatedType(Class clazz) {
return getAnnotatedType(elements.getTypeElement(clazz.getCanonicalName()));
}
@Override
public @Nullable AnnotationMirror getAnnotationMirror(
Tree tree, Class target) {
if (isSupportedQualifier(target)) {
AnnotatedTypeMirror atm = getAnnotatedType(tree);
return atm.getAnnotation(target);
}
return null;
}
/**
* Returns an AnnotatedTypeMirror representing the annotated type of {@code tree}.
*
* @param tree the AST node
* @return the annotated type of {@code tree}
*/
public AnnotatedTypeMirror getAnnotatedType(Tree tree) {
if (tree == null) {
throw new BugInCF("AnnotatedTypeFactory.getAnnotatedType: null tree");
}
if (shouldCache && classAndMethodTreeCache.containsKey(tree)) {
return classAndMethodTreeCache.get(tree).deepCopy();
}
AnnotatedTypeMirror type;
if (TreeUtils.isClassTree(tree)) {
type = fromClass((ClassTree) tree);
} else if (tree.getKind() == Tree.Kind.METHOD || tree.getKind() == Tree.Kind.VARIABLE) {
type = fromMember(tree);
} else if (TreeUtils.isExpressionTree(tree)) {
tree = TreeUtils.withoutParens((ExpressionTree) tree);
type = fromExpression((ExpressionTree) tree);
} else {
throw new BugInCF(
"AnnotatedTypeFactory.getAnnotatedType: query of annotated type for tree "
+ tree.getKind());
}
addComputedTypeAnnotations(tree, type);
if (TreeUtils.isClassTree(tree) || tree.getKind() == Tree.Kind.METHOD) {
// Don't cache VARIABLE
if (shouldCache) {
classAndMethodTreeCache.put(tree, type.deepCopy());
}
} else {
// No caching otherwise
}
return type;
}
/**
* Called by {@link BaseTypeVisitor#visitClass(ClassTree, Void)} before the classTree is type
* checked.
*
* @param classTree ClassTree on which to perform preprocessing
*/
public void preProcessClassTree(ClassTree classTree) {}
/**
* Called by {@link BaseTypeVisitor#visitClass(ClassTree, Void)} after the ClassTree has been type
* checked.
*
*
The default implementation uses this to store the defaulted AnnotatedTypeMirrors and
* inherited declaration annotations back into the corresponding Elements. Subclasses might want
* to override this method if storing defaulted types is not desirable.
*/
public void postProcessClassTree(ClassTree tree) {
TypesIntoElements.store(processingEnv, this, tree);
DeclarationsIntoElements.store(processingEnv, this, tree);
if (wholeProgramInference != null) {
// Write out the results of whole-program inference, just once for each class. As soon as any
// class is finished processing, all modified scenes are written to files, in case this was
// the last class to be processed. Post-processing of subsequent classes might result in
// re-writing some of the scenes if new information has been written to them.
wholeProgramInference.writeResultsToFile(wpiOutputFormat, this.checker);
}
}
/**
* Determines the annotated type from a type in tree form.
*
*
Note that we cannot decide from a Tree whether it is a type use or an expression.
* TreeUtils.isTypeTree is only an under-approximation. For example, an identifier can be either a
* type or an expression.
*
* @param tree the type tree
* @return the annotated type of the type in the AST
*/
public AnnotatedTypeMirror getAnnotatedTypeFromTypeTree(Tree tree) {
if (tree == null) {
throw new BugInCF("AnnotatedTypeFactory.getAnnotatedTypeFromTypeTree: null tree");
}
AnnotatedTypeMirror type = fromTypeTree(tree);
addComputedTypeAnnotations(tree, type);
return type;
}
/**
* Returns the set of qualifiers that are the upper bounds for a use of the type.
*
* @param type a type whose upper bounds to obtain
*/
public Set getTypeDeclarationBounds(TypeMirror type) {
return qualifierUpperBounds.getBoundQualifiers(type);
}
/**
* Returns the set of qualifiers that are the upper bound for a type use if no other bound is
* specified for the type.
*
* This implementation returns the top qualifiers by default. Subclass may override to return
* different qualifiers.
*
* @return the set of qualifiers that are the upper bound for a type use if no other bound is
* specified for the type
*/
protected Set getDefaultTypeDeclarationBounds() {
return qualHierarchy.getTopAnnotations();
}
/**
* Returns the type of the extends or implements clause.
*
*
The primary qualifier is either an explicit annotation on {@code clause}, or it is the
* qualifier upper bounds for uses of the type of the clause.
*
* @param clause tree that represents an extends or implements clause
* @return the type of the extends or implements clause
*/
public AnnotatedTypeMirror getTypeOfExtendsImplements(Tree clause) {
AnnotatedTypeMirror fromTypeTree = fromTypeTree(clause);
Set bound = getTypeDeclarationBounds(fromTypeTree.getUnderlyingType());
fromTypeTree.addMissingAnnotations(bound);
return fromTypeTree;
}
// **********************************************************************
// Factories for annotated types that do not account for default qualifiers.
// They only include qualifiers explicitly inserted by the user.
// **********************************************************************
/**
* Creates an AnnotatedTypeMirror for {@code elt} that includes: annotations explicitly written on
* the element and annotations from stub files.
*
* Does not include default qualifiers. To obtain them, use {@link #getAnnotatedType(Element)}.
*
*
Does not include fake overrides from the stub file.
*
* @param elt the element
* @return AnnotatedTypeMirror of the element with explicitly-written and stub file annotations
*/
public AnnotatedTypeMirror fromElement(Element elt) {
if (shouldCache && elementCache.containsKey(elt)) {
return elementCache.get(elt).deepCopy();
}
if (elt.getKind() == ElementKind.PACKAGE) {
return toAnnotatedType(elt.asType(), false);
}
AnnotatedTypeMirror type;
// Because of a bug in Java 8, annotations on type parameters are not stored in elements, so get
// explicit annotations from the tree. (This bug has been fixed in Java 9.) Also, since
// annotations computed by the AnnotatedTypeFactory are stored in the element, the annotations
// have to be retrieved from the tree so that only explicit annotations are returned.
Tree decl = declarationFromElement(elt);
if (decl == null) {
type = stubTypes.getAnnotatedTypeMirror(elt);
if (type == null) {
type = toAnnotatedType(elt.asType(), ElementUtils.isTypeDeclaration(elt));
ElementAnnotationApplier.apply(type, elt, this);
}
} else if (decl instanceof ClassTree) {
type = fromClass((ClassTree) decl);
} else if (decl instanceof VariableTree) {
type = fromMember(decl);
} else if (decl instanceof MethodTree) {
type = fromMember(decl);
} else if (decl.getKind() == Tree.Kind.TYPE_PARAMETER) {
type = fromTypeTree(decl);
} else {
throw new BugInCF(
"AnnotatedTypeFactory.fromElement: cannot be here. decl: "
+ decl.getKind()
+ " elt: "
+ elt);
}
type = mergeAnnotationFileAnnosIntoType(type, elt, ajavaTypes);
if (currentFileAjavaTypes != null) {
type = mergeAnnotationFileAnnosIntoType(type, elt, currentFileAjavaTypes);
}
if (checker.hasOption("mergeStubsWithSource")) {
if (debugStubParser) {
System.out.printf("fromElement: mergeStubsIntoType(%s, %s)", type, elt);
}
type = mergeAnnotationFileAnnosIntoType(type, elt, stubTypes);
if (debugStubParser) {
System.out.printf(" => %s%n", type);
}
}
// Caching is disabled if annotation files are being parsed, because calls to this
// method before the annotation files are fully read can return incorrect results.
if (shouldCache
&& !stubTypes.isParsing()
&& !ajavaTypes.isParsing()
&& (currentFileAjavaTypes == null || !currentFileAjavaTypes.isParsing())) {
elementCache.put(elt, type.deepCopy());
}
return type;
}
/**
* Returns an AnnotatedDeclaredType with explicit annotations from the ClassTree {@code tree}.
*
* @param tree the class declaration
* @return AnnotatedDeclaredType with explicit annotations from {@code tree}
*/
private AnnotatedDeclaredType fromClass(ClassTree tree) {
return TypeFromTree.fromClassTree(this, tree);
}
/**
* Creates an AnnotatedTypeMirror for a variable or method declaration tree. The
* AnnotatedTypeMirror contains annotations explicitly written on the tree.
*
*
If a VariableTree is a parameter to a lambda, this method also adds annotations from the
* declared type of the functional interface and the executable type of its method.
*
*
The returned AnnotatedTypeMirror also contains explicitly written annotations from any ajava
* file and if {@code -AmergeStubsWithSource} is passed, it also merges any explicitly written
* annotations from stub files.
*
* @param tree MethodTree or VariableTree
* @return AnnotatedTypeMirror with explicit annotations from {@code tree}
*/
private final AnnotatedTypeMirror fromMember(Tree tree) {
if (!(tree instanceof MethodTree || tree instanceof VariableTree)) {
throw new BugInCF(
"AnnotatedTypeFactory.fromMember: not a method or variable declaration: " + tree);
}
if (shouldCache && fromMemberTreeCache.containsKey(tree)) {
return fromMemberTreeCache.get(tree).deepCopy();
}
AnnotatedTypeMirror result = TypeFromTree.fromMember(this, tree);
result = mergeAnnotationFileAnnosIntoType(result, tree, ajavaTypes);
if (currentFileAjavaTypes != null) {
result = mergeAnnotationFileAnnosIntoType(result, tree, currentFileAjavaTypes);
}
if (checker.hasOption("mergeStubsWithSource")) {
if (debugStubParser) {
System.out.printf("fromClass: mergeStubsIntoType(%s, %s)", result, tree);
}
result = mergeAnnotationFileAnnosIntoType(result, tree, stubTypes);
if (debugStubParser) {
System.out.printf(" => %s%n", result);
}
}
if (shouldCache) {
fromMemberTreeCache.put(tree, result.deepCopy());
}
return result;
}
/**
* Merges types from annotation files for {@code tree} into {@code type} by taking the greatest
* lower bound of the annotations in both.
*
* @param type the type to apply annotation file types to
* @param tree the tree from which to read annotation file types
* @param source storage for current annotation file annotations
* @return the given type, side-effected to add the annotation file types
*/
private AnnotatedTypeMirror mergeAnnotationFileAnnosIntoType(
@Nullable AnnotatedTypeMirror type, Tree tree, AnnotationFileElementTypes source) {
Element elt = TreeUtils.elementFromTree(tree);
return mergeAnnotationFileAnnosIntoType(type, elt, source);
}
/**
* A scanner used to combine annotations from two AnnotatedTypeMirrors. The scanner requires
* {@link #qualHierarchy}, which is set in {@link #postInit()} rather than the construtor, so
* lazily initialize this field before use.
*/
private @MonotonicNonNull AnnotatedTypeCombiner annotatedTypeCombiner = null;
/**
* Merges types from annotation files for {@code elt} into {@code type} by taking the greatest
* lower bound of the annotations in both.
*
* @param type the type to apply annotation file types to
* @param elt the element from which to read annotation file types
* @param source storage for current annotation file annotations
* @return the type, side-effected to add the annotation file types
*/
protected AnnotatedTypeMirror mergeAnnotationFileAnnosIntoType(
@Nullable AnnotatedTypeMirror type, Element elt, AnnotationFileElementTypes source) {
AnnotatedTypeMirror typeFromFile = source.getAnnotatedTypeMirror(elt);
if (typeFromFile == null) {
return type;
}
if (type == null) {
return typeFromFile;
}
if (annotatedTypeCombiner == null) {
annotatedTypeCombiner = new AnnotatedTypeCombiner(qualHierarchy);
}
// Must merge (rather than only take the annotation file type if it is a subtype) to support
// WPI.
annotatedTypeCombiner.visit(typeFromFile, type);
return type;
}
/**
* Creates an AnnotatedTypeMirror for an ExpressionTree. The AnnotatedTypeMirror contains explicit
* annotations written on the expression and for some expressions, annotations from
* sub-expressions that could have been explicitly written, defaulted, refined, or otherwise
* computed. (Expression whose type include annotations from sub-expressions are: ArrayAccessTree,
* ConditionalExpressionTree, IdentifierTree, MemberSelectTree, and MethodInvocationTree.)
*
*
For example, the AnnotatedTypeMirror returned for an array access expression is the fully
* annotated type of the array component of the array being accessed.
*
* @param tree an expression
* @return AnnotatedTypeMirror of the expressions either fully-annotated or partially annotated
* depending on the kind of expression
* @see TypeFromExpressionVisitor
*/
private AnnotatedTypeMirror fromExpression(ExpressionTree tree) {
if (shouldCache && fromExpressionTreeCache.containsKey(tree)) {
return fromExpressionTreeCache.get(tree).deepCopy();
}
AnnotatedTypeMirror result = TypeFromTree.fromExpression(this, tree);
if (shouldCache
&& tree.getKind() != Tree.Kind.NEW_CLASS
&& tree.getKind() != Tree.Kind.NEW_ARRAY
&& tree.getKind() != Tree.Kind.CONDITIONAL_EXPRESSION) {
// Don't cache the type of some expressions, because incorrect annotations would be
// cached during dataflow analysis. See Issue #602.
fromExpressionTreeCache.put(tree, result.deepCopy());
}
return result;
}
/**
* Creates an AnnotatedTypeMirror for the tree. The AnnotatedTypeMirror contains annotations
* explicitly written on the tree. It also adds type arguments to raw types that include
* annotations from the element declaration of the type {@link #fromElement(Element)}.
*
*
Called on the following trees: AnnotatedTypeTree, ArrayTypeTree, ParameterizedTypeTree,
* PrimitiveTypeTree, TypeParameterTree, WildcardTree, UnionType, IntersectionTypeTree, and
* IdentifierTree, MemberSelectTree.
*
* @param tree the type tree
* @return the (partially) annotated type of the type in the AST
*/
/*package private*/ final AnnotatedTypeMirror fromTypeTree(Tree tree) {
if (shouldCache && fromTypeTreeCache.containsKey(tree)) {
return fromTypeTreeCache.get(tree).deepCopy();
}
AnnotatedTypeMirror result = TypeFromTree.fromTypeTree(this, tree);
if (shouldCache) {
fromTypeTreeCache.put(tree, result.deepCopy());
}
return result;
}
// **********************************************************************
// Customization methods meant to be overridden by subclasses to include
// defaulted annotations
// **********************************************************************
/**
* Changes annotations on a type obtained from a {@link Tree}. By default, this method does
* nothing. GenericAnnotatedTypeFactory uses this method to implement defaulting and inference
* (flow-sensitive type refinement). Its subclasses usually override it only to customize default
* annotations.
*
*
Subclasses that override this method should also override {@link
* #addComputedTypeAnnotations(Element, AnnotatedTypeMirror)}.
*
*
In classes that extend {@link GenericAnnotatedTypeFactory}, override {@link
* GenericAnnotatedTypeFactory#addComputedTypeAnnotations(Tree, AnnotatedTypeMirror, boolean)}
* instead of this method.
*
* @param tree an AST node
* @param type the type obtained from {@code tree}
*/
protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) {
// Pass.
}
/**
* Changes annotations on a type obtained from an {@link Element}. By default, this method does
* nothing. GenericAnnotatedTypeFactory uses this method to implement defaulting.
*
*
Subclasses that override this method should also override {@link
* #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}.
*
* @param elt an element
* @param type the type obtained from {@code elt}
*/
protected void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) {
// Pass.
}
/**
* Adds default annotations to {@code type}. This method should only be used in places where the
* correct annotations cannot be computed because of uninferred type arguments. (See {@link
* AnnotatedWildcardType#isUninferredTypeArgument()}.)
*
* @param type annotated type to which default annotations are added
*/
public void addDefaultAnnotations(AnnotatedTypeMirror type) {
// Pass.
}
/**
* A callback method for the AnnotatedTypeFactory subtypes to customize directSupertypes().
* Overriding methods should merely change the annotations on the supertypes, without adding or
* removing new types.
*
*
The default provided implementation adds {@code type} annotations to {@code supertypes}.
* This allows the {@code type} and its supertypes to have the qualifiers.
*
* @param type the type whose supertypes are desired
* @param supertypes the supertypes as specified by the base AnnotatedTypeFactory
*/
protected void postDirectSuperTypes(
AnnotatedTypeMirror type, List supertypes) {
// Use the effective annotations here to get the correct annotations
// for type variables and wildcards.
Set annotations = type.getEffectiveAnnotations();
for (AnnotatedTypeMirror supertype : supertypes) {
if (!annotations.equals(supertype.getEffectiveAnnotations())) {
supertype.clearPrimaryAnnotations();
// TODO: is this correct for type variables and wildcards?
supertype.addAnnotations(annotations);
}
}
}
/**
* A callback method for the AnnotatedTypeFactory subtypes to customize
* AnnotatedTypes.asMemberOf(). Overriding methods should merely change the annotations on the
* subtypes, without changing the types.
*
* @param type the annotated type of the element
* @param owner the annotated type of the receiver of the accessing tree
* @param element the element of the field or method
*/
public void postAsMemberOf(AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) {
if (element.getKind() == ElementKind.FIELD) {
addAnnotationFromFieldInvariant(type, owner, (VariableElement) element);
}
addComputedTypeAnnotations(element, type);
}
/**
* Adds the qualifier specified by a field invariant for {@code field} to {@code type}.
*
* @param type annotated type to which the annotation is added
* @param accessedVia the annotated type of the receiver of the accessing tree. (Only used to get
* the type element of the underling type.)
* @param field element representing the field
*/
protected void addAnnotationFromFieldInvariant(
AnnotatedTypeMirror type, AnnotatedTypeMirror accessedVia, VariableElement field) {
TypeMirror declaringType = accessedVia.getUnderlyingType();
// Find the first upper bound that isn't a wildcard or type variable
while (declaringType.getKind() == TypeKind.WILDCARD
|| declaringType.getKind() == TypeKind.TYPEVAR) {
if (declaringType.getKind() == TypeKind.WILDCARD) {
declaringType = TypesUtils.wildUpperBound(declaringType, processingEnv);
} else if (declaringType.getKind() == TypeKind.TYPEVAR) {
declaringType = ((TypeVariable) declaringType).getUpperBound();
}
}
TypeElement typeElement = TypesUtils.getTypeElement(declaringType);
if (ElementUtils.enclosingTypeElement(field).equals(typeElement)) {
// If the field is declared in the accessedVia class, then the field in the invariant
// cannot be this field, even if the field has the same name.
return;
}
FieldInvariants invariants = getFieldInvariants(typeElement);
if (invariants == null) {
return;
}
List invariantAnnos = invariants.getQualifiersFor(field.getSimpleName());
type.replaceAnnotations(invariantAnnos);
}
/**
* Returns the field invariants for the given class, as expressed by the user in {@link
* FieldInvariant @FieldInvariant} method annotations.
*
* Subclasses may implement their own field invariant annotations if {@link
* FieldInvariant @FieldInvariant} is not expressive enough. They must override this method to
* properly create AnnotationMirror and also override {@link
* #getFieldInvariantDeclarationAnnotations()} to return their field invariants.
*
* @param element class for which to get invariants
* @return field invariants for {@code element}
*/
public FieldInvariants getFieldInvariants(TypeElement element) {
if (element == null) {
return null;
}
AnnotationMirror fieldInvarAnno = getDeclAnnotation(element, FieldInvariant.class);
if (fieldInvarAnno == null) {
return null;
}
List fields =
AnnotationUtils.getElementValueArray(
fieldInvarAnno, fieldInvariantFieldElement, String.class);
List<@CanonicalName Name> classes =
AnnotationUtils.getElementValueClassNames(fieldInvarAnno, fieldInvariantQualifierElement);
List qualifiers =
CollectionsPlume.mapList(
(Name name) ->
// Calling AnnotationBuilder.fromName (which ignores elements/fields) is acceptable
// because @FieldInvariant does not handle classes with elements/fields.
AnnotationBuilder.fromName(elements, name),
classes);
if (qualifiers.size() == 1) {
while (fields.size() > qualifiers.size()) {
qualifiers.add(qualifiers.get(0));
}
}
if (fields.size() != qualifiers.size()) {
// The user wrote a malformed @FieldInvariant annotation, so just return a malformed
// FieldInvariants object. The BaseTypeVisitor will issue an error.
return new FieldInvariants(fields, qualifiers);
}
// Only keep qualifiers that are supported by this checker. (The other qualifiers cannot
// be checked by this checker, so they must be ignored.)
List annotatedFields = new ArrayList<>();
List supportedQualifiers = new ArrayList<>();
for (int i = 0; i < fields.size(); i++) {
if (isSupportedQualifier(qualifiers.get(i))) {
annotatedFields.add(fields.get(i));
supportedQualifiers.add(qualifiers.get(i));
}
}
if (annotatedFields.isEmpty()) {
return null;
}
return new FieldInvariants(annotatedFields, supportedQualifiers);
}
/**
* Returns the AnnotationTree which is a use of one of the field invariant annotations (as
* specified via {@link #getFieldInvariantDeclarationAnnotations()}. If one isn't found, null is
* returned.
*
* @param annoTrees list of trees to search; the result is one of the list elements, or null
* @return the AnnotationTree that is a use of one of the field invariant annotations, or null if
* one isn't found
*/
public AnnotationTree getFieldInvariantAnnotationTree(List annoTrees) {
List annos = TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees);
for (int i = 0; i < annos.size(); i++) {
for (Class clazz : getFieldInvariantDeclarationAnnotations()) {
if (areSameByClass(annos.get(i), clazz)) {
return annoTrees.get(i);
}
}
}
return null;
}
/** Returns the set of classes of field invariant annotations. */
protected Set> getFieldInvariantDeclarationAnnotations() {
return Collections.singleton(FieldInvariant.class);
}
/**
* Adapt the upper bounds of the type variables of a class relative to the type instantiation. In
* some type systems, the upper bounds depend on the instantiation of the class. For example, in
* the Generic Universe Type system, consider a class declaration
*
* {@code class C }
*
* then the instantiation
*
* {@code @Rep C<@Rep Object> }
*
* is legal. The upper bounds of class C have to be adapted by the main modifier.
*
* An example of an adaptation follows. Suppose, I have a declaration:
*
*
{@code class MyClass>}
*
* And an instantiation:
*
* {@code new MyClass<@NonNull String>()}
*
* The upper bound of E adapted to the argument String, would be {@code List<@NonNull String>}
* and the lower bound would be an AnnotatedNullType.
*
*
TODO: ensure that this method is consistently used instead of directly querying the type
* variables.
*
* @param type the use of the type
* @param element the corresponding element
* @return the adapted bounds of the type parameters
*/
public List typeVariablesFromUse(
AnnotatedDeclaredType type, TypeElement element) {
AnnotatedDeclaredType generic = getAnnotatedType(element);
List targs = type.getTypeArguments();
List tvars = generic.getTypeArguments();
assert targs.size() == tvars.size()
: "Mismatch in type argument size between " + type + " and " + generic;
// System.err.printf("TVFU%n type: %s%n generic: %s%n", type, generic);
Map typeParamToTypeArg = new HashMap<>();
AnnotatedDeclaredType enclosing = type;
while (enclosing != null) {
List enclosingTArgs = enclosing.getTypeArguments();
AnnotatedDeclaredType declaredType =
getAnnotatedType((TypeElement) enclosing.getUnderlyingType().asElement());
List enclosingTVars = declaredType.getTypeArguments();
for (int i = 0; i < enclosingTArgs.size(); i++) {
AnnotatedTypeVariable enclosingTVar = (AnnotatedTypeVariable) enclosingTVars.get(i);
typeParamToTypeArg.put(enclosingTVar.getUnderlyingType(), enclosingTArgs.get(i));
}
enclosing = enclosing.getEnclosingType();
}
List res = new ArrayList<>(tvars.size());
for (AnnotatedTypeMirror atm : tvars) {
AnnotatedTypeVariable atv = (AnnotatedTypeVariable) atm;
AnnotatedTypeMirror upper =
typeVarSubstitutor.substitute(typeParamToTypeArg, atv.getUpperBound());
AnnotatedTypeMirror lower =
typeVarSubstitutor.substitute(typeParamToTypeArg, atv.getLowerBound());
res.add(new AnnotatedTypeParameterBounds(upper, lower));
}
return res;
}
/**
* Creates and returns an AnnotatedNullType qualified with {@code annotations}.
*
* @param annotations set of AnnotationMirrors to qualify the returned type with
* @return AnnotatedNullType qualified with {@code annotations}
*/
public AnnotatedNullType getAnnotatedNullType(Set annotations) {
final AnnotatedTypeMirror.AnnotatedNullType nullType =
(AnnotatedNullType) toAnnotatedType(processingEnv.getTypeUtils().getNullType(), false);
nullType.addAnnotations(annotations);
return nullType;
}
// **********************************************************************
// Utilities method for getting specific types from trees or elements
// **********************************************************************
/**
* Return the implicit receiver type of an expression tree.
*
* The result is null for expressions that don't have a receiver, e.g. for a local variable or
* method parameter access. The result is also null for expressions that have an explicit
* receiver.
*
*
Clients should generally call {@link #getReceiverType}.
*
* @param tree the expression that might have an implicit receiver
* @return the type of the implicit receiver
*/
protected @Nullable AnnotatedDeclaredType getImplicitReceiverType(ExpressionTree tree) {
assert (tree.getKind() == Tree.Kind.IDENTIFIER
|| tree.getKind() == Tree.Kind.MEMBER_SELECT
|| tree.getKind() == Tree.Kind.METHOD_INVOCATION
|| tree.getKind() == Tree.Kind.NEW_CLASS)
: "Unexpected tree kind: " + tree.getKind();
// Return null if the element kind has no receiver.
Element element = TreeUtils.elementFromTree(tree);
assert element != null : "Unexpected null element for tree: " + tree;
if (!ElementUtils.hasReceiver(element)) {
return null;
}
// Return null if the receiver is explicit.
if (TreeUtils.getReceiverTree(tree) != null) {
return null;
}
TypeElement elementOfImplicitReceiver = ElementUtils.enclosingTypeElement(element);
if (tree.getKind() == Tree.Kind.NEW_CLASS) {
if (elementOfImplicitReceiver.getEnclosingElement() != null) {
elementOfImplicitReceiver =
ElementUtils.enclosingTypeElement(elementOfImplicitReceiver.getEnclosingElement());
} else {
elementOfImplicitReceiver = null;
}
if (elementOfImplicitReceiver == null) {
// If the typeElt does not have an enclosing class, then the NewClassTree
// does not have an implicit receiver.
return null;
}
}
TypeMirror typeOfImplicitReceiver = elementOfImplicitReceiver.asType();
AnnotatedDeclaredType thisType = getSelfType(tree);
if (thisType == null) {
return null;
}
// An implicit receiver is the first enclosing type that is a subtype of the type where
// element is declared.
while (!isSubtype(thisType.getUnderlyingType(), typeOfImplicitReceiver)) {
thisType = thisType.getEnclosingType();
}
return thisType;
}
/**
* Returns the type of {@code this} at the location of {@code tree}. Returns {@code null} if
* {@code tree} is in a location where {@code this} has no meaning, such as the body of a static
* method.
*
*
The parameter is an arbitrary tree and does not have to mention "this", neither explicitly
* nor implicitly. This method can be overridden for type-system specific behavior.
*
* @param tree location used to decide the type of {@code this}
* @return the type of {@code this} at the location of {@code tree}
*/
public @Nullable AnnotatedDeclaredType getSelfType(Tree tree) {
if (TreeUtils.isClassTree(tree)) {
return getAnnotatedType(TreeUtils.elementFromDeclaration((ClassTree) tree));
}
Tree enclosingTree = getEnclosingClassOrMethod(tree);
if (enclosingTree == null) {
// tree is inside an annotation, where "this" is not allowed. So, no self type exists.
return null;
} else if (enclosingTree.getKind() == Tree.Kind.METHOD) {
MethodTree enclosingMethod = (MethodTree) enclosingTree;
if (TreeUtils.isConstructor(enclosingMethod)) {
return (AnnotatedDeclaredType) getAnnotatedType(enclosingMethod).getReturnType();
} else {
return getAnnotatedType(enclosingMethod).getReceiverType();
}
} else if (TreeUtils.isClassTree(enclosingTree)) {
return (AnnotatedDeclaredType) getAnnotatedType(enclosingTree);
}
return null;
}
/** A set containing class, method, and annotation tree kinds. */
private static final Set classMethodAnnotationKinds =
EnumSet.copyOf(TreeUtils.classTreeKinds());
static {
classMethodAnnotationKinds.add(Tree.Kind.METHOD);
classMethodAnnotationKinds.add(Tree.Kind.TYPE_ANNOTATION);
classMethodAnnotationKinds.add(Tree.Kind.ANNOTATION);
}
/**
* Returns the innermost enclosing method or class tree of {@code tree}. If {@code tree} is
* artificial (that is, created by dataflow), then {@link #artificialTreeToEnclosingElementMap} is
* used to find the enclosing tree.
*
* If the tree is inside an annotation, then {@code null} is returned.
*
* @param tree tree to whose innermost enclosing method or class to return
* @return the innermost enclosing method or class tree of {@code tree}, or {@code null} if {@code
* tree} is inside an annotation
*/
public @Nullable Tree getEnclosingClassOrMethod(Tree tree) {
TreePath path = getPath(tree);
Tree enclosing = TreePathUtil.enclosingOfKind(path, classMethodAnnotationKinds);
if (enclosing != null) {
if (enclosing.getKind() == Tree.Kind.ANNOTATION
|| enclosing.getKind() == Tree.Kind.TYPE_ANNOTATION) {
return null;
}
return enclosing;
}
Element e = getEnclosingElementForArtificialTree(tree);
if (e != null) {
Element enclosingMethodOrClass = e;
while (enclosingMethodOrClass != null
&& enclosingMethodOrClass.getKind() != ElementKind.METHOD
&& !enclosingMethodOrClass.getKind().isClass()
&& !enclosingMethodOrClass.getKind().isInterface()) {
enclosingMethodOrClass = enclosingMethodOrClass.getEnclosingElement();
}
return declarationFromElement(enclosingMethodOrClass);
}
return TreePathUtil.enclosingClass(path);
}
/**
* Returns the {@link AnnotatedTypeMirror} of the enclosing type at the location of {@code tree}
* that is the same type as {@code typeElement}.
*
* @param typeElement type of the enclosing type to return
* @param tree location to use
* @return the enclosing type at the location of {@code tree} that is the same type as {@code
* typeElement}
*/
public AnnotatedDeclaredType getEnclosingType(TypeElement typeElement, Tree tree) {
AnnotatedDeclaredType thisType = getSelfType(tree);
while (!isSameType(thisType.getUnderlyingType(), typeElement.asType())) {
thisType = thisType.getEnclosingType();
}
return thisType;
}
/**
* Returns true if the erasure of {@code type1} is a subtype of the erasure of {@code type2}.
*
* @param type1 a type
* @param type2 a type
* @return true if the erasure of {@code type1} is a subtype of the erasure of {@code type2}
*/
private boolean isSubtype(TypeMirror type1, TypeMirror type2) {
return types.isSubtype(types.erasure(type1), types.erasure(type2));
}
/**
* Returns true if the erasure of {@code type1} is the same type as the erasure of {@code type2}.
*
* @param type1 a type
* @param type2 a type
* @return true if the erasure of {@code type1} is the same type as the erasure of {@code type2}
*/
private boolean isSameType(TypeMirror type1, TypeMirror type2) {
return types.isSameType(types.erasure(type1), types.erasure(type2));
}
/**
* Returns the receiver type of the expression tree, which might be the type of an implicit {@code
* this}. Returns null if the expression has no explicit or implicit receiver.
*
* @param expression the expression for which to determine the receiver type
* @return the type of the receiver of expression
*/
public final AnnotatedTypeMirror getReceiverType(ExpressionTree expression) {
ExpressionTree receiver = TreeUtils.getReceiverTree(expression);
if (receiver != null) {
return getAnnotatedType(receiver);
}
Element element = TreeUtils.elementFromUse(expression);
if (element != null && ElementUtils.hasReceiver(element)) {
// The tree references an element that has a receiver, but the tree does not have an explicit
// receiver. So, the tree must have an implicit receiver of "this" or "Outer.this".
return getImplicitReceiverType(expression);
} else {
return null;
}
}
/** The type for an instantiated generic method or constructor. */
public static class ParameterizedExecutableType {
/** The method's/constructor's type. */
public final AnnotatedExecutableType executableType;
/** The types of the generic type arguments. */
public final List typeArgs;
/** Create a ParameterizedExecutableType. */
public ParameterizedExecutableType(
AnnotatedExecutableType executableType, List typeArgs) {
this.executableType = executableType;
this.typeArgs = typeArgs;
}
@Override
public String toString() {
if (typeArgs.isEmpty()) {
return executableType.toString();
} else {
StringJoiner typeArgsString = new StringJoiner(",", "<", ">");
for (AnnotatedTypeMirror atm : typeArgs) {
typeArgsString.add(atm.toString());
}
return typeArgsString + " " + executableType.toString();
}
}
}
/**
* Determines the type of the invoked method based on the passed method invocation tree.
*
* The returned method type has all type variables resolved, whether based on receiver type,
* passed type parameters if any, and method invocation parameter.
*
*
Subclasses may override this method to customize inference of types or qualifiers based on
* method invocation parameters.
*
*
As an implementation detail, this method depends on {@link AnnotatedTypes#asMemberOf(Types,
* AnnotatedTypeFactory, AnnotatedTypeMirror, Element)}, and customization based on receiver type
* should be in accordance to its specification.
*
*
The return type is a pair of the type of the invoked method and the (inferred) type
* arguments. Note that neither the explicitly passed nor the inferred type arguments are
* guaranteed to be subtypes of the corresponding upper bounds. See method {@link
* org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments} for the checks of type
* argument well-formedness.
*
*
Note that "this" and "super" constructor invocations are also handled by this method
* (explicit or implicit ones, at the beginning of a constructor). Method {@link
* #constructorFromUse(NewClassTree)} is only used for a constructor invocation in a "new"
* expression.
*
* @param tree the method invocation tree
* @return the method type being invoked with tree and the (inferred) type arguments
*/
public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) {
ExecutableElement methodElt = TreeUtils.elementFromUse(tree);
AnnotatedTypeMirror receiverType = getReceiverType(tree);
if (receiverType == null && TreeUtils.isSuperConstructorCall(tree)) {
// super() calls don't have a receiver, but they should be view-point adapted as if
// "this" is the receiver.
receiverType = getSelfType(tree);
}
if (receiverType != null && receiverType.getKind() == TypeKind.DECLARED) {
receiverType = applyCaptureConversion(receiverType);
}
ParameterizedExecutableType result = methodFromUse(tree, methodElt, receiverType);
if (checker.shouldResolveReflection()
&& reflectionResolver.isReflectiveMethodInvocation(tree)) {
result = reflectionResolver.resolveReflectiveCall(this, tree, result);
}
AnnotatedExecutableType method = result.executableType;
if (method.getReturnType().getKind() == TypeKind.WILDCARD
&& ((AnnotatedWildcardType) method.getReturnType()).isUninferredTypeArgument()) {
// Get the correct Java type from the tree and use it as the upper bound of the wildcard.
TypeMirror tm = TreeUtils.typeOf(tree);
AnnotatedTypeMirror t = toAnnotatedType(tm, false);
AnnotatedWildcardType wildcard = (AnnotatedWildcardType) method.getReturnType();
if (ignoreUninferredTypeArguments) {
// Remove the annotations so that default annotations are used instead.
// (See call to addDefaultAnnotations below.)
t.clearPrimaryAnnotations();
} else {
t.replaceAnnotations(wildcard.getExtendsBound().getAnnotations());
}
wildcard.setExtendsBound(t);
addDefaultAnnotations(wildcard);
}
return result;
}
/**
* Determines the type of the invoked method based on the passed expression tree, executable
* element, and receiver type.
*
* @param tree either a MethodInvocationTree or a MemberReferenceTree
* @param methodElt the element of the referenced method
* @param receiverType the type of the receiver
* @return the method type being invoked with tree and the (inferred) type arguments
* @see #methodFromUse(MethodInvocationTree)
*/
public ParameterizedExecutableType methodFromUse(
ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) {
AnnotatedExecutableType memberTypeWithoutOverrides =
getAnnotatedType(methodElt); // get unsubstituted type
AnnotatedExecutableType memberTypeWithOverrides =
applyFakeOverrides(receiverType, methodElt, memberTypeWithoutOverrides);
memberTypeWithOverrides = applyRecordTypesToAccessors(methodElt, memberTypeWithOverrides);
methodFromUsePreSubstitution(tree, memberTypeWithOverrides);
AnnotatedExecutableType methodType =
AnnotatedTypes.asMemberOf(types, this, receiverType, methodElt, memberTypeWithOverrides);
List typeargs = new ArrayList<>(methodType.getTypeVariables().size());
Map typeParamToTypeArg =
AnnotatedTypes.findTypeArguments(processingEnv, this, tree, methodElt, methodType);
if (!typeParamToTypeArg.isEmpty()) {
typeParamToTypeArg =
captureMethodTypeArgs(typeParamToTypeArg, memberTypeWithOverrides.getTypeVariables());
for (AnnotatedTypeVariable tv : methodType.getTypeVariables()) {
if (typeParamToTypeArg.get(tv.getUnderlyingType()) == null) {
throw new BugInCF(
"AnnotatedTypeFactory.methodFromUse:mismatch between declared method type variables"
+ " and the inferred method type arguments. Method type variables: "
+ methodType.getTypeVariables()
+ "; "
+ "Inferred method type arguments: "
+ typeParamToTypeArg);
}
typeargs.add(typeParamToTypeArg.get(tv.getUnderlyingType()));
}
methodType =
(AnnotatedExecutableType) typeVarSubstitutor.substitute(typeParamToTypeArg, methodType);
}
if (tree.getKind() == Tree.Kind.METHOD_INVOCATION
&& TreeUtils.isMethodInvocation(tree, objectGetClass, processingEnv)) {
adaptGetClassReturnTypeToReceiver(methodType, receiverType, tree);
}
return new ParameterizedExecutableType(methodType, typeargs);
}
/**
* Apply capture conversion to the type arguments of a method invocation.
*
* @param typeVarToAnnotatedTypeArg mapping from type variable in the method declaration to the
* corresponding (annotated) type argument at the method invocation
* @param declTypeVar list of the (annotated) type variable declarations in the method
* @return a mapping from type variable in the method declaration to its captured type argument.
* Its keys are the same as in {@code typeVarToAnnotatedTypeArg}, and the values are their
* captures (for a non-wildcard, capture conversion is the identity).
*/
// TODO: This should happen as part of Java 8 inference and this method should be removed when
// #979 is fixed.
private Map captureMethodTypeArgs(
Map typeVarToAnnotatedTypeArg,
List declTypeVar) {
Map typeParameter = new HashMap<>();
for (AnnotatedTypeVariable t : declTypeVar) {
typeParameter.put(t.getUnderlyingType(), t);
}
// `newTypeVarToAnnotatedTypeArg` is the result of this method.
Map newTypeVarToAnnotatedTypeArg = new HashMap<>();
Map capturedTypeVarToAnnotatedTypeVar = new HashMap<>();
// The first loop replaces each wildcard by a fresh type variable.
for (Map.Entry entry :
typeVarToAnnotatedTypeArg.entrySet()) {
TypeVariable typeVariable = entry.getKey();
AnnotatedTypeMirror originalTypeArg = entry.getValue();
if (originalTypeArg.containsUninferredTypeArguments()) {
// Don't capture uninferred type arguments; return the argument.
return typeVarToAnnotatedTypeArg;
}
if (originalTypeArg.getKind() == TypeKind.WILDCARD) {
TypeMirror cap =
TypesUtils.freshTypeVariable(originalTypeArg.getUnderlyingType(), processingEnv);
AnnotatedTypeMirror capturedArg = AnnotatedTypeMirror.createType(cap, this, false);
newTypeVarToAnnotatedTypeArg.put(typeVariable, capturedArg);
capturedTypeVarToAnnotatedTypeVar.put(
(TypeVariable) cap, (AnnotatedTypeVariable) capturedArg);
} else {
newTypeVarToAnnotatedTypeArg.put(typeVariable, originalTypeArg);
}
}
// The second loop captures: it side-effects the new type variables.
List order = TypesUtils.order(typeVarToAnnotatedTypeArg.keySet(), types);
for (TypeVariable typeVariable : order) {
AnnotatedTypeMirror originalTypeArg = typeVarToAnnotatedTypeArg.get(typeVariable);
AnnotatedTypeMirror newTypeArg = newTypeVarToAnnotatedTypeArg.get(typeVariable);
if (TypesUtils.isCapturedTypeVariable(newTypeArg.getUnderlyingType())
&& originalTypeArg.getKind() == TypeKind.WILDCARD) {
annotateCapturedTypeVar(
newTypeVarToAnnotatedTypeArg,
capturedTypeVarToAnnotatedTypeVar,
(AnnotatedWildcardType) originalTypeArg,
typeParameter.get(typeVariable),
(AnnotatedTypeVariable) newTypeArg);
}
}
return newTypeVarToAnnotatedTypeArg;
}
/**
* Given a member and its type, returns the type with fake overrides applied to it.
*
* @param receiverType the type of the class that contains member (or a subtype of it)
* @param member a type member, such as a method or field
* @param memberType the type of {@code member}
* @return {@code memberType}, adjusted according to fake overrides
*/
private AnnotatedExecutableType applyFakeOverrides(
AnnotatedTypeMirror receiverType, Element member, AnnotatedExecutableType memberType) {
// Currently, handle only methods, not fields. TODO: Handle fields.
if (memberType.getKind() != TypeKind.EXECUTABLE) {
return memberType;
}
AnnotationFileElementTypes afet = stubTypes;
AnnotatedExecutableType methodType = afet.getFakeOverride(member, receiverType);
if (methodType == null) {
methodType = memberType;
}
return methodType;
}
/**
* Given a method, checks if there is: a record component with the same name AND the record
* component has an annotation AND the method has no-arguments. If so, replaces the annotations on
* the method return type with those from the record type in the same hierarchy.
*
* @param member a method or constructor
* @param memberType the type of the method/constructor; side-effected by this method
* @return {@code memberType} with annotations replaced if applicable
*/
private AnnotatedExecutableType applyRecordTypesToAccessors(
ExecutableElement member, AnnotatedExecutableType memberType) {
if (memberType.getKind() != TypeKind.EXECUTABLE) {
throw new BugInCF(
"member %s has type %s of kind %s", member, memberType, memberType.getKind());
}
stubTypes.injectRecordComponentType(types, member, memberType);
return memberType;
}
/**
* A callback method for the AnnotatedTypeFactory subtypes to customize the handling of the
* declared method type before type variable substitution.
*
* @param tree either a method invocation or a member reference tree
* @param type declared method type before type variable substitution
*/
protected void methodFromUsePreSubstitution(ExpressionTree tree, AnnotatedExecutableType type) {
assert tree instanceof MethodInvocationTree || tree instanceof MemberReferenceTree;
}
/**
* Java special-cases the return type of {@link java.lang.Class#getClass() getClass()}. Though the
* method has a return type of {@code Class}, the compiler special cases this return-type and
* changes the bound of the type argument to the erasure of the receiver type. For example:
*
*
* - {@code x.getClass()} has the type {@code Class< ? extends erasure_of_x >}
*
- {@code someInteger.getClass()} has the type {@code Class< ? extends Integer >}
*
*
* @param getClassType this must be a type representing a call to Object.getClass otherwise a
* runtime exception will be thrown. It is modified by side effect.
* @param receiverType the receiver type of the method invocation (not the declared receiver type)
* @param tree getClass method invocation tree
*/
protected void adaptGetClassReturnTypeToReceiver(
AnnotatedExecutableType getClassType, AnnotatedTypeMirror receiverType, ExpressionTree tree) {
TypeMirror type = TreeUtils.typeOf(tree);
AnnotatedTypeMirror returnType = AnnotatedTypeMirror.createType(type, this, false);
if (returnType == null
|| !(returnType.getKind() == TypeKind.DECLARED)
|| ((AnnotatedDeclaredType) returnType).getTypeArguments().size() != 1) {
throw new BugInCF(
"Unexpected type passed to AnnotatedTypes.adaptGetClassReturnTypeToReceiver%n"
+ "getClassType=%s%nreceiverType=%s",
getClassType, receiverType);
}
AnnotatedWildcardType classWildcardArg =
(AnnotatedWildcardType)
((AnnotatedDeclaredType) getClassType.getReturnType()).getTypeArguments().get(0);
getClassType.setReturnType(returnType);
// Usually, the only locations that will add annotations to the return type are getClass in stub
// files defaults and propagation tree annotator. Since getClass is final they cannot come from
// source code. Also, since the newBound is an erased type we have no type arguments. So, we
// just copy the annotations from the bound of the declared type to the new bound.
Set newAnnos = AnnotationUtils.createAnnotationSet();
Set typeBoundAnnos =
getTypeDeclarationBounds(receiverType.getErased().getUnderlyingType());
Set wildcardBoundAnnos = classWildcardArg.getExtendsBound().getAnnotations();
for (AnnotationMirror typeBoundAnno : typeBoundAnnos) {
AnnotationMirror wildcardAnno =
qualHierarchy.findAnnotationInSameHierarchy(wildcardBoundAnnos, typeBoundAnno);
if (qualHierarchy.isSubtype(typeBoundAnno, wildcardAnno)) {
newAnnos.add(typeBoundAnno);
} else {
newAnnos.add(wildcardAnno);
}
}
AnnotatedTypeMirror newTypeArg =
((AnnotatedDeclaredType) getClassType.getReturnType()).getTypeArguments().get(0);
((AnnotatedTypeVariable) newTypeArg).getUpperBound().replaceAnnotations(newAnnos);
}
/**
* Return the element type of {@code expression}. This is usually the type of {@code
* expression.itertor().next()}. If {@code expression} is an array, it is the component type of
* the array.
*
* @param expression an expression whose type is an array or implements {@link Iterable}
* @return the type of {@code expression.itertor().next()} or if {@code expression} is an array,
* the component type of the array.
*/
public AnnotatedTypeMirror getIterableElementType(ExpressionTree expression) {
return getIterableElementType(expression, getAnnotatedType(expression));
}
/**
* Return the element type of {@code iterableType}. This is usually the type of {@code
* expression.itertor().next()}. If {@code expression} is an array, it is the component type of
* the array.
*
* @param expression an expression whose type is an array or implements {@link Iterable}
* @param iterableType the type of the expression
* @return the type of {@code expression.itertor().next()} or if {@code expression} is an array,
* the component type of the array.
*/
protected AnnotatedTypeMirror getIterableElementType(
ExpressionTree expression, AnnotatedTypeMirror iterableType) {
switch (iterableType.getKind()) {
case ARRAY:
return ((AnnotatedArrayType) iterableType).getComponentType();
case WILDCARD:
return getIterableElementType(
expression, ((AnnotatedWildcardType) iterableType).getExtendsBound().deepCopy());
case TYPEVAR:
return getIterableElementType(
expression, ((AnnotatedTypeVariable) iterableType).getUpperBound());
case DECLARED:
AnnotatedDeclaredType dt =
AnnotatedTypes.asSuper(this, iterableType, this.iterableDeclType);
if (dt.getTypeArguments().isEmpty()) {
TypeElement e = ElementUtils.getTypeElement(processingEnv, Object.class);
return getAnnotatedType(e);
} else {
return dt.getTypeArguments().get(0);
}
// TODO: Properly desugar Iterator.next(), which is needed if an annotated JDK has
// annotations on Iterator#next.
// The below doesn't work because methodFromUse() assumes that the expression tree
// matches the method element.
// TypeElement iteratorElement =
// ElementUtils.getTypeElement(processingEnv, Iterator.class);
// AnnotatedTypeMirror iteratorType =
// AnnotatedTypeMirror.createType(iteratorElement.asType(), this, false);
// Map mapping = new HashMap<>();
// mapping.put(
// (TypeVariable) iteratorElement.getTypeParameters().get(0).asType(),
// typeArg);
// iteratorType = typeVarSubstitutor.substitute(mapping, iteratorType);
// ExecutableElement next =
// TreeUtils.getMethod("java.util.Iterator", "next", 0, processingEnv);
// ParameterizedExecutableType m = methodFromUse(expression, next, iteratorType);
// return m.executableType.getReturnType();
default:
throw new BugInCF(
"AnnotatedTypeFactory.getIterableElementType: not iterable type: " + iterableType);
}
}
/**
* Determines the type of the invoked constructor based on the passed new class tree.
*
* The returned method type has all type variables resolved, whether based on receiver type,
* passed type parameters if any, and constructor invocation parameter.
*
*
Subclasses may override this method to customize inference of types or qualifiers based on
* constructor invocation parameters.
*
*
As an implementation detail, this method depends on {@link AnnotatedTypes#asMemberOf(Types,
* AnnotatedTypeFactory, AnnotatedTypeMirror, Element)}, and customization based on receiver type
* should be in accordance with its specification.
*
*
The return type is a pair of the type of the invoked constructor and the (inferred) type
* arguments. Note that neither the explicitly passed nor the inferred type arguments are
* guaranteed to be subtypes of the corresponding upper bounds. See method {@link
* org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments} for the checks of type
* argument well-formedness.
*
*
Note that "this" and "super" constructor invocations are handled by method {@link
* #methodFromUse}. This method only handles constructor invocations in a "new" expression.
*
* @param tree the constructor invocation tree
* @return the annotated type of the invoked constructor (as an executable type) and the
* (inferred) type arguments
*/
public ParameterizedExecutableType constructorFromUse(NewClassTree tree) {
// Get the annotations written on the new class tree.
AnnotatedDeclaredType type =
(AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(tree), false);
if (!TreeUtils.isDiamondTree(tree)) {
if (tree.getClassBody() == null) {
type.setTypeArguments(getExplicitNewClassClassTypeArgs(tree));
}
} else {
type = getAnnotatedType(TypesUtils.getTypeElement(type.underlyingType));
// Add explicit annotations below.
type.clearPrimaryAnnotations();
}
Set explicitAnnos = getExplicitNewClassAnnos(tree);
type.addAnnotations(explicitAnnos);
// Get the enclosing type of the constructor, if one exists.
// this.new InnerClass()
AnnotatedDeclaredType enclosingType = (AnnotatedDeclaredType) getReceiverType(tree);
type.setEnclosingType(enclosingType);
// Add computed annotations to the type.
addComputedTypeAnnotations(tree, type);
ExecutableElement ctor = TreeUtils.constructor(tree);
AnnotatedExecutableType con = getAnnotatedType(ctor); // get unsubstituted type
constructorFromUsePreSubstitution(tree, con);
if (tree.getClassBody() != null) {
// Because the anonymous constructor can't have explicit annotations on its parameters, they
// are copied from the super constructor invoked in the anonymous constructor. To do this:
// 1. get unsubstituted type of the super constructor.
// 2. adapt it to this call site.
// 3. copy the parameters to the anonymous constructor, `con`.
// 4. copy annotations on the return type to `con`.
AnnotatedExecutableType superCon = getAnnotatedType(TreeUtils.getSuperConstructor(tree));
constructorFromUsePreSubstitution(tree, superCon);
superCon = AnnotatedTypes.asMemberOf(types, this, type, superCon.getElement(), superCon);
if (superCon.getParameterTypes().size() == con.getParameterTypes().size()) {
con.setParameterTypes(superCon.getParameterTypes());
} else {
// If the super class of the anonymous class has an enclosing type, then it is the first
// parameter of the anonymous constructor. For example,
// class Outer { class Inner {} }
// new Inner(){};
// Then javac creates the following constructor:
// (.Outer x0) {
// x0.super();
// }
// So the code below deals with this.
List p = new ArrayList<>(superCon.getParameterTypes().size() + 1);
if (TreeUtils.hasSyntheticArgument(tree)) {
p.add(con.getParameterTypes().get(0));
} else if (con.receiverType != null) {
p.add(con.receiverType);
} else {
p.add(con.paramTypes.get(0));
}
p.addAll(1, superCon.getParameterTypes());
con.setParameterTypes(p);
}
con.getReturnType().replaceAnnotations(superCon.getReturnType().getAnnotations());
} else {
con = AnnotatedTypes.asMemberOf(types, this, type, ctor, con);
}
Map typeParamToTypeArg =
new HashMap<>(AnnotatedTypes.findTypeArguments(processingEnv, this, tree, ctor, con));
List typeargs;
if (typeParamToTypeArg.isEmpty()) {
typeargs = Collections.emptyList();
} else {
typeargs =
CollectionsPlume.mapList(
(AnnotatedTypeVariable tv) -> typeParamToTypeArg.get(tv.getUnderlyingType()),
con.getTypeVariables());
}
if (TreeUtils.isDiamondTree(tree)) {
// TODO: This should be done at the same time as type argument inference.
List classTypeArgs = inferDiamondType(tree);
int i = 0;
for (AnnotatedTypeMirror typeParam : type.getTypeArguments()) {
typeParamToTypeArg.put((TypeVariable) typeParam.getUnderlyingType(), classTypeArgs.get(i));
i++;
}
}
con = (AnnotatedExecutableType) typeVarSubstitutor.substitute(typeParamToTypeArg, con);
stubTypes.injectRecordComponentType(types, ctor, con);
if (enclosingType != null) {
// Reset the enclosing type because it can be substituted incorrectly.
((AnnotatedDeclaredType) con.getReturnType()).setEnclosingType(enclosingType);
}
return new ParameterizedExecutableType(con, typeargs);
}
/**
* Creates an AnnotatedDeclaredType for a NewClassTree. Only adds explicit annotations, unless
* newClassTree has a diamond operator. In that case, the annotations on the type arguments are
* inferred using the assignment context and contain defaults.
*
* (Subclass beside {@link GenericAnnotatedTypeFactory} should not override this method.)
*
* @param newClassTree NewClassTree
* @return AnnotatedDeclaredType
* @deprecated Use {@link #getExplicitNewClassAnnos(NewClassTree)}, {@link
* #getExplicitNewClassClassTypeArgs(NewClassTree)}, or {@link #getAnnotatedType(ClassTree)}
* instead.
*/
@Deprecated // This should be removed when the #979 is fixed and the remain use is removed.
public AnnotatedDeclaredType fromNewClass(NewClassTree newClassTree) {
AnnotatedDeclaredType type =
(AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(newClassTree), false);
if (!TreeUtils.isDiamondTree(newClassTree)) {
if (newClassTree.getClassBody() == null) {
type.setTypeArguments(getExplicitNewClassClassTypeArgs(newClassTree));
}
} else {
assert TreeUtils.isDiamondTree(newClassTree) : "Expected diamond new class tree";
TreePath p = getPath(newClassTree);
AnnotatedTypeMirror ctxtype = TypeArgInferenceUtil.assignedTo(this, p);
if (ctxtype != null && ctxtype.getKind() == TypeKind.DECLARED) {
AnnotatedDeclaredType adctx = (AnnotatedDeclaredType) ctxtype;
if (type.getTypeArguments().size() == adctx.getTypeArguments().size()) {
// Try to simply take the type arguments from LHS.
List oldArgs = type.getTypeArguments();
List newArgs = adctx.getTypeArguments();
for (int i = 0; i < type.getTypeArguments().size(); ++i) {
if (!types.isSubtype(newArgs.get(i).underlyingType, oldArgs.get(i).underlyingType)) {
// One of the underlying types doesn't match. Give up.
newArgs = oldArgs;
break;
}
}
type.setTypeArguments(newArgs);
}
}
}
Set explicitAnnos = getExplicitNewClassAnnos(newClassTree);
// Type may already have explicit dependent type annotations that have not yet been vpa.
type.clearPrimaryAnnotations();
type.addAnnotations(explicitAnnos);
return type;
}
/**
* Returns the annotations explicitly written on a NewClassTree.
*
* {@code new @HERE Class()}
*
* @param newClassTree a constructor invocation
* @return the annotations explicitly written on a NewClassTree
*/
public Set getExplicitNewClassAnnos(NewClassTree newClassTree) {
if (newClassTree.getClassBody() != null) {
// In Java 17+, the annotations are on the identifier, so copy them.
AnnotatedTypeMirror identifierType = fromTypeTree(newClassTree.getIdentifier());
// In Java 11 and lower, if newClassTree creates an anonymous class, then annotations in this
// location:
// new @HERE Class() {}
// are on not on the identifier newClassTree, but rather on the modifier newClassTree.
List annoTrees =
newClassTree.getClassBody().getModifiers().getAnnotations();
// Add the annotations to an AnnotatedTypeMirror removes the annotations that are not
// supported by this type system.
identifierType.addAnnotations(TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees));
return identifierType.getAnnotations();
} else {
return fromTypeTree(newClassTree.getIdentifier()).getAnnotations();
}
}
/**
* Returns the partially-annotated explicit class type arguments of the new class tree. The {@code
* AnnotatedTypeMirror} only include the annotations explicitly written on the explict type
* arguments. (If {@code newClass} use a diamond operator, this method returns the empty list.)
* For example, when called with {@code new MyClass<@HERE String>()} this method would return a
* list containing {@code @HERE String}.
*
* @param newClass a new class tree
* @return the partially annotated {@code AnnotatedTypeMirror}s for the (explicit) class type
* arguments of the new class tree
*/
protected List getExplicitNewClassClassTypeArgs(NewClassTree newClass) {
if (!TreeUtils.isDiamondTree(newClass)) {
return ((AnnotatedDeclaredType) fromTypeTree(newClass.getIdentifier())).getTypeArguments();
}
return Collections.emptyList();
}
/**
* Infer the class type arguments for the diamond operator.
*
* If {@code newClassTree} is assigned to the same type (not a supertype), then the type
* arguments are inferred to be the same as the assignment. Otherwise, the type arguments are
* annotated by {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}.
*
* @param newClassTree a diamond new class tree
* @return the class type arguments for {@code newClassTree}
*/
private List inferDiamondType(NewClassTree newClassTree) {
assert TreeUtils.isDiamondTree(newClassTree) : "Expected diamond new class tree";
AnnotatedDeclaredType diamondType =
(AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(newClassTree), false);
TreePath p = getPath(newClassTree);
AnnotatedTypeMirror ctxtype = TypeArgInferenceUtil.assignedTo(this, p);
if (ctxtype != null && ctxtype.getKind() == TypeKind.DECLARED) {
AnnotatedDeclaredType adctx = (AnnotatedDeclaredType) ctxtype;
if (diamondType.getTypeArguments().size() == adctx.getTypeArguments().size()) {
// Try to simply take the type arguments from LHS.
List oldArgs = diamondType.getTypeArguments();
List newArgs = adctx.getTypeArguments();
boolean useLhs = true;
for (int i = 0; i < diamondType.getTypeArguments().size(); ++i) {
if (!types.isSubtype(newArgs.get(i).underlyingType, oldArgs.get(i).underlyingType)) {
// One of the underlying types doesn't match. Give up.
useLhs = false;
break;
}
}
if (useLhs) {
return newArgs;
}
}
}
addComputedTypeAnnotations(newClassTree, diamondType);
return diamondType.getTypeArguments();
}
/**
* A callback method for the AnnotatedTypeFactory subtypes to customize the handling of the
* declared constructor type before type variable substitution.
*
* @param tree a NewClassTree from constructorFromUse()
* @param type declared method type before type variable substitution
*/
protected void constructorFromUsePreSubstitution(
NewClassTree tree, AnnotatedExecutableType type) {}
/**
* Returns the return type of the method {@code m}.
*
* @param m tree of a method declaration
* @return the return type of the method
*/
public AnnotatedTypeMirror getMethodReturnType(MethodTree m) {
AnnotatedExecutableType methodType = getAnnotatedType(m);
AnnotatedTypeMirror ret = methodType.getReturnType();
return ret;
}
/**
* Returns the return type of the method {@code m} at the return statement {@code r}. This
* implementation just calls {@link #getMethodReturnType(MethodTree)}, but subclasses may override
* this method to change the type based on the return statement.
*
* @param m tree of a method declaration
* @param r a return statement within method {@code m}
* @return the return type of the method {@code m} at the return statement {@code r}
*/
public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) {
return getMethodReturnType(m);
}
/**
* Returns the annotated boxed type of the given primitive type. The returned type would only have
* the annotations on the given type.
*
* Subclasses may override this method safely to override this behavior.
*
* @param type the primitive type
* @return the boxed declared type of the passed primitive type
*/
public AnnotatedDeclaredType getBoxedType(AnnotatedPrimitiveType type) {
TypeElement typeElt = types.boxedClass(type.getUnderlyingType());
AnnotatedDeclaredType dt = fromElement(typeElt).asUse();
dt.addAnnotations(type.getAnnotations());
return dt;
}
/**
* Return a primitive type: either the argument, or the result of unboxing it (which might affect
* its annotations).
*
*
Subclasses should override {@link #getUnboxedType} rather than this method.
*
* @param type a type: a primitive or boxed primitive
* @return the unboxed variant of the type
*/
public final AnnotatedPrimitiveType applyUnboxing(AnnotatedTypeMirror type) {
TypeMirror underlying = type.getUnderlyingType();
if (TypesUtils.isPrimitive(underlying)) {
return (AnnotatedPrimitiveType) type;
} else if (TypesUtils.isBoxedPrimitive(underlying)) {
return getUnboxedType((AnnotatedDeclaredType) type);
} else {
throw new BugInCF("Bad argument to applyUnboxing: " + type);
}
}
/**
* Returns the annotated primitive type of the given declared type if it is a boxed declared type.
* Otherwise, it throws IllegalArgumentException exception.
*
*
In the {@code AnnotatedTypeFactory} implementation, the returned type has the same primary
* annotations as the given type. Subclasses may override this behavior.
*
* @param type the declared type
* @return the unboxed primitive type
* @throws IllegalArgumentException if the type given has no unbox conversion
*/
public AnnotatedPrimitiveType getUnboxedType(AnnotatedDeclaredType type)
throws IllegalArgumentException {
PrimitiveType primitiveType = types.unboxedType(type.getUnderlyingType());
AnnotatedPrimitiveType pt =
(AnnotatedPrimitiveType) AnnotatedTypeMirror.createType(primitiveType, this, false);
pt.addAnnotations(type.getAnnotations());
return pt;
}
/**
* Returns AnnotatedDeclaredType with underlying type String and annotations copied from type.
* Subclasses may change the annotations.
*
* @param type type to convert to String
* @return AnnotatedTypeMirror that results from converting type to a String type
*/
// TODO: Test that this is called in all the correct locations
// See Issue #715
// https://github.com/typetools/checker-framework/issues/715
public AnnotatedDeclaredType getStringType(AnnotatedTypeMirror type) {
TypeMirror stringTypeMirror = TypesUtils.typeFromClass(String.class, types, elements);
AnnotatedDeclaredType stringATM =
(AnnotatedDeclaredType)
AnnotatedTypeMirror.createType(stringTypeMirror, this, type.isDeclaration());
stringATM.addAnnotations(type.getEffectiveAnnotations());
return stringATM;
}
/**
* Returns a widened type if applicable, otherwise returns its first argument.
*
*
Subclasses should override {@link #getWidenedAnnotations} rather than this method.
*
* @param exprType type to possibly widen
* @param widenedType type to possibly widen to; its annotations are ignored
* @return if widening is applicable, the result of converting {@code type} to the underlying type
* of {@code widenedType}; otherwise {@code type}
*/
public final AnnotatedTypeMirror getWidenedType(
AnnotatedTypeMirror exprType, AnnotatedTypeMirror widenedType) {
TypeKind exprKind = exprType.getKind();
TypeKind widenedKind = widenedType.getKind();
if (!TypeKindUtils.isNumeric(widenedKind)) {
// The target type is not a numeric primitive, so primitive widening is not applicable.
return exprType;
}
AnnotatedPrimitiveType exprPrimitiveType;
if (TypeKindUtils.isNumeric(exprKind)) {
exprPrimitiveType = (AnnotatedPrimitiveType) exprType;
} else if (TypesUtils.isNumericBoxed(exprType.getUnderlyingType())) {
exprPrimitiveType = getUnboxedType((AnnotatedDeclaredType) exprType);
} else {
return exprType;
}
switch (TypeKindUtils.getPrimitiveConversionKind(
exprPrimitiveType.getKind(), widenedType.getKind())) {
case WIDENING:
return getWidenedPrimitive(exprPrimitiveType, widenedType.getUnderlyingType());
case NARROWING:
return getNarrowedPrimitive(exprPrimitiveType, widenedType.getUnderlyingType());
case SAME:
return exprType;
default:
throw new BugInCF("unhandled PrimitiveConversionKind");
}
}
/**
* Applies widening if applicable, otherwise returns its first argument.
*
*
Subclasses should override {@link #getWidenedAnnotations} rather than this method.
*
* @param exprAnnos annotations to possibly widen
* @param exprTypeMirror type to possibly widen
* @param widenedType type to possibly widen to; its annotations are ignored
* @return if widening is applicable, the result of converting {@code type} to the underlying type
* of {@code widenedType}; otherwise {@code type}
*/
public final AnnotatedTypeMirror getWidenedType(
Set exprAnnos, TypeMirror exprTypeMirror, AnnotatedTypeMirror widenedType) {
AnnotatedTypeMirror exprType = toAnnotatedType(exprTypeMirror, false);
exprType.replaceAnnotations(exprAnnos);
return getWidenedType(exprType, widenedType);
}
/**
* Returns an AnnotatedPrimitiveType with underlying type {@code widenedTypeMirror} and with
* annotations copied or adapted from {@code type}.
*
* @param type type to widen; a primitive or boxed primitive
* @param widenedTypeMirror underlying type for the returned type mirror; a primitive or boxed
* primitive (same boxing as {@code type})
* @return result of converting {@code type} to {@code widenedTypeMirror}
*/
private AnnotatedPrimitiveType getWidenedPrimitive(
AnnotatedPrimitiveType type, TypeMirror widenedTypeMirror) {
AnnotatedPrimitiveType result =
(AnnotatedPrimitiveType)
AnnotatedTypeMirror.createType(widenedTypeMirror, this, type.isDeclaration());
result.addAnnotations(
getWidenedAnnotations(type.getAnnotations(), type.getKind(), result.getKind()));
return result;
}
/**
* Returns annotations applicable to type {@code narrowedTypeKind}, that are copied or adapted
* from {@code annos}.
*
* @param annos annotations to narrow, from a primitive or boxed primitive
* @param typeKind primitive type to narrow
* @param narrowedTypeKind target for the returned annotations; a primitive type that is narrower
* than {@code typeKind} (in the sense of JLS 5.1.3).
* @return result of converting {@code annos} from {@code typeKind} to {@code narrowedTypeKind}
*/
public Set getNarrowedAnnotations(
Set annos, TypeKind typeKind, TypeKind narrowedTypeKind) {
return annos;
}
/**
* Returns annotations applicable to type {@code widenedTypeKind}, that are copied or adapted from
* {@code annos}.
*
* @param annos annotations to widen, from a primitive or boxed primitive
* @param typeKind primitive type to widen
* @param widenedTypeKind target for the returned annotations; a primitive type that is wider than
* {@code typeKind} (in the sense of JLS 5.1.2)
* @return result of converting {@code annos} from {@code typeKind} to {@code widenedTypeKind}
*/
public Set getWidenedAnnotations(
Set annos, TypeKind typeKind, TypeKind widenedTypeKind) {
return annos;
}
/**
* Returns the types of the two arguments to the BinaryTree, accounting for widening and unboxing
* if applicable.
*
* @param node a binary tree
* @return the types of the two arguments
*/
public Pair binaryTreeArgTypes(BinaryTree node) {
return binaryTreeArgTypes(
getAnnotatedType(node.getLeftOperand()), getAnnotatedType(node.getRightOperand()));
}
/**
* Returns the types of the two arguments to the CompoundAssignmentTree, accounting for widening
* and unboxing if applicable.
*
* @param node a compound assignment tree
* @return the types of the two arguments
*/
public Pair compoundAssignmentTreeArgTypes(
CompoundAssignmentTree node) {
return binaryTreeArgTypes(
getAnnotatedType(node.getVariable()), getAnnotatedType(node.getExpression()));
}
/**
* Returns the types of the two arguments to a binary operation, accounting for widening and
* unboxing if applicable.
*
* @param left the type of the left argument of a binary operation
* @param right the type of the right argument of a binary operation
* @return the types of the two arguments
*/
public Pair binaryTreeArgTypes(
AnnotatedTypeMirror left, AnnotatedTypeMirror right) {
TypeKind resultTypeKind =
TypeKindUtils.widenedNumericType(left.getUnderlyingType(), right.getUnderlyingType());
if (TypeKindUtils.isNumeric(resultTypeKind)) {
TypeMirror resultTypeMirror = types.getPrimitiveType(resultTypeKind);
AnnotatedPrimitiveType leftUnboxed = applyUnboxing(left);
AnnotatedPrimitiveType rightUnboxed = applyUnboxing(right);
AnnotatedPrimitiveType leftWidened =
(leftUnboxed.getKind() == resultTypeKind
? leftUnboxed
: getWidenedPrimitive(leftUnboxed, resultTypeMirror));
AnnotatedPrimitiveType rightWidened =
(rightUnboxed.getKind() == resultTypeKind
? rightUnboxed
: getWidenedPrimitive(rightUnboxed, resultTypeMirror));
return Pair.of(leftWidened, rightWidened);
} else {
return Pair.of(left, right);
}
}
/**
* Returns AnnotatedPrimitiveType with underlying type {@code narrowedTypeMirror} and with
* annotations copied or adapted from {@code type}.
*
* Currently this method is called only for primitives that are narrowed at assignments from
* literal ints, for example, {@code byte b = 1;}. All other narrowing conversions happen at
* typecasts.
*
* @param type type to narrow
* @param narrowedTypeMirror underlying type for the returned type mirror
* @return result of converting {@code type} to {@code narrowedTypeMirror}
*/
public AnnotatedPrimitiveType getNarrowedPrimitive(
AnnotatedPrimitiveType type, TypeMirror narrowedTypeMirror) {
AnnotatedPrimitiveType narrowed =
(AnnotatedPrimitiveType)
AnnotatedTypeMirror.createType(narrowedTypeMirror, this, type.isDeclaration());
narrowed.addAnnotations(type.getAnnotations());
return narrowed;
}
// **********************************************************************
// random methods wrapping #getAnnotatedType(Tree) and #fromElement(Tree)
// with appropriate casts to reduce casts on the client side
// **********************************************************************
/**
* See {@link #getAnnotatedType(Tree)}.
*
* @see #getAnnotatedType(Tree)
*/
public final AnnotatedDeclaredType getAnnotatedType(ClassTree tree) {
return (AnnotatedDeclaredType) getAnnotatedType((Tree) tree);
}
/**
* See {@link #getAnnotatedType(Tree)}.
*
* @see #getAnnotatedType(Tree)
*/
public final AnnotatedDeclaredType getAnnotatedType(NewClassTree tree) {
return (AnnotatedDeclaredType) getAnnotatedType((Tree) tree);
}
/**
* See {@link #getAnnotatedType(Tree)}.
*
* @see #getAnnotatedType(Tree)
*/
public final AnnotatedArrayType getAnnotatedType(NewArrayTree tree) {
return (AnnotatedArrayType) getAnnotatedType((Tree) tree);
}
/**
* See {@link #getAnnotatedType(Tree)}.
*
* @see #getAnnotatedType(Tree)
*/
public final AnnotatedExecutableType getAnnotatedType(MethodTree tree) {
return (AnnotatedExecutableType) getAnnotatedType((Tree) tree);
}
/**
* See {@link #getAnnotatedType(Element)}.
*
* @see #getAnnotatedType(Element)
*/
public final AnnotatedDeclaredType getAnnotatedType(TypeElement elt) {
return (AnnotatedDeclaredType) getAnnotatedType((Element) elt);
}
/**
* See {@link #getAnnotatedType(Element)}.
*
* @see #getAnnotatedType(Element)
*/
public final AnnotatedExecutableType getAnnotatedType(ExecutableElement elt) {
return (AnnotatedExecutableType) getAnnotatedType((Element) elt);
}
/**
* See {@link #fromElement(Element)}.
*
* @see #fromElement(Element)
*/
public final AnnotatedDeclaredType fromElement(TypeElement elt) {
return (AnnotatedDeclaredType) fromElement((Element) elt);
}
/**
* See {@link #fromElement(Element)}.
*
* @see #fromElement(Element)
*/
public final AnnotatedExecutableType fromElement(ExecutableElement elt) {
return (AnnotatedExecutableType) fromElement((Element) elt);
}
// **********************************************************************
// Helper methods for this classes
// **********************************************************************
/**
* Determines whether the given annotation is a part of the type system under which this type
* factory operates. Null is never a supported qualifier; the parameter is nullable to allow the
* result of canonicalAnnotation to be passed in directly.
*
* @param a any annotation
* @return true if that annotation is part of the type system under which this type factory
* operates, false otherwise
*/
public boolean isSupportedQualifier(@Nullable AnnotationMirror a) {
if (a == null) {
return false;
}
return isSupportedQualifier(AnnotationUtils.annotationName(a));
}
/**
* Determines whether the given class is a part of the type system under which this type factory
* operates.
*
* @param clazz annotation class
* @return true if that class is a type qualifier in the type system under which this type factory
* operates, false otherwise
*/
public boolean isSupportedQualifier(Class clazz) {
return getSupportedTypeQualifiers().contains(clazz);
}
/**
* Determines whether the given class name is a part of the type system under which this type
* factory operates.
*
* @param className fully-qualified annotation class name
* @return true if that class name is a type qualifier in the type system under which this type
* factory operates, false otherwise
*/
public boolean isSupportedQualifier(String className) {
return getSupportedTypeQualifierNames().contains(className);
}
/**
* Adds the annotation {@code aliasClass} as an alias for the canonical annotation {@code type}
* that will be used by the Checker Framework in the alias's place.
*
*
By specifying the alias/canonical relationship using this method, the elements of the alias
* are not preserved when the canonical annotation to use is constructed from the alias. If you
* want the elements to be copied over as well, use {@link #addAliasedTypeAnnotation(Class, Class,
* boolean, String...)}.
*
* @param aliasClass the class of the aliased annotation
* @param type the canonical annotation
* @deprecated use {@code addAliasedTypeAnnotation}
*/
@Deprecated // 2020-12-15
protected void addAliasedAnnotation(Class aliasClass, AnnotationMirror type) {
addAliasedTypeAnnotation(aliasClass, type);
}
/**
* Adds the annotation {@code aliasClass} as an alias for the canonical annotation {@code
* canonicalAnno} that will be used by the Checker Framework in the alias's place.
*
*
By specifying the alias/canonical relationship using this method, the elements of the alias
* are not preserved when the canonical annotation to use is constructed from the alias. If you
* want the elements to be copied over as well, use {@link #addAliasedTypeAnnotation(Class, Class,
* boolean, String...)}.
*
* @param aliasClass the class of the aliased annotation
* @param canonicalAnno the canonical annotation
*/
protected void addAliasedTypeAnnotation(Class aliasClass, AnnotationMirror canonicalAnno) {
if (getSupportedTypeQualifiers().contains(aliasClass)) {
throw new BugInCF(
"AnnotatedTypeFactory: alias %s should not be in type hierarchy for %s",
aliasClass, this.getClass().getSimpleName());
}
addAliasedTypeAnnotation(aliasClass.getCanonicalName(), canonicalAnno);
}
/**
* Adds the annotation, whose fully-qualified name is given by {@code aliasName}, as an alias for
* the canonical annotation {@code canonicalAnno} that will be used by the Checker Framework in
* the alias's place.
*
*
Use this method if the alias class is not necessarily on the classpath at Checker Framework
* compile and run time. Otherwise, use {@link #addAliasedTypeAnnotation(Class, AnnotationMirror)}
* which prevents the possibility of a typo in the class name.
*
* @param aliasName the canonical name of the aliased annotation
* @param canonicalAnno the canonical annotation
* @deprecated use {@link #addAliasedTypeAnnotation}
*/
// aliasName is annotated as @FullyQualifiedName because there is no way to confirm that the
// name of an external annotation is a canoncal name.
@Deprecated // 2020-12-15
protected void addAliasedAnnotation(
@FullyQualifiedName String aliasName, AnnotationMirror canonicalAnno) {
addAliasedTypeAnnotation(aliasName, canonicalAnno);
}
/**
* Adds the annotation, whose fully-qualified name is given by {@code aliasName}, as an alias for
* the canonical annotation {@code canonicalAnno} that will be used by the Checker Framework in
* the alias's place.
*
*
Use this method if the alias class is not necessarily on the classpath at Checker Framework
* compile and run time. Otherwise, use {@link #addAliasedTypeAnnotation(Class, AnnotationMirror)}
* which prevents the possibility of a typo in the class name.
*
* @param aliasName the canonical name of the aliased annotation
* @param canonicalAnno the canonical annotation
*/
// aliasName is annotated as @FullyQualifiedName because there is no way to confirm that the
// name of an external annotation is a canoncal name.
protected void addAliasedTypeAnnotation(
@FullyQualifiedName String aliasName, AnnotationMirror canonicalAnno) {
aliases.put(aliasName, new Alias(aliasName, canonicalAnno, false, null, null));
}
/**
* Adds the annotation {@code aliasClass} as an alias for the canonical annotation {@code
* canonicalAnno} that will be used by the Checker Framework in the alias's place.
*
*
You may specify the copyElements flag to indicate whether you want the elements of the alias
* to be copied over when the canonical annotation is constructed as a copy of {@code
* canonicalAnno}. Be careful that the framework will try to copy the elements by name matching,
* so make sure that names and types of the elements to be copied over are exactly the same as the
* ones in the canonical annotation. Otherwise, an 'Couldn't find element in annotation' error is
* raised.
*
*
To facilitate the cases where some of the elements are ignored on purpose when constructing
* the canonical annotation, this method also provides a varargs {@code ignorableElements} for you
* to explicitly specify the ignoring rules. For example, {@code
* org.checkerframework.checker.index.qual.IndexFor} is an alias of {@code
* org.checkerframework.checker.index.qual.NonNegative}, but the element "value" of
* {@code @IndexFor} should be ignored when constructing {@code @NonNegative}. In the cases where
* all elements are ignored, we can simply use {@link #addAliasedTypeAnnotation(Class,
* AnnotationMirror)} instead.
*
* @param aliasClass the class of the aliased annotation
* @param canonical the canonical annotation
* @param copyElements a flag that indicates whether you want to copy the elements over when
* getting the alias from the canonical annotation
* @param ignorableElements a list of elements that can be safely dropped when the elements are
* being copied over
* @deprecated use {@code addAliasedTypeAnnotation}
*/
@Deprecated // 2020-12-15
protected void addAliasedAnnotation(
Class aliasClass, Class canonical, boolean copyElements, String... ignorableElements) {
addAliasedTypeAnnotation(aliasClass, canonical, copyElements, ignorableElements);
}
/**
* Adds the annotation {@code aliasClass} as an alias for the canonical annotation {@code
* canonicalClass} that will be used by the Checker Framework in the alias's place.
*
*
You may specify the copyElements flag to indicate whether you want the elements of the alias
* to be copied over when the canonical annotation is constructed as a copy of {@code
* canonicalClass}. Be careful that the framework will try to copy the elements by name matching,
* so make sure that names and types of the elements to be copied over are exactly the same as the
* ones in the canonical annotation. Otherwise, an 'Couldn't find element in annotation' error is
* raised.
*
*
To facilitate the cases where some of the elements are ignored on purpose when constructing
* the canonical annotation, this method also provides a varargs {@code ignorableElements} for you
* to explicitly specify the ignoring rules. For example, {@code
* org.checkerframework.checker.index.qual.IndexFor} is an alias of {@code
* org.checkerframework.checker.index.qual.NonNegative}, but the element "value" of
* {@code @IndexFor} should be ignored when constructing {@code @NonNegative}. In the cases where
* all elements are ignored, we can simply use {@link #addAliasedTypeAnnotation(Class,
* AnnotationMirror)} instead.
*
* @param aliasClass the class of the aliased annotation
* @param canonicalClass the class of the canonical annotation
* @param copyElements a flag that indicates whether you want to copy the elements over when
* getting the alias from the canonical annotation
* @param ignorableElements a list of elements that can be safely dropped when the elements are
* being copied over
*/
protected void addAliasedTypeAnnotation(
Class aliasClass,
Class canonicalClass,
boolean copyElements,
String... ignorableElements) {
if (getSupportedTypeQualifiers().contains(aliasClass)) {
throw new BugInCF(
"AnnotatedTypeFactory: alias %s should not be in type hierarchy for %s",
aliasClass, this.getClass().getSimpleName());
}
addAliasedTypeAnnotation(
aliasClass.getCanonicalName(), canonicalClass, copyElements, ignorableElements);
}
/**
* Adds the annotation, whose fully-qualified name is given by {@code aliasName}, as an alias for
* the canonical annotation {@code canonicalAnno} that will be used by the Checker Framework in
* the alias's place.
*
*
Use this method if the alias class is not necessarily on the classpath at Checker Framework
* compile and run time. Otherwise, use {@link #addAliasedTypeAnnotation(Class, Class, boolean,
* String[])} which prevents the possibility of a typo in the class name.
*
* @param aliasName the canonical name of the aliased class
* @param canonicalAnno the canonical annotation
* @param copyElements a flag that indicates whether we want to copy the elements over when
* getting the alias from the canonical annotation
* @param ignorableElements a list of elements that can be safely dropped when the elements are
* being copied over
*/
// aliasName is annotated as @FullyQualifiedName because there is no way to confirm that the
// name of an external annotation is a canoncal name.
protected void addAliasedTypeAnnotation(
@FullyQualifiedName String aliasName,
Class canonicalAnno,
boolean copyElements,
String... ignorableElements) {
// The copyElements argument disambiguates overloading.
if (!copyElements) {
throw new BugInCF("Do not call with false");
}
aliases.put(
aliasName,
new Alias(
aliasName, null, copyElements, canonicalAnno.getCanonicalName(), ignorableElements));
}
/**
* Returns the canonical annotation for the passed annotation. Returns null if the passed
* annotation is not an alias of a canonical one in the framework.
*
*
A canonical annotation is the internal annotation that will be used by the Checker Framework
* in the aliased annotation's place.
*
* @param a the qualifier to check for an alias
* @return the canonical annotation, or null if none exists
*/
public @Nullable AnnotationMirror canonicalAnnotation(AnnotationMirror a) {
TypeElement elem = (TypeElement) a.getAnnotationType().asElement();
String qualName = elem.getQualifiedName().toString();
Alias alias = aliases.get(qualName);
if (alias == null) {
return null;
}
if (alias.copyElements) {
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, alias.canonicalName);
builder.copyElementValuesFromAnnotation(a, alias.ignorableElements);
return builder.build();
} else {
return alias.canonical;
}
}
/**
* Add the annotation {@code alias} as an alias for the declaration annotation {@code annotation},
* where the annotation mirror {@code annotationToUse} will be used instead. If multiple calls are
* made with the same {@code annotation}, then the {@code annotationToUse} must be the same.
*
*
The point of {@code annotationToUse} is that it may include elements/fields.
*/
protected void addAliasedDeclAnnotation(
Class alias,
Class annotation,
AnnotationMirror annotationToUse) {
Pair>> pair = declAliases.get(annotation);
if (pair != null) {
if (!AnnotationUtils.areSame(annotationToUse, pair.first)) {
throw new BugInCF("annotationToUse should be the same: %s %s", pair.first, annotationToUse);
}
} else {
pair = Pair.of(annotationToUse, new HashSet<>());
declAliases.put(annotation, pair);
}
Set> aliases = pair.second;
aliases.add(alias);
}
/**
* Adds the annotation {@code annotation} in the set of declaration annotations that should be
* inherited. A declaration annotation will be inherited if it is in this list, or if it has the
* meta-annotation @InheritedAnnotation. The meta-annotation @InheritedAnnotation should be used
* instead of this method, if possible.
*/
protected void addInheritedAnnotation(AnnotationMirror annotation) {
inheritedAnnotations.add(annotation);
}
/**
* A convenience method that converts a {@link TypeMirror} to an empty {@link AnnotatedTypeMirror}
* using {@link AnnotatedTypeMirror#createType}.
*
* @param t the {@link TypeMirror}
* @param declaration true if the result should be marked as a type declaration
* @return an {@link AnnotatedTypeMirror} that has {@code t} as its underlying type
*/
protected final AnnotatedTypeMirror toAnnotatedType(TypeMirror t, boolean declaration) {
return AnnotatedTypeMirror.createType(t, this, declaration);
}
/**
* Determines an empty annotated type of the given tree. In other words, finds the {@link
* TypeMirror} for the tree and converts that into an {@link AnnotatedTypeMirror}, but does not
* add any annotations to the result.
*
* Most users will want to use {@link #getAnnotatedType(Tree)} instead; this method is mostly
* for internal use.
*
* @param node the tree to analyze
* @return the type of {@code node}, without any annotations
*/
protected final AnnotatedTypeMirror type(Tree node) {
boolean isDeclaration = TreeUtils.isTypeDeclaration(node);
// Attempt to obtain the type via JCTree.
if (TreeUtils.typeOf(node) != null) {
AnnotatedTypeMirror result = toAnnotatedType(TreeUtils.typeOf(node), isDeclaration);
return result;
}
// Attempt to obtain the type via TreePath (slower).
TreePath path = this.getPath(node);
assert path != null
: "No path or type in tree: " + node + " [" + node.getClass().getSimpleName() + "]";
TypeMirror t = trees.getTypeMirror(path);
assert validType(t) : "Invalid type " + t + " for node " + t;
AnnotatedTypeMirror result = toAnnotatedType(t, isDeclaration);
return result;
}
/**
* Gets the declaration tree for the element, if the source is available.
*
*
TODO: would be nice to move this to InternalUtils/TreeUtils.
*
* @param elt an element
* @return the tree declaration of the element if found
*/
public final Tree declarationFromElement(Element elt) {
// if root is null, we cannot find any declaration
if (root == null) {
return null;
}
if (shouldCache && elementToTreeCache.containsKey(elt)) {
return elementToTreeCache.get(elt);
}
// Check for new declarations, outside of the AST.
if (elt instanceof DetachedVarSymbol) {
return ((DetachedVarSymbol) elt).getDeclaration();
}
// TODO: handle type parameter declarations?
Tree fromElt;
// Prevent calling declarationFor on elements we know we don't have the tree for.
switch (ElementUtils.getKindRecordAsClass(elt)) {
case CLASS: // Including RECORD
case ENUM:
case INTERFACE:
case ANNOTATION_TYPE:
case FIELD:
case ENUM_CONSTANT:
case METHOD:
case CONSTRUCTOR:
fromElt = trees.getTree(elt);
break;
default:
fromElt =
com.sun.tools.javac.tree.TreeInfo.declarationFor(
(com.sun.tools.javac.code.Symbol) elt, (com.sun.tools.javac.tree.JCTree) root);
break;
}
if (shouldCache) {
elementToTreeCache.put(elt, fromElt);
}
return fromElt;
}
/**
* Returns the class tree enclosing {@code tree}.
*
* @param tree the tree whose enclosing class is returned
* @return the class tree enclosing {@code tree}
* @deprecated Use {@code TreePathUtil.enclosingClass(getPath(tree))} instead.
*/
@Deprecated
protected final ClassTree getCurrentClassTree(Tree tree) {
return TreePathUtil.enclosingClass(getPath(tree));
}
/**
* Returns the receiver type of the method enclosing {@code tree}.
*
*
The method uses the parameter only if the most enclosing method cannot be found directly.
*
* @param tree the tree used to find the enclosing method.
* @return receiver type of the most enclosing method being visited
* @deprecated Use {@link #getSelfType(Tree)} instead.
*/
@Deprecated
protected final @Nullable AnnotatedDeclaredType getCurrentMethodReceiver(Tree tree) {
TreePath path = getPath(tree);
if (path == null) {
return null;
}
@SuppressWarnings("interning:assignment") // used for == test
@InternedDistinct MethodTree enclosingMethod = TreePathUtil.enclosingMethod(path);
ClassTree enclosingClass = TreePathUtil.enclosingClass(path);
boolean found = false;
for (Tree member : enclosingClass.getMembers()) {
if (member.getKind() == Tree.Kind.METHOD) {
if (member == enclosingMethod) {
found = true;
}
}
}
if (found && enclosingMethod != null) {
AnnotatedExecutableType method = getAnnotatedType(enclosingMethod);
return method.getReceiverType();
} else {
// We are within an anonymous class or field initializer
return this.getAnnotatedType(enclosingClass);
}
}
/**
* Returns true if {@code tree} is within a constructor.
*
* @param tree the tree that might be within a constructor.
* @return true if {@code tree} is within a constructor
*/
protected final boolean isWithinConstructor(Tree tree) {
MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getPath(tree));
return enclosingMethod != null && TreeUtils.isConstructor(enclosingMethod);
}
/**
* Sets the path to the tree that an external "visitor" is visiting. The visitor is either a
* subclass of {@link BaseTypeVisitor} or {@link
* org.checkerframework.framework.flow.CFAbstractTransfer}.
*
* @param visitorTreePath path to the current tree that an external "visitor" is visiting
*/
public void setVisitorTreePath(@Nullable TreePath visitorTreePath) {
this.visitorTreePath = visitorTreePath;
}
/**
* Returns the path to the tree that an external "visitor" is visiting. The type factory does not
* update this value as it computes the types of any tree or element needed compute the type of
* the tree being visited. Therefore this path may not be the path to the tree whose type is being
* computed. This method should not be used directly. Use {@link #getPath(Tree)} instead.
*
*
This method is used to save the previous tree path and to give a hint to {@link
* #getPath(Tree)} on where to look for a tree rather than searching starting at the root.
*
* @return the path to the tree that an external "visitor" is visiting
*/
public @Nullable TreePath getVisitorTreePath() {
return visitorTreePath;
}
/**
* Gets the path for the given {@link Tree} under the current root by checking from the visitor's
* current path, and using {@link Trees#getPath(CompilationUnitTree, Tree)} (which is much slower)
* only if {@code node} is not found on the current path.
*
*
Note that the given Tree has to be within the current compilation unit, otherwise null will
* be returned.
*
* @param node the {@link Tree} to get the path for
* @return the path for {@code node} under the current root. Returns null if {@code node} is not
* within the current compilation unit.
*/
public final @Nullable TreePath getPath(@FindDistinct Tree node) {
assert root != null
: "AnnotatedTypeFactory.getPath("
+ node.getKind()
+ "): root needs to be set when used on trees; factory: "
+ this.getClass().getSimpleName();
if (node == null) {
return null;
}
if (artificialTreeToEnclosingElementMap.containsKey(node)) {
return null;
}
if (treePathCache.isCached(node)) {
return treePathCache.getPath(root, node);
}
TreePath currentPath = visitorTreePath;
if (currentPath == null) {
TreePath path = TreePath.getPath(root, node);
treePathCache.addPath(node, path);
return path;
}
// This method uses multiple heuristics to avoid calling
// TreePath.getPath()
// If the current path you are visiting is for this node we are done
if (currentPath.getLeaf() == node) {
treePathCache.addPath(node, currentPath);
return currentPath;
}
// When running on Daikon, we noticed that a lot of calls happened
// within a small subtree containing the node we are currently visiting
// When testing on Daikon, two steps resulted in the best performance
if (currentPath.getParentPath() != null) {
currentPath = currentPath.getParentPath();
treePathCache.addPath(currentPath.getLeaf(), currentPath);
if (currentPath.getLeaf() == node) {
return currentPath;
}
if (currentPath.getParentPath() != null) {
currentPath = currentPath.getParentPath();
treePathCache.addPath(currentPath.getLeaf(), currentPath);
if (currentPath.getLeaf() == node) {
return currentPath;
}
}
}
final TreePath pathWithinSubtree = TreePath.getPath(currentPath, node);
if (pathWithinSubtree != null) {
treePathCache.addPath(node, pathWithinSubtree);
return pathWithinSubtree;
}
// climb the current path till we see that
// Works when getPath called on the enclosing method, enclosing class.
TreePath current = currentPath;
while (current != null) {
treePathCache.addPath(current.getLeaf(), current);
if (current.getLeaf() == node) {
return current;
}
current = current.getParentPath();
}
// OK, we give up. Use the cache to look up.
return treePathCache.getPath(root, node);
}
/**
* Gets the {@link Element} representing the declaration of the method enclosing a tree node. This
* feature is used to record the enclosing methods of {@link Tree}s that are created internally by
* the checker.
*
*
TODO: Find a better way to store information about enclosing Trees.
*
* @param node the {@link Tree} to get the enclosing method for
* @return the method {@link Element} enclosing the argument, or null if none has been recorded
*/
public final Element getEnclosingElementForArtificialTree(Tree node) {
return artificialTreeToEnclosingElementMap.get(node);
}
/**
* Adds the given mapping from a synthetic (generated) tree to its enclosing element.
*
*
See {@code
* org.checkerframework.framework.flow.CFCFGBuilder.CFCFGTranslationPhaseOne.handleArtificialTree(Tree)}.
*
* @param tree artifical tree
* @param enclosing element that encloses {@code tree}
*/
public final void setEnclosingElementForArtificialTree(Tree tree, Element enclosing) {
artificialTreeToEnclosingElementMap.put(tree, enclosing);
}
/**
* Assert that the type is a type of valid type mirror, i.e. not an ERROR or OTHER type.
*
* @param type an annotated type
* @return true if the type is a valid annotated type, false otherwise
*/
static final boolean validAnnotatedType(AnnotatedTypeMirror type) {
if (type == null) {
return false;
}
return validType(type.getUnderlyingType());
}
/**
* Used for asserting that a type is valid for converting to an annotated type.
*
* @return true if {@code type} can be converted to an annotated type, false otherwise
*/
private static final boolean validType(TypeMirror type) {
if (type == null) {
return false;
}
switch (type.getKind()) {
case ERROR:
case OTHER:
case PACKAGE:
return false;
default:
return true;
}
}
/**
* Parses all annotation files in the following order:
*
*
* - jdk.astub in the same directory as the checker, if it exists and ignorejdkastub option is
* not supplied
* - jdkN.astub, where N is the Java version in the same directory as the checker, if it
* exists and ignorejdkastub option is not supplied
* - Stub files listed in @StubFiles annotation on the checker; must be in same directory as
* the checker
* - Stub files provided via -Astubs compiler option
*
- Ajava files provided via -Aajava compiler option
*
*
* If a type is annotated with a qualifier from the same hierarchy in more than one stub file,
* the qualifier in the last stub file is applied.
*
*
The annotations are stored by side-effecting {@link #stubTypes} and {@link #ajavaTypes}.
*/
protected void parseAnnotationFiles() {
stubTypes.parseStubFiles();
ajavaTypes.parseAjavaFiles();
}
/**
* Returns all of the declaration annotations whose name equals the passed annotation class (or is
* an alias for it) including annotations:
*
*
* - on the element
*
- written in stubfiles
*
- inherited from overriden methods, (see {@link InheritedAnnotation})
*
- inherited from superclasses or super interfaces (see {@link Inherited})
*
*
* @see #getDeclAnnotationNoAliases
* @param elt the element to retrieve the declaration annotation from
* @param anno annotation class
* @return the annotation mirror for anno
*/
@Override
public final AnnotationMirror getDeclAnnotation(Element elt, Class anno) {
return getDeclAnnotation(elt, anno, true);
}
/**
* Returns the actual annotation mirror used to annotate this element, whose name equals the
* passed annotation class. Returns null if none exists. Does not check for aliases of the
* annotation class.
*
* Call this method from a checker that needs to alias annotations for one purpose and not for
* another. For example, in the Lock Checker, {@code @LockingFree} and {@code @ReleasesNoLocks}
* are both aliases of {@code @SideEffectFree} since they are all considered side-effect-free with
* regard to the set of locks held before and after the method call. However, a {@code
* synchronized} block is permitted inside a {@code @ReleasesNoLocks} method but not inside a
* {@code @LockingFree} or {@code @SideEffectFree} method.
*
* @see #getDeclAnnotation
* @param elt the element to retrieve the declaration annotation from
* @param anno annotation class
* @return the annotation mirror for anno
*/
public final AnnotationMirror getDeclAnnotationNoAliases(
Element elt, Class anno) {
return getDeclAnnotation(elt, anno, false);
}
/**
* Returns true if the element appears in a stub file (Currently only works for methods,
* constructors, and fields).
*/
public boolean isFromStubFile(Element element) {
return this.getDeclAnnotation(element, FromStubFile.class) != null;
}
/**
* Returns true if the element is from bytecode and the if the element did not appear in a stub
* file. Currently only works for methods, constructors, and fields.
*/
public boolean isFromByteCode(Element element) {
if (isFromStubFile(element)) {
return false;
}
return ElementUtils.isElementFromByteCode(element);
}
/**
* Returns true if redundancy between a stub file and bytecode should be reported.
*
*
For most type systems the default behavior of returning true is correct. For subcheckers,
* redundancy in one of the type hierarchies can be ok. Such implementations should return false.
*
* @return whether to warn about redundancy between a stub file and bytecode
*/
public boolean shouldWarnIfStubRedundantWithBytecode() {
return true;
}
/**
* Returns the actual annotation mirror used to annotate this element, whose name equals the
* passed annotation class (or is an alias for it). Returns null if none exists. May return the
* canonical annotation that annotationName is an alias for.
*
*
This is the private implementation of the same-named, public method.
*
*
An option is provided to not to check for aliases of annotations. For example, an annotated
* type factory may use aliasing for a pair of annotations for convenience while needing in some
* cases to determine a strict ordering between them, such as when determining whether the
* annotations on an overrider method are more specific than the annotations of an overridden
* method.
*
* @param elt the element to retrieve the annotation from
* @param annoClass the class the annotation to retrieve
* @param checkAliases whether to return an annotation mirror for an alias of the requested
* annotation class name
* @return the annotation mirror for the requested annotation, or null if not found
*/
private AnnotationMirror getDeclAnnotation(
Element elt, Class annoClass, boolean checkAliases) {
Set declAnnos = getDeclAnnotations(elt);
for (AnnotationMirror am : declAnnos) {
if (areSameByClass(am, annoClass)) {
return am;
}
}
// Look through aliases.
if (checkAliases) {
Pair>> aliases = declAliases.get(annoClass);
if (aliases != null) {
for (Class alias : aliases.second) {
for (AnnotationMirror am : declAnnos) {
if (areSameByClass(am, alias)) {
// TODO: need to copy over elements/fields
return aliases.first;
}
}
}
}
}
// Not found.
return null;
}
/**
* Returns all of the declaration annotations on this element including annotations:
*
*
* - on the element
*
- written in stubfiles
*
- inherited from overriden methods, (see {@link InheritedAnnotation})
*
- inherited from superclasses or super interfaces (see {@link Inherited})
*
*
* This method returns the actual annotations not their aliases. {@link
* #getDeclAnnotation(Element, Class)} returns aliases.
*
* @param elt the element for which to determine annotations
* @return all of the declaration annotations on this element, written in stub files, or inherited
*/
public Set getDeclAnnotations(Element elt) {
Set cachedValue = cacheDeclAnnos.get(elt);
if (cachedValue != null) {
// Found in cache, return result.
return cachedValue;
}
Set results = AnnotationUtils.createAnnotationSet();
// Retrieving the annotations from the element.
// This includes annotations inherited from superclasses, but not superinterfaces or
// overriden methods.
List fromEle = elements.getAllAnnotationMirrors(elt);
for (AnnotationMirror annotation : fromEle) {
try {
results.add(annotation);
} catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) {
// If a CompletionFailure occurs, issue a warning.
checker.reportWarning(
annotation.getAnnotationType().asElement(),
"annotation.not.completed",
ElementUtils.getQualifiedName(elt),
annotation);
}
}
// If parsing annotation files, return only the annotations in the element.
if (stubTypes.isParsing()
|| ajavaTypes.isParsing()
|| (currentFileAjavaTypes != null && currentFileAjavaTypes.isParsing())) {
return results;
}
// Add annotations from annotation files.
results.addAll(stubTypes.getDeclAnnotations(elt));
results.addAll(ajavaTypes.getDeclAnnotations(elt));
if (currentFileAjavaTypes != null) {
results.addAll(currentFileAjavaTypes.getDeclAnnotations(elt));
}
if (elt.getKind() == ElementKind.METHOD) {
// Retrieve the annotations from the overridden method's element.
inheritOverriddenDeclAnnos((ExecutableElement) elt, results);
} else if (ElementUtils.isTypeDeclaration(elt)) {
inheritOverriddenDeclAnnosFromTypeDecl(elt.asType(), results);
}
// Add the element and its annotations to the cache.
cacheDeclAnnos.put(elt, results);
return results;
}
/**
* Adds into {@code results} the inherited declaration annotations found in all elements of the
* super types of {@code typeMirror}. (Both superclasses and superinterfaces.)
*
* @param typeMirror type
* @param results set of AnnotationMirrors to which this method adds declarations annotations
*/
private void inheritOverriddenDeclAnnosFromTypeDecl(
TypeMirror typeMirror, Set results) {
List superTypes = types.directSupertypes(typeMirror);
for (TypeMirror superType : superTypes) {
TypeElement elt = TypesUtils.getTypeElement(superType);
if (elt == null) {
continue;
}
Set superAnnos = getDeclAnnotations(elt);
for (AnnotationMirror annotation : superAnnos) {
List annotationsOnAnnotation;
try {
annotationsOnAnnotation =
annotation.getAnnotationType().asElement().getAnnotationMirrors();
} catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) {
// Fix for Issue 348: If a CompletionFailure occurs, issue a warning.
checker.reportWarning(
annotation.getAnnotationType().asElement(),
"annotation.not.completed",
ElementUtils.getQualifiedName(elt),
annotation);
continue;
}
if (containsSameByClass(annotationsOnAnnotation, Inherited.class)
|| AnnotationUtils.containsSameByName(inheritedAnnotations, annotation)) {
addOrMerge(results, annotation);
}
}
}
}
/**
* Adds into {@code results} the declaration annotations found in all elements that the method
* element {@code elt} overrides.
*
* @param elt method element
* @param results {@code elt} local declaration annotations. The ones found in stub files and in
* the element itself.
*/
private void inheritOverriddenDeclAnnos(ExecutableElement elt, Set results) {
Map overriddenMethods =
AnnotatedTypes.overriddenMethods(elements, this, elt);
if (overriddenMethods != null) {
for (ExecutableElement superElt : overriddenMethods.values()) {
Set superAnnos = getDeclAnnotations(superElt);
for (AnnotationMirror annotation : superAnnos) {
List annotationsOnAnnotation;
try {
annotationsOnAnnotation =
annotation.getAnnotationType().asElement().getAnnotationMirrors();
} catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) {
// Fix for Issue 348: If a CompletionFailure occurs,
// issue a warning.
checker.reportWarning(
annotation.getAnnotationType().asElement(),
"annotation.not.completed",
ElementUtils.getQualifiedName(elt),
annotation);
continue;
}
if (containsSameByClass(annotationsOnAnnotation, InheritedAnnotation.class)
|| AnnotationUtils.containsSameByName(inheritedAnnotations, annotation)) {
addOrMerge(results, annotation);
}
}
}
}
}
private void addOrMerge(Set results, AnnotationMirror annotation) {
if (AnnotationUtils.containsSameByName(results, annotation)) {
/*
* TODO: feature request: figure out a way to merge multiple annotations
* of the same kind. For some annotations this might mean merging some
* arrays, for others it might mean converting a single annotation into a
* container annotation. We should define a protected method for subclasses
* to adapt the behavior.
* For now, do nothing and just take the first, most concrete, annotation.
AnnotationMirror prev = null;
for (AnnotationMirror an : results) {
if (AnnotationUtils.areSameByName(an, annotation)) {
prev = an;
break;
}
}
results.remove(prev);
AnnotationMirror merged = ...;
results.add(merged);
*/
} else {
results.add(annotation);
}
}
/**
* Returns a list of all declaration annotations used to annotate the element, which have a
* meta-annotation (i.e., an annotation on that annotation) with class {@code
* metaAnnotationClass}.
*
* @param element the element for which to determine annotations
* @param metaAnnotationClass the class of the meta-annotation that needs to be present
* @return a list of pairs {@code (anno, metaAnno)} where {@code anno} is the annotation mirror at
* {@code element}, and {@code metaAnno} is the annotation mirror (of type {@code
* metaAnnotationClass}) used to meta-annotate the declaration of {@code anno}
*/
public List> getDeclAnnotationWithMetaAnnotation(
Element element, Class metaAnnotationClass) {
List> result = new ArrayList<>();
Set annotationMirrors = getDeclAnnotations(element);
for (AnnotationMirror candidate : annotationMirrors) {
List metaAnnotationsOnAnnotation;
try {
metaAnnotationsOnAnnotation =
candidate.getAnnotationType().asElement().getAnnotationMirrors();
} catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) {
// Fix for Issue 309: If a CompletionFailure occurs, issue a warning.
// I didn't find a nicer alternative to check whether the Symbol can be completed.
// The completer field of a Symbol might be non-null also in successful cases.
// Issue a warning (exception only happens once) and continue.
checker.reportWarning(
candidate.getAnnotationType().asElement(),
"annotation.not.completed",
ElementUtils.getQualifiedName(element),
candidate);
continue;
}
// First call copier, if exception, continue normal modula laws.
for (AnnotationMirror ma : metaAnnotationsOnAnnotation) {
if (areSameByClass(ma, metaAnnotationClass)) {
// This candidate has the right kind of meta-annotation.
// It might be a real contract, or a list of contracts.
if (isListForRepeatedAnnotation(candidate)) {
@SuppressWarnings("deprecation") // concrete annotation class is not known
List wrappedCandidates =
AnnotationUtils.getElementValueArray(
candidate, "value", AnnotationMirror.class, false);
for (AnnotationMirror wrappedCandidate : wrappedCandidates) {
result.add(Pair.of(wrappedCandidate, ma));
}
} else {
result.add(Pair.of(candidate, ma));
}
}
}
}
return result;
}
/** Cache for {@link #isListForRepeatedAnnotation}. */
private final Map isListForRepeatedAnnotationCache = new HashMap<>();
/**
* Returns true if the given annotation is a wrapper for multiple repeated annotations.
*
* @param a an annotation that might be a wrapper
* @return true if the argument is a wrapper for multiple repeated annotations
*/
private boolean isListForRepeatedAnnotation(AnnotationMirror a) {
DeclaredType annotationType = a.getAnnotationType();
Boolean resultObject = isListForRepeatedAnnotationCache.get(annotationType);
if (resultObject != null) {
return resultObject;
}
boolean result = isListForRepeatedAnnotationImplementation(annotationType);
isListForRepeatedAnnotationCache.put(annotationType, result);
return result;
}
/**
* Returns true if the annotation is a wrapper for multiple repeated annotations.
*
* @param annotationType the declaration of the annotation to test
* @return true if the annotation is a wrapper for multiple repeated annotations
*/
private boolean isListForRepeatedAnnotationImplementation(DeclaredType annotationType) {
TypeMirror enclosingType = annotationType.getEnclosingType();
if (enclosingType == null) {
return false;
}
if (!annotationType.asElement().getSimpleName().contentEquals("List")) {
return false;
}
List annoElements = annotationType.asElement().getEnclosedElements();
if (annoElements.size() != 1) {
return false;
}
// TODO: should check that the type of the single element is: "array of enclosingType".
return true;
}
/**
* Returns a list of all annotations used to annotate this element, which have a meta-annotation
* (i.e., an annotation on that annotation) with class {@code metaAnnotationClass}.
*
* @param element the element at which to look for annotations
* @param metaAnnotationClass the class of the meta-annotation that needs to be present
* @return a list of pairs {@code (anno, metaAnno)} where {@code anno} is the annotation mirror at
* {@code element}, and {@code metaAnno} is the annotation mirror used to annotate {@code
* anno}.
*/
public List> getAnnotationWithMetaAnnotation(
Element element, Class metaAnnotationClass) {
Set annotationMirrors = AnnotationUtils.createAnnotationSet();
// Consider real annotations.
annotationMirrors.addAll(getAnnotatedType(element).getAnnotations());
// Consider declaration annotations
annotationMirrors.addAll(getDeclAnnotations(element));
List> result = new ArrayList<>();
// Go through all annotations found.
for (AnnotationMirror annotation : annotationMirrors) {
List annotationsOnAnnotation =
annotation.getAnnotationType().asElement().getAnnotationMirrors();
for (AnnotationMirror a : annotationsOnAnnotation) {
if (areSameByClass(a, metaAnnotationClass)) {
result.add(Pair.of(annotation, a));
}
}
}
return result;
}
/**
* Whether or not the {@code annotatedTypeMirror} has a qualifier parameter.
*
* @param annotatedTypeMirror AnnotatedTypeMirror to check
* @param top the top of the hierarchy to check
* @return true if the type has a qualifier parameter
*/
public boolean hasQualifierParameterInHierarchy(
AnnotatedTypeMirror annotatedTypeMirror, AnnotationMirror top) {
return AnnotationUtils.containsSame(getQualifierParameterHierarchies(annotatedTypeMirror), top);
}
/**
* Whether or not the {@code element} has a qualifier parameter.
*
* @param element element to check
* @param top the top of the hierarchy to check
* @return true if the type has a qualifier parameter
*/
public boolean hasQualifierParameterInHierarchy(@Nullable Element element, AnnotationMirror top) {
if (element == null) {
return false;
}
return AnnotationUtils.containsSame(getQualifierParameterHierarchies(element), top);
}
/**
* Returns whether the {@code HasQualifierParameter} annotation was explicitly written on {@code
* element} for the hierarchy given by {@code top}.
*
* @param element the Element to check
* @param top the top qualifier for the hierarchy to check
* @return whether the class given by {@code element} has been explicitly annotated with {@code
* HasQualifierParameter} for the given hierarchy
*/
public boolean hasExplicitQualifierParameterInHierarchy(Element element, AnnotationMirror top) {
return AnnotationUtils.containsSame(
getSupportedAnnotationsInElementAnnotation(
element, HasQualifierParameter.class, hasQualifierParameterValueElement),
top);
}
/**
* Returns whether the {@code NoQualifierParameter} annotation was explicitly written on {@code
* element} for the hierarchy given by {@code top}.
*
* @param element the Element to check
* @param top the top qualifier for the hierarchy to check
* @return whether the class given by {@code element} has been explicitly annotated with {@code
* NoQualifierParameter} for the given hierarchy
*/
public boolean hasExplicitNoQualifierParameterInHierarchy(Element element, AnnotationMirror top) {
return AnnotationUtils.containsSame(
getSupportedAnnotationsInElementAnnotation(
element, NoQualifierParameter.class, noQualifierParameterValueElement),
top);
}
/**
* Returns the set of top annotations representing all the hierarchies for which this type has a
* qualifier parameter.
*
* @param annotatedType AnnotatedTypeMirror to check
* @return the set of top annotations representing all the hierarchies for which this type has a
* qualifier parameter
*/
public Set getQualifierParameterHierarchies(AnnotatedTypeMirror annotatedType) {
while (annotatedType.getKind() == TypeKind.TYPEVAR
|| annotatedType.getKind() == TypeKind.WILDCARD) {
if (annotatedType.getKind() == TypeKind.TYPEVAR) {
annotatedType = ((AnnotatedTypeVariable) annotatedType).getUpperBound();
} else if (annotatedType.getKind() == TypeKind.WILDCARD) {
annotatedType = ((AnnotatedWildcardType) annotatedType).getSuperBound();
}
}
if (annotatedType.getKind() != TypeKind.DECLARED) {
return Collections.emptySet();
}
AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) annotatedType;
Element element = declaredType.getUnderlyingType().asElement();
if (element == null) {
return Collections.emptySet();
}
return getQualifierParameterHierarchies(element);
}
/**
* Returns the set of top annotations representing all the hierarchies for which this element has
* a qualifier parameter.
*
* @param element the Element to check
* @return the set of top annotations representing all the hierarchies for which this element has
* a qualifier parameter
*/
public Set getQualifierParameterHierarchies(Element element) {
if (!ElementUtils.isTypeDeclaration(element)) {
return Collections.emptySet();
}
Set found = AnnotationUtils.createAnnotationSet();
found.addAll(
getSupportedAnnotationsInElementAnnotation(
element, HasQualifierParameter.class, hasQualifierParameterValueElement));
Set hasQualifierParameterTops = AnnotationUtils.createAnnotationSet();
PackageElement packageElement = ElementUtils.enclosingPackage(element);
// Traverse all packages containing this element.
while (packageElement != null) {
Set packageDefaultTops =
getSupportedAnnotationsInElementAnnotation(
packageElement, HasQualifierParameter.class, hasQualifierParameterValueElement);
hasQualifierParameterTops.addAll(packageDefaultTops);
packageElement = ElementUtils.parentPackage(packageElement, elements);
}
Set noQualifierParamClasses =
getSupportedAnnotationsInElementAnnotation(
element, NoQualifierParameter.class, noQualifierParameterValueElement);
for (AnnotationMirror anno : hasQualifierParameterTops) {
if (!AnnotationUtils.containsSame(noQualifierParamClasses, anno)) {
found.add(anno);
}
}
return found;
}
/**
* Returns a set of supported annotation mirrors corresponding to the annotation classes listed in
* the value element of an annotation with class {@code annoClass} on {@code element}.
*
* @param element the Element to check
* @param annoClass the class for an annotation that's written on elements, whose value element is
* a list of annotation classes. It is always HasQualifierParameter or NoQualifierParameter
* @param valueElement the {@code value} field/element of an annotation with class {@code
* annoClass}
* @return the set of supported annotations with classes listed in the value element of an
* annotation with class {@code annoClass} on the {@code element}. Returns an empty set if
* {@code annoClass} is not written on {@code element} or {@code element} is null.
*/
private Set getSupportedAnnotationsInElementAnnotation(
@Nullable Element element,
Class annoClass,
ExecutableElement valueElement) {
if (element == null) {
return Collections.emptySet();
}
// TODO: caching
AnnotationMirror annotation = getDeclAnnotation(element, annoClass);
if (annotation == null) {
return Collections.emptySet();
}
Set found = AnnotationUtils.createAnnotationSet();
List<@CanonicalName Name> qualClasses =
AnnotationUtils.getElementValueClassNames(annotation, valueElement);
for (Name qual : qualClasses) {
AnnotationMirror annotationMirror = AnnotationBuilder.fromName(elements, qual);
if (isSupportedQualifier(annotationMirror)) {
found.add(annotationMirror);
}
}
return found;
}
/**
* A scanner that replaces annotations in one type with annotations from another. Used by {@link
* #replaceAnnotations(AnnotatedTypeMirror, AnnotatedTypeMirror)} and {@link
* #replaceAnnotations(AnnotatedTypeMirror, AnnotatedTypeMirror, AnnotationMirror)}.
*/
private final AnnotatedTypeReplacer annotatedTypeReplacer = new AnnotatedTypeReplacer();
/**
* Replaces or adds all annotations from {@code from} to {@code to}. Annotations from {@code from}
* will be used everywhere they exist, but annotations in {@code to} will be kept anywhere that
* {@code from} is unannotated.
*
* @param from the annotated type mirror from which to take new annotations
* @param to the annotated type mirror to which the annotations will be added
*/
public void replaceAnnotations(final AnnotatedTypeMirror from, final AnnotatedTypeMirror to) {
annotatedTypeReplacer.visit(from, to);
}
/**
* Replaces or adds annotations in {@code top}'s hierarchy from {@code from} to {@code to}.
* Annotations from {@code from} will be used everywhere they exist, but annotations in {@code to}
* will be kept anywhere that {@code from} is unannotated.
*
* @param from the annotated type mirror from which to take new annotations
* @param to the annotated type mirror to which the annotations will be added
* @param top the top type of the hierarchy whose annotations will be added
*/
public void replaceAnnotations(
final AnnotatedTypeMirror from, final AnnotatedTypeMirror to, final AnnotationMirror top) {
annotatedTypeReplacer.setTop(top);
annotatedTypeReplacer.visit(from, to);
annotatedTypeReplacer.setTop(null);
}
/** The implementation of the visitor for #containsUninferredTypeArguments. */
private final SimpleAnnotatedTypeScanner uninferredTypeArgumentScanner =
new SimpleAnnotatedTypeScanner<>(
(type, p) ->
type.getKind() == TypeKind.WILDCARD
&& ((AnnotatedWildcardType) type).isUninferredTypeArgument(),
Boolean::logicalOr,
false);
/**
* Returns whether this type or any component type is a wildcard type for which Java 7 type
* inference is insufficient. See issue 979, or the documentation on AnnotatedWildcardType.
*
* @param type type to check
* @return whether this type or any component type is a wildcard type for which Java 7 type
* inference is insufficient
*/
public boolean containsUninferredTypeArguments(AnnotatedTypeMirror type) {
return uninferredTypeArgumentScanner.visit(type);
}
/**
* Returns a wildcard type to be used as a type argument when the correct type could not be
* inferred. The wildcard will be marked as an uninferred wildcard so that {@link
* AnnotatedWildcardType#isUninferredTypeArgument()} returns true.
*
* This method should only be used by type argument inference.
* org.checkerframework.framework.util.AnnotatedTypes.inferTypeArguments(ProcessingEnvironment,
* AnnotatedTypeFactory, ExpressionTree, ExecutableElement)
*
* @param typeVar TypeVariable which could not be inferred
* @return a wildcard that is marked as an uninferred type argument
*/
public AnnotatedWildcardType getUninferredWildcardType(AnnotatedTypeVariable typeVar) {
final boolean intersectionType;
final TypeMirror boundType;
if (typeVar.getUpperBound().getKind() == TypeKind.INTERSECTION) {
boundType = typeVar.getUpperBound().directSupertypes().get(0).getUnderlyingType();
intersectionType = true;
} else {
boundType = typeVar.getUnderlyingType().getUpperBound();
intersectionType = false;
}
WildcardType wc = types.getWildcardType(boundType, null);
AnnotatedWildcardType wctype =
(AnnotatedWildcardType) AnnotatedTypeMirror.createType(wc, this, false);
wctype.setTypeVariable(typeVar.getUnderlyingType());
if (!intersectionType) {
wctype.setExtendsBound(typeVar.getUpperBound().deepCopy());
} else {
wctype.getExtendsBound().addAnnotations(typeVar.getUpperBound().getAnnotations());
}
wctype.setSuperBound(typeVar.getLowerBound().deepCopy());
wctype.addAnnotations(typeVar.getAnnotations());
addDefaultAnnotations(wctype);
wctype.setUninferredTypeArgument();
return wctype;
}
/**
* Returns the function type that this member reference targets.
*
*
The function type is the type of the single method declared in the functional interface
* adapted as if it were invoked using the functional interface as the receiver expression.
*
*
The target type of a member reference is the type to which it is assigned or casted.
*
* @param tree member reference tree
* @return the function type that this method reference targets
*/
public AnnotatedExecutableType getFunctionTypeFromTree(MemberReferenceTree tree) {
return getFnInterfaceFromTree(tree).second;
}
/**
* Returns the function type that this lambda targets.
*
*
The function type is the type of the single method declared in the functional interface
* adapted as if it were invoked using the functional interface as the receiver expression.
*
*
The target type of a lambda is the type to which it is assigned or casted.
*
* @param tree lambda expression tree
* @return the function type that this lambda targets
*/
public AnnotatedExecutableType getFunctionTypeFromTree(LambdaExpressionTree tree) {
return getFnInterfaceFromTree(tree).second;
}
/**
* Returns the functional interface and the function type that this lambda or member references
* targets.
*
*
The function type is the type of the single method declared in the functional interface
* adapted as if it were invoked using the functional interface as the receiver expression.
*
*
The target type of a lambda or a method reference is the type to which it is assigned or
* casted.
*
* @param tree lambda expression tree or member reference tree
* @return the functional interface and the function type that this method reference or lambda
* targets
*/
public Pair getFnInterfaceFromTree(Tree tree) {
// Functional interface
AnnotatedTypeMirror functionalInterfaceType = getFunctionalInterfaceType(tree);
if (functionalInterfaceType.getKind() == TypeKind.DECLARED) {
functionalInterfaceType =
makeGroundTargetType(
(AnnotatedDeclaredType) functionalInterfaceType,
(DeclaredType) TreeUtils.typeOf(tree));
}
// Functional method
Element fnElement = TreeUtils.findFunction(tree, processingEnv);
// Function type
AnnotatedExecutableType functionType =
(AnnotatedExecutableType)
AnnotatedTypes.asMemberOf(types, this, functionalInterfaceType, fnElement);
return Pair.of(functionalInterfaceType, functionType);
}
/**
* Get the AnnotatedDeclaredType for the FunctionalInterface from assignment context of the method
* reference or lambda expression which may be a variable assignment, a method call, or a cast.
*
* The assignment context is not always correct, so we must search up the AST. It will
* recursively search for lambdas nested in lambdas.
*
* @param tree the tree of the lambda or method reference
* @return the functional interface type or an uninferred type argument
*/
private AnnotatedTypeMirror getFunctionalInterfaceType(Tree tree) {
Tree parentTree = getPath(tree).getParentPath().getLeaf();
switch (parentTree.getKind()) {
case PARENTHESIZED:
return getFunctionalInterfaceType(parentTree);
case TYPE_CAST:
TypeCastTree cast = (TypeCastTree) parentTree;
assert isFunctionalInterface(
trees.getTypeMirror(getPath(cast.getType())), parentTree, tree);
AnnotatedTypeMirror castATM = getAnnotatedType(cast.getType());
if (castATM.getKind() == TypeKind.INTERSECTION) {
AnnotatedIntersectionType itype = (AnnotatedIntersectionType) castATM;
for (AnnotatedTypeMirror t : itype.directSupertypes()) {
if (TypesUtils.isFunctionalInterface(t.getUnderlyingType(), getProcessingEnv())) {
return t;
}
}
// We should never reach here: isFunctionalInterface performs the same check
// and would have raised an error already.
throw new BugInCF(
"Expected the type of a cast tree in an assignment context to contain a functional"
+ " interface bound. Found type: %s for tree: %s in lambda tree: %s",
castATM, cast, tree);
}
return castATM;
case NEW_CLASS:
NewClassTree newClass = (NewClassTree) parentTree;
int indexOfLambda = newClass.getArguments().indexOf(tree);
ParameterizedExecutableType con = this.constructorFromUse(newClass);
AnnotatedTypeMirror constructorParam =
AnnotatedTypes.getAnnotatedTypeMirrorOfParameter(con.executableType, indexOfLambda);
assert isFunctionalInterface(constructorParam.getUnderlyingType(), parentTree, tree);
return constructorParam;
case NEW_ARRAY:
NewArrayTree newArray = (NewArrayTree) parentTree;
AnnotatedArrayType newArrayATM = getAnnotatedType(newArray);
AnnotatedTypeMirror elementATM = newArrayATM.getComponentType();
assert isFunctionalInterface(elementATM.getUnderlyingType(), parentTree, tree);
return elementATM;
case METHOD_INVOCATION:
MethodInvocationTree method = (MethodInvocationTree) parentTree;
int index = method.getArguments().indexOf(tree);
ParameterizedExecutableType exe = this.methodFromUse(method);
AnnotatedTypeMirror param =
AnnotatedTypes.getAnnotatedTypeMirrorOfParameter(exe.executableType, index);
if (param.getKind() == TypeKind.WILDCARD) {
// param is an uninferred wildcard.
TypeMirror typeMirror = TreeUtils.typeOf(tree);
param = AnnotatedTypeMirror.createType(typeMirror, this, false);
addDefaultAnnotations(param);
}
assert isFunctionalInterface(param.getUnderlyingType(), parentTree, tree);
return param;
case VARIABLE:
VariableTree varTree = (VariableTree) parentTree;
assert isFunctionalInterface(TreeUtils.typeOf(varTree), parentTree, tree);
return getAnnotatedType(varTree.getType());
case ASSIGNMENT:
AssignmentTree assignmentTree = (AssignmentTree) parentTree;
assert isFunctionalInterface(TreeUtils.typeOf(assignmentTree), parentTree, tree);
return getAnnotatedType(assignmentTree.getVariable());
case RETURN:
Tree enclosing =
TreePathUtil.enclosingOfKind(
getPath(parentTree),
new HashSet<>(Arrays.asList(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)));
if (enclosing.getKind() == Tree.Kind.METHOD) {
MethodTree enclosingMethod = (MethodTree) enclosing;
return getAnnotatedType(enclosingMethod.getReturnType());
} else {
LambdaExpressionTree enclosingLambda = (LambdaExpressionTree) enclosing;
AnnotatedExecutableType methodExe = getFunctionTypeFromTree(enclosingLambda);
return methodExe.getReturnType();
}
case LAMBDA_EXPRESSION:
LambdaExpressionTree enclosingLambda = (LambdaExpressionTree) parentTree;
AnnotatedExecutableType methodExe = getFunctionTypeFromTree(enclosingLambda);
return methodExe.getReturnType();
case CONDITIONAL_EXPRESSION:
ConditionalExpressionTree conditionalExpressionTree =
(ConditionalExpressionTree) parentTree;
final AnnotatedTypeMirror falseType =
getAnnotatedType(conditionalExpressionTree.getFalseExpression());
final AnnotatedTypeMirror trueType =
getAnnotatedType(conditionalExpressionTree.getTrueExpression());
// Known cases where we must use LUB because falseType/trueType will not be equal:
// a) when one of the types is a type variable that extends a functional interface
// or extends a type variable that extends a functional interface
// b) When one of the two sides of the expression is a reference to a sub-interface.
// e.g. interface ConsumeStr {
// public void consume(String s)
// }
// interface SubConsumer extends ConsumeStr {
// default void someOtherMethod() { ... }
// }
// SubConsumer s = ...;
// ConsumeStr stringConsumer = (someCondition) ? s : System.out::println;
AnnotatedTypeMirror conditionalType =
AnnotatedTypes.leastUpperBound(this, trueType, falseType);
assert isFunctionalInterface(conditionalType.getUnderlyingType(), parentTree, tree);
return conditionalType;
default:
throw new BugInCF(
"Could not find functional interface from assignment context. "
+ "Unexpected tree type: "
+ parentTree.getKind()
+ " For lambda tree: "
+ tree);
}
}
private boolean isFunctionalInterface(TypeMirror typeMirror, Tree contextTree, Tree tree) {
if (typeMirror.getKind() == TypeKind.WILDCARD) {
// Ignore wildcards, because they are uninferred type arguments.
return true;
}
Type type = (Type) typeMirror;
if (!TypesUtils.isFunctionalInterface(type, processingEnv)) {
if (type.getKind() == TypeKind.INTERSECTION) {
IntersectionType itype = (IntersectionType) type;
for (TypeMirror t : itype.getBounds()) {
if (TypesUtils.isFunctionalInterface(t, processingEnv)) {
// As long as any of the bounds is a functional interface
// we should be fine.
return true;
}
}
}
throw new BugInCF(
"Expected the type of %s tree in assignment context to be a functional interface. "
+ "Found type: %s for tree: %s in lambda tree: %s",
contextTree.getKind(), type, contextTree, tree);
}
return true;
}
/**
* Create the ground target type of the functional interface.
*
*
Basically, it replaces the wildcards with their bounds doing a capture conversion like glb
* for extends bounds.
*
* @see "JLS 9.9"
* @param functionalType the functional interface type
* @param groundTargetJavaType the Java type as found by javac
* @return the grounded functional type
*/
private AnnotatedDeclaredType makeGroundTargetType(
AnnotatedDeclaredType functionalType, DeclaredType groundTargetJavaType) {
if (functionalType.getTypeArguments().isEmpty()) {
return functionalType;
}
List bounds =
this.typeVariablesFromUse(
functionalType, (TypeElement) functionalType.getUnderlyingType().asElement());
boolean sizesDiffer =
functionalType.getTypeArguments().size() != groundTargetJavaType.getTypeArguments().size();
// This is the declared type of the functional type meaning that the type arguments are the
// type parameters.
DeclaredType declaredType =
(DeclaredType) functionalType.getUnderlyingType().asElement().asType();
Map typeVarToTypeArg =
new HashMap<>(functionalType.getTypeArguments().size());
for (int i = 0; i < functionalType.getTypeArguments().size(); i++) {
TypeVariable typeVariable = (TypeVariable) declaredType.getTypeArguments().get(i);
AnnotatedTypeMirror argType = functionalType.getTypeArguments().get(i);
if (argType.getKind() == TypeKind.WILDCARD) {
AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) argType;
TypeMirror wildcardUbType = wildcardType.getExtendsBound().getUnderlyingType();
if (wildcardType.isUninferredTypeArgument()) {
// Keep the uninferred type so that it is ignored by later subtyping and containment
// checks.
typeVarToTypeArg.put(typeVariable, wildcardType);
} else if (isExtendsWildcard(wildcardType)) {
TypeMirror correctArgType;
if (sizesDiffer) {
// The Java type is raw.
TypeMirror typeParamUbType = bounds.get(i).getUpperBound().getUnderlyingType();
correctArgType =
TypesUtils.greatestLowerBound(
typeParamUbType, wildcardUbType, this.checker.getProcessingEnvironment());
} else {
correctArgType = groundTargetJavaType.getTypeArguments().get(i);
}
final AnnotatedTypeMirror newArg;
if (types.isSameType(wildcardUbType, correctArgType)) {
newArg = wildcardType.getExtendsBound().deepCopy();
} else if (correctArgType.getKind() == TypeKind.TYPEVAR) {
newArg = this.toAnnotatedType(correctArgType, false);
AnnotatedTypeVariable newArgAsTypeVar = (AnnotatedTypeVariable) newArg;
newArgAsTypeVar
.getUpperBound()
.replaceAnnotations(wildcardType.getExtendsBound().getAnnotations());
newArgAsTypeVar
.getLowerBound()
.replaceAnnotations(wildcardType.getSuperBound().getAnnotations());
} else {
newArg = this.toAnnotatedType(correctArgType, false);
newArg.replaceAnnotations(wildcardType.getExtendsBound().getAnnotations());
}
typeVarToTypeArg.put(typeVariable, newArg);
} else {
typeVarToTypeArg.put(typeVariable, wildcardType.getSuperBound());
}
} else {
typeVarToTypeArg.put(typeVariable, argType);
}
}
// The ground functional type must be created using type variable substitution or else the
// underlying type will not match the annotated type.
AnnotatedDeclaredType groundFunctionalType =
(AnnotatedDeclaredType)
AnnotatedTypeMirror.createType(declaredType, this, functionalType.isDeclaration());
initializeAtm(groundFunctionalType);
groundFunctionalType =
(AnnotatedDeclaredType)
getTypeVarSubstitutor().substitute(typeVarToTypeArg, groundFunctionalType);
groundFunctionalType.addAnnotations(functionalType.getAnnotations());
// When the groundTargetJavaType is different from the underlying type of functionalType, only
// the main annotations are copied. Add default annotations in places without annotations.
addDefaultAnnotations(groundFunctionalType);
return groundFunctionalType;
}
/**
* Return true if {@code type} should be captured.
*
* {@code type} should be captured if all of the following are true:
*
*
* - {@code type} and {@code typeMirror} are both declared types.
*
- {@code type} does not have an uninferred type argument and its underlying type is not
* raw.
*
- {@code type} has a wildcard as a type argument and {@code typeMirror} has a captured type
* variable as the corresponding type argument.
*
*
* @param type annotated type that might need to be captured
* @param typeMirror the capture of the underlying type of {@code type}
* @return true if {@code type} should be captured
*/
private boolean shouldCapture(AnnotatedTypeMirror type, TypeMirror typeMirror) {
if (type.getKind() != TypeKind.DECLARED || typeMirror.getKind() != TypeKind.DECLARED) {
return false;
}
AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type;
DeclaredType capturedTypeMirror = (DeclaredType) typeMirror;
if (capturedTypeMirror.getTypeArguments().isEmpty()) {
return false;
}
if (uncapturedType.isUnderlyingTypeRaw() || uncapturedType.containsUninferredTypeArguments()) {
return false;
}
if (capturedTypeMirror.getTypeArguments().size() != uncapturedType.getTypeArguments().size()) {
throw new BugInCF(
"Not the same number of type arguments: capturedTypeMirror: %s uncapturedType: %s",
capturedTypeMirror, uncapturedType);
}
for (int i = 0; i < capturedTypeMirror.getTypeArguments().size(); i++) {
TypeMirror capturedTypeArgTM = capturedTypeMirror.getTypeArguments().get(i);
AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i);
if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD
&& (TypesUtils.isCapturedTypeVariable(capturedTypeArgTM)
|| capturedTypeArgTM.getKind() != TypeKind.WILDCARD)) {
return true;
}
}
return false;
}
/**
* Apply capture conversion to {@code typeToCapture}.
*
* Capture conversion is the process of converting wildcards in a parameterized type to fresh
* type variables. See JLS 5.1.10
* for details.
*
*
If {@code type} is not a declared type or if it does not have any wildcard type arguments,
* this method returns {@code type}.
*
* @param typeToCapture type to capture
* @return the result of applying capture conversion to {@code typeToCapture}
*/
public AnnotatedTypeMirror applyCaptureConversion(AnnotatedTypeMirror typeToCapture) {
TypeMirror capturedTypeMirror = types.capture(typeToCapture.getUnderlyingType());
return applyCaptureConversion(typeToCapture, capturedTypeMirror);
}
/**
* Apply capture conversion to {@code type}.
*
*
Capture conversion is the process of converting wildcards in a parameterized type to fresh
* type variables. See JLS 5.1.10
* for details.
*
*
If {@code type} is not a declared type or if it does not have any wildcard type arguments,
* this method returns {@code type}.
*
* @param type type to capture
* @param typeMirror the result of applying capture conversion to the underlying type of {@code
* type}; it is used as the underlying type of the returned type
* @return the result of applying capture conversion to {@code type}
*/
public AnnotatedTypeMirror applyCaptureConversion(
AnnotatedTypeMirror type, TypeMirror typeMirror) {
// If the type contains uninferred type arguments, don't capture, but mark all wildcards that
// shuuld have been captured as "uninferred" before it is returned.
if (type.containsUninferredTypeArguments()
&& typeMirror.getKind() == TypeKind.DECLARED
&& type.getKind() == TypeKind.DECLARED) {
AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type;
DeclaredType capturedTypeMirror = (DeclaredType) typeMirror;
for (int i = 0; i < capturedTypeMirror.getTypeArguments().size(); i++) {
AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i);
TypeMirror capturedTypeArgTM = capturedTypeMirror.getTypeArguments().get(i);
if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD
&& (TypesUtils.isCapturedTypeVariable(capturedTypeArgTM)
|| capturedTypeArgTM.getKind() != TypeKind.WILDCARD)) {
((AnnotatedWildcardType) uncapturedTypeArg).setUninferredTypeArgument();
}
}
return type;
}
if (!shouldCapture(type, typeMirror)) {
return type;
}
AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type;
DeclaredType capturedTypeMirror = (DeclaredType) typeMirror;
// `capturedType` is the return value of this method.
AnnotatedDeclaredType capturedType =
(AnnotatedDeclaredType) AnnotatedTypeMirror.createType(capturedTypeMirror, this, false);
nonWildcardTypeArgCopier.copy(uncapturedType, capturedType);
AnnotatedDeclaredType typeDeclaration =
(AnnotatedDeclaredType) getAnnotatedType(uncapturedType.getUnderlyingType().asElement());
// A mapping from type variable to its type argument in the captured type.
Map typeVarToAnnotatedTypeArg = new HashMap<>();
// A mapping from a captured type variable to the annotated captured type variable.
Map capturedTypeVarToAnnotatedTypeVar = new HashMap<>();
// `newTypeArgs` will be the type arguments of the result of this method.
List newTypeArgs = new ArrayList<>();
for (int i = 0; i < typeDeclaration.getTypeArguments().size(); i++) {
TypeVariable typeVarTypeMirror =
(TypeVariable) typeDeclaration.getTypeArguments().get(i).getUnderlyingType();
AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i);
AnnotatedTypeMirror capturedTypeArg = capturedType.getTypeArguments().get(i);
if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD) {
// The type argument is a captured type variable. Use the type argument from the newly
// created and yet-to-be annotated capturedType. (The annotations are added by
// #annotateCapturedTypeVar, which is called at the end of this method.)
typeVarToAnnotatedTypeArg.put(typeVarTypeMirror, capturedTypeArg);
newTypeArgs.add(capturedTypeArg);
if (TypesUtils.isCapturedTypeVariable(capturedTypeArg.getUnderlyingType())) {
// Also, add a mapping from the captured type variable to the annotated captured
// type variable, so that if one captured type variable refers to another, the same
// AnnotatedTypeVariable object is used.
capturedTypeVarToAnnotatedTypeVar.put(
((AnnotatedTypeVariable) capturedTypeArg).getUnderlyingType(),
(AnnotatedTypeVariable) capturedTypeArg);
} else {
// Javac used a declared type instead of a captured type variable. This seems to happen
// when the bounds of the captured type variable would have been identical. This seems to
// be a violation of the JLS, but javac does this, so the Checker Framework must handle
// that case. (See https://bugs.openjdk.java.net/browse/JDK-8054309.)
replaceAnnotations(
((AnnotatedWildcardType) uncapturedTypeArg).getSuperBound(), capturedTypeArg);
}
} else {
// The type argument is not a wildcard.
// typeVarTypeMirror is the type parameter for which uncapturedTypeArg is a type argument.
typeVarToAnnotatedTypeArg.put(typeVarTypeMirror, uncapturedTypeArg);
if (uncapturedTypeArg.getKind() == TypeKind.TYPEVAR) {
// If the type arg is a type variable also add it to the typeVarToAnnotatedTypeArg map, so
// that references to the type variable are substituted.
AnnotatedTypeVariable typeVar = (AnnotatedTypeVariable) uncapturedTypeArg;
typeVarToAnnotatedTypeArg.put(typeVar.getUnderlyingType(), typeVar);
}
newTypeArgs.add(uncapturedTypeArg);
}
}
// Set the annotations of each captured type variable.
List orderToCapture = order(capturedTypeVarToAnnotatedTypeVar.values());
for (AnnotatedTypeVariable capturedTypeArg : orderToCapture) {
int i = capturedTypeMirror.getTypeArguments().indexOf(capturedTypeArg.getUnderlyingType());
AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i);
AnnotatedTypeVariable typeVariable =
(AnnotatedTypeVariable) typeDeclaration.getTypeArguments().get(i);
annotateCapturedTypeVar(
typeVarToAnnotatedTypeArg,
capturedTypeVarToAnnotatedTypeVar,
(AnnotatedWildcardType) uncapturedTypeArg,
typeVariable,
capturedTypeArg);
newTypeArgs.set(i, capturedTypeArg);
}
capturedType.setTypeArguments(newTypeArgs);
capturedType.addAnnotations(uncapturedType.getAnnotations());
return capturedType;
}
/**
* Copy the non-wildcard type args from a uncapturedType to its capturedType. Also, ensure that
* type variables in capturedType are the same object when they are refer to the same type
* variable.
*
* To use, call {@link NonWildcardTypeArgCopier#copy} rather than a visit method.
*/
private final NonWildcardTypeArgCopier nonWildcardTypeArgCopier = new NonWildcardTypeArgCopier();
/**
* Copy the non-wildcard type args from {@code uncapturedType} to {@code capturedType}. Also,
* ensure that type variables in {@code capturedType} are the same object when they refer to the
* same type variable.
*
*
To use, call {@link NonWildcardTypeArgCopier#copy} rather than a visit method.
*/
private class NonWildcardTypeArgCopier extends AnnotatedTypeCopier {
/**
* Copy the non-wildcard type args from {@code uncapturedType} to {@code capturedType}. Also,
* ensure that type variables {@code capturedType} are the same object when they are refer to
* the same type variable.
*
* @param uncapturedType a declared type that has not under gone capture conversion
* @param capturedType the captured version of {@code uncapturedType} before it has been
* annotated
*/
private void copy(AnnotatedDeclaredType uncapturedType, AnnotatedDeclaredType capturedType) {
// The name "originalToCopy" means a mapping from the original to the copy, not an original
// that needs to be copied.
IdentityHashMap originalToCopy =
new IdentityHashMap<>();
originalToCopy.put(uncapturedType, capturedType);
int numTypeArgs = uncapturedType.getTypeArguments().size();
AnnotatedTypeMirror[] newTypeArgs = new AnnotatedTypeMirror[numTypeArgs];
// Mapping from type var to it's AnnotatedTypeVariable. These are type variables
// that are type arguments of the uncaptured type.
Map typeVarToAnnotatedTypeVar = new HashMap<>(numTypeArgs);
// Copy the non-wildcard type args from uncapturedType to newTypeArgs.
// If the non-wildcard type arg is a type var, add it to typeVarToAnnotatedTypeVar.
for (int i = 0; i < numTypeArgs; i++) {
AnnotatedTypeMirror uncapturedArg = uncapturedType.getTypeArguments().get(i);
if (uncapturedArg.getKind() != TypeKind.WILDCARD) {
AnnotatedTypeMirror copyOfArg = visit(uncapturedArg, originalToCopy);
newTypeArgs[i] = copyOfArg;
if (copyOfArg.getKind() == TypeKind.TYPEVAR) {
typeVarToAnnotatedTypeVar.put(
((AnnotatedTypeVariable) copyOfArg).getUnderlyingType(), copyOfArg);
}
}
}
// Substitute the type variables in each type argument of capturedType using
// typeVarToAnnotatedTypeVar.
// This makes type variables in capturedType the same object when they are the same type
// variable.
for (int i = 0; i < numTypeArgs; i++) {
AnnotatedTypeMirror uncapturedArg = uncapturedType.getTypeArguments().get(i);
AnnotatedTypeMirror capturedArg = capturedType.getTypeArguments().get(i);
// Note: This `if` statement can't be replaced with
// if (TypesUtils.isCapturedTypeVariable(capturedArg))
// because if the bounds of the captured wildcard are equal, then instead of a captured
// wildcard, the type of the bound is used.
if (uncapturedArg.getKind() == TypeKind.WILDCARD) {
AnnotatedTypeMirror newCapArg =
typeVarSubstitutor.substituteWithoutCopyingTypeArguments(
typeVarToAnnotatedTypeVar, capturedArg);
newTypeArgs[i] = newCapArg;
}
}
// Set capturedType type args to newTypeArgs.
capturedType.setTypeArguments(Arrays.asList(newTypeArgs));
// Visit the enclosing type.
if (uncapturedType.getEnclosingType() != null) {
capturedType.setEnclosingType(
(AnnotatedDeclaredType) visit(uncapturedType.getEnclosingType(), originalToCopy));
}
}
}
/**
* Returns the list of type variables such that a type variable in the list only references type
* variables at a lower index than itself.
*
* @param collection a collection of type variables
* @return the type variables ordered so that each type variable only references earlier type
* variables
*/
public List order(Collection collection) {
List list = new ArrayList<>(collection);
List ordered = new ArrayList<>();
while (!list.isEmpty()) {
AnnotatedTypeVariable free = doesNotContainOthers(list);
list.remove(free);
ordered.add(free);
}
return ordered;
}
/**
* Returns the first TypeVariable in {@code collection} that does not lexically contain any other
* type in the collection. Or if all the TypeVariables contain another, then it returns the first
* TypeVariable in {@code collection}.
*
* @param collection a collection of type variables
* @return the first TypeVariable in {@code collection} that does not contain any other type in
* the collection, except possibly itself
*/
@SuppressWarnings("interning:not.interned") // must be the same object from collection
private AnnotatedTypeVariable doesNotContainOthers(
Collection collection) {
AnnotatedTypeVariable first = null;
for (AnnotatedTypeVariable candidate : collection) {
if (first == null) {
first = candidate;
}
boolean doesNotContain = true;
for (AnnotatedTypeVariable other : collection) {
if (candidate != other && captureScanner.visit(candidate, other.getUnderlyingType())) {
doesNotContain = false;
break;
}
}
if (doesNotContain) {
return candidate;
}
}
return first;
}
/**
* Scanner that returns true if the underlying type of any part of an {@link AnnotatedTypeMirror}
* is the passed captured type variable.
*
* The second argument to visit must be a captured type variable.
*/
@SuppressWarnings("interning:not.interned") // Captured type vars can be compared with ==.
private final SimpleAnnotatedTypeScanner captureScanner =
new SimpleAnnotatedTypeScanner<>(
(type, other) -> type.getUnderlyingType() == other, Boolean::logicalOr, false);
/**
* Set the annotated bounds for fresh type variable {@code capturedTypeVar}, so that it is the
* capture of {@code wildcard}. Also, sets {@code capturedTypeVar} primary annotation if the
* annotation on the bounds is identical.
*
* @param typeVarToAnnotatedTypeArg mapping from a (type mirror) type variable to its (annotated
* type mirror) type argument
* @param capturedTypeVarToAnnotatedTypeVar mapping from a captured type variable to its {@link
* AnnotatedTypeMirror}
* @param wildcard wildcard which is converted to {@code capturedTypeVar}
* @param typeVariable type variable for which {@code wildcard} is a type argument
* @param capturedTypeVar the fresh type variable which is side-effected by this method
*/
private void annotateCapturedTypeVar(
Map typeVarToAnnotatedTypeArg,
Map capturedTypeVarToAnnotatedTypeVar,
AnnotatedWildcardType wildcard,
AnnotatedTypeVariable typeVariable,
AnnotatedTypeVariable capturedTypeVar) {
AnnotatedTypeMirror typeVarUpperBound =
typeVarSubstitutor.substituteWithoutCopyingTypeArguments(
typeVarToAnnotatedTypeArg, typeVariable.getUpperBound());
AnnotatedTypeMirror upperBound =
AnnotatedTypes.annotatedGLB(this, typeVarUpperBound, wildcard.getExtendsBound());
if (upperBound.getKind() == TypeKind.INTERSECTION
&& capturedTypeVar.getUpperBound().getKind() != TypeKind.INTERSECTION) {
// There is a bug in javac such that the upper bound of the captured type variable is not the
// greatest lower bound. So the captureTypeVar.getUnderlyingType().getUpperBound() may not
// be the same type as upperbound.getUnderlyingType(). See
// framework/tests/all-systems/Issue4890Interfaces.java,
// framework/tests/all-systems/Issue4890.java and framework/tests/all-systems/Issue4877.java.
// (I think this is https://bugs.openjdk.java.net/browse/JDK-8039222.)
for (AnnotatedTypeMirror bound : ((AnnotatedIntersectionType) upperBound).getBounds()) {
if (types.isSameType(
bound.underlyingType, capturedTypeVar.getUpperBound().getUnderlyingType())) {
upperBound = bound;
}
}
}
capturedTypeVar.setUpperBound(upperBound);
// typeVariable's lower bound is a NullType, so there's nothing to substitute.
AnnotatedTypeMirror lowerBound =
AnnotatedTypes.leastUpperBound(
this, typeVariable.getLowerBound(), wildcard.getSuperBound());
capturedTypeVar.setLowerBound(lowerBound);
// Add as a primary annotation any qualifiers that are the same on the upper and lower bound.
AnnotationMirrorSet p =
new AnnotationMirrorSet(capturedTypeVar.getUpperBound().getAnnotations());
p.retainAll(capturedTypeVar.getLowerBound().getAnnotations());
capturedTypeVar.replaceAnnotations(p);
capturedTypeVarSubstitutor.substitute(capturedTypeVar, capturedTypeVarToAnnotatedTypeVar);
}
/**
* Substitutes references to captured type variables.
*
* Unlike {@link #typeVarSubstitutor}, this class does not copy the type. Call {@code
* substitute} to use.
*/
private final CapturedTypeVarSubstitutor capturedTypeVarSubstitutor =
new CapturedTypeVarSubstitutor();
/**
* Substitutes references to captured types in {@code type} using {@code
* capturedTypeVarToAnnotatedTypeVar}.
*
*
Unlike {@link #typeVarSubstitutor}, this class does not copy the type. Call {@code
* substitute} to use.
*/
private static class CapturedTypeVarSubstitutor extends AnnotatedTypeCopier {
/** A mapping from a captured type variable to its AnnotatedTypeVariable. */
private Map capturedTypeVarToAnnotatedTypeVar;
/**
* Substitutes references to captured type variable in {@code type} using {@code
* capturedTypeVarToAnnotatedTypeVar}.
*
* Unlike {@link #typeVarSubstitutor}, this method does not copy the type.
*
* @param type AnnotatedTypeMirror whose captured type variables are substituted with those in
* {@code capturedTypeVarToAnnotatedTypeVar}
* @param capturedTypeVarToAnnotatedTypeVar mapping from TypeVariable, that is a captured type
* variable, to an AnnotatedTypeVariable
*/
private void substitute(
AnnotatedTypeVariable type,
Map capturedTypeVarToAnnotatedTypeVar) {
this.capturedTypeVarToAnnotatedTypeVar = capturedTypeVarToAnnotatedTypeVar;
IdentityHashMap mapping = new IdentityHashMap<>();
visit(type.getLowerBound(), mapping);
visit(type.getUpperBound(), mapping);
}
@Override
public AnnotatedTypeMirror visitTypeVariable(
AnnotatedTypeVariable original,
IdentityHashMap originalToCopy) {
AnnotatedTypeMirror cap = capturedTypeVarToAnnotatedTypeVar.get(original.getUnderlyingType());
if (cap != null) {
return cap;
}
return super.visitTypeVariable(original, originalToCopy);
}
@Override
protected T makeOrReturnCopy(
T original, IdentityHashMap originalToCopy) {
AnnotatedTypeMirror copy = originalToCopy.get(original);
if (copy != null) {
@SuppressWarnings(
"unchecked" // the key-value pairs in originalToCopy are always the same kind of
// AnnotatedTypeMirror.
)
T copyCasted = (T) copy;
return copyCasted;
}
if (original.getKind() == TypeKind.TYPEVAR) {
AnnotatedTypeMirror captureType =
capturedTypeVarToAnnotatedTypeVar.get(
((AnnotatedTypeVariable) original).getUnderlyingType());
if (captureType != null) {
originalToCopy.put(original, captureType);
@SuppressWarnings(
"unchecked" // the key-value pairs in originalToCopy are always the same kind of
// AnnotatedTypeMirror.
)
T captureTypeCasted = (T) captureType;
return captureTypeCasted;
}
}
originalToCopy.put(original, original);
return original;
}
}
/**
* Check that a wildcard is an extends wildcard.
*
* @param awt the wildcard type
* @return true if awt is an extends wildcard
*/
private boolean isExtendsWildcard(AnnotatedWildcardType awt) {
return awt.getUnderlyingType().getSuperBound() == null;
}
/** Accessor for the element utilities. */
public Elements getElementUtils() {
return this.elements;
}
/** Accessor for the tree utilities. */
public Trees getTreeUtils() {
return this.trees;
}
/** Accessor for the processing environment. */
public ProcessingEnvironment getProcessingEnv() {
return this.processingEnv;
}
/** Matches addition of a constant. */
static final Pattern plusConstant = Pattern.compile(" *\\+ *(-?[0-9]+)$");
/** Matches subtraction of a constant. */
static final Pattern minusConstant = Pattern.compile(" *- *(-?[0-9]+)$");
/** Matches a string whose only parens are at the beginning and end of the string. */
private static Pattern surroundingParensPattern = Pattern.compile("^\\([^()]\\)");
/**
* Given an expression, split it into a subexpression and a constant offset. For example:
*
* {@code
* "a" => <"a", "0">
* "a + 5" => <"a", "5">
* "a + -5" => <"a", "-5">
* "a - 5" => <"a", "-5">
* }
*
* There are methods that can only take as input an expression that represents a JavaExpression.
* The purpose of this is to pre-process expressions to make those methods more likely to succeed.
*
* @param expression an expression to remove a constant offset from
* @return a sub-expression and a constant offset. The offset is "0" if this routine is unable to
* splite the given expression
*/
// TODO: generalize. There is no reason this couldn't handle arbitrary addition and subtraction
// expressions, given the Index Checker's support for OffsetEquation. That might even make its
// implementation simpler.
public static Pair getExpressionAndOffset(String expression) {
String expr = expression;
String offset = "0";
// Is this normalization necessary?
// Remove surrounding whitespace.
expr = expr.trim();
// Remove surrounding parentheses.
if (surroundingParensPattern.matcher(expr).matches()) {
expr = expr.substring(1, expr.length() - 2).trim();
}
Matcher mPlus = plusConstant.matcher(expr);
Matcher mMinus = minusConstant.matcher(expr);
if (mPlus.find()) {
expr = expr.substring(0, mPlus.start());
offset = mPlus.group(1);
} else if (mMinus.find()) {
expr = expr.substring(0, mMinus.start());
offset = negateConstant(mMinus.group(1));
}
if (offset.equals("-0")) {
offset = "0";
}
expr = expr.intern();
offset = offset.intern();
return Pair.of(expr, offset);
}
/**
* Given an expression string, returns its negation.
*
* @param constantExpression a string representing an integer constant
* @return the negation of constantExpression
*/
// Also see Subsequence.negateString which is similar but more sophisticated.
public static String negateConstant(String constantExpression) {
if (constantExpression.startsWith("-")) {
return constantExpression.substring(1);
} else {
if (constantExpression.startsWith("+")) {
constantExpression = constantExpression.substring(1);
}
return "-" + constantExpression;
}
}
/**
* Returns {@code null} or an annotated type mirror that type argument inference should assume
* {@code expressionTree} is assigned to.
*
* If {@code null} is returned, inference proceeds normally.
*
*
If a type is returned, then inference assumes that {@code expressionTree} was asigned to it.
* This biases the inference algorithm toward the annotations in the returned type. In particular,
* if the annotations on type variables in invariant positions are a super type of the annotations
* inferred, the super type annotations are chosen.
*
*
This implementation returns null, but subclasses may override this method to return a type.
*
* @param expressionTree an expression which has no assignment context and for which type
* arguments need to be inferred
* @return {@code null} or an annotated type mirror that inferrence should pretend {@code
* expressionTree} is assigned to
*/
public @Nullable AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) {
return null;
}
/**
* Checks that the annotation {@code am} has the name of {@code annoClass}. Values are ignored.
*
*
This method is faster than {@link AnnotationUtils#areSameByClass(AnnotationMirror, Class)}
* because it caches the name of the class rather than computing it each time.
*
* @param am the AnnotationMirror whose class to compare
* @param annoClass the class to compare
* @return true if annoclass is the class of am
*/
public boolean areSameByClass(AnnotationMirror am, Class annoClass) {
if (!shouldCache) {
return AnnotationUtils.areSameByName(am, annoClass.getCanonicalName());
}
@SuppressWarnings("nullness") // assume getCanonicalName returns non-null
String canonicalName = annotationClassNames.computeIfAbsent(annoClass, Class::getCanonicalName);
return AnnotationUtils.areSameByName(am, canonicalName);
}
/**
* Checks that the collection contains the annotation. Using Collection.contains does not always
* work, because it does not use areSame for comparison.
*
*
This method is faster than {@link AnnotationUtils#containsSameByClass(Collection, Class)}
* because is caches the name of the class rather than computing it each time.
*
* @param c a collection of AnnotationMirrors
* @param anno the annotation class to search for in c
* @return true iff c contains anno, according to areSameByClass
*/
public boolean containsSameByClass(
Collection c, Class anno) {
return getAnnotationByClass(c, anno) != null;
}
/**
* Returns the AnnotationMirror in {@code c} that has the same class as {@code anno}.
*
*
This method is faster than {@link AnnotationUtils#getAnnotationByClass(Collection, Class)}
* because is caches the name of the class rather than computing it each time.
*
* @param c a collection of AnnotationMirrors
* @param anno the class to search for in c
* @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according to
* areSameByClass; otherwise, {@code null}
*/
public @Nullable AnnotationMirror getAnnotationByClass(
Collection c, Class anno) {
for (AnnotationMirror an : c) {
if (areSameByClass(an, anno)) {
return an;
}
}
return null;
}
/**
* Changes the type of {@code rhsATM} when being assigned to a field, for use by whole-program
* inference. The default implementation does nothing.
*
* @param lhsTree the tree for the field whose type will be changed
* @param element the element for the field whose type will be changed
* @param fieldName the name of the field whose type will be changed
* @param rhsATM the type of the expression being assigned to the field, which is side-effected by
* this method
*/
public void wpiAdjustForUpdateField(
Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) {}
/**
* Changes the type of {@code rhsATM} when being assigned to anything other than a field, for use
* by whole-program inference. The default implementation does nothing.
*
* @param rhsATM the type of the rhs of the pseudo-assignment, which is side-effected by this
* method
*/
public void wpiAdjustForUpdateNonField(AnnotatedTypeMirror rhsATM) {}
/**
* Side-effects the method or constructor annotations to make any desired changes before writing
* to an annotation file.
*
* @param methodAnnos the method or constructor annotations to modify
*/
public void prepareMethodForWriting(AMethod methodAnnos) {
// This implementation does nothing.
}
/**
* Side-effects the method or constructor annotations to make any desired changes before writing
* to an ajava file.
*
* @param methodAnnos the method or constructor annotations to modify
*/
public void prepareMethodForWriting(
WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) {
// This implementation does nothing.
}
/**
* Does {@code anno}, which is an {@link org.checkerframework.framework.qual.AnnotatedFor}
* annotation, apply to this checker?
*
* @param annotatedForAnno an {@link AnnotatedFor} annotation
* @return whether {@code anno} applies to this checker
*/
public boolean doesAnnotatedForApplyToThisChecker(AnnotationMirror annotatedForAnno) {
List annotatedForCheckers =
AnnotationUtils.getElementValueArray(
annotatedForAnno, annotatedForValueElement, String.class);
for (String annoForChecker : annotatedForCheckers) {
if (checker.getUpstreamCheckerNames().contains(annoForChecker)
|| CheckerMain.matchesFullyQualifiedProcessor(
annoForChecker, checker.getUpstreamCheckerNames(), true)) {
return true;
}
}
return false;
}
/**
* Get the {@code expression} field/element of the given contract annotation.
*
* @param contractAnno a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link
* EnsuresQualifier}
* @return the {@code expression} field/element of the given annotation
*/
public List getContractExpressions(AnnotationMirror contractAnno) {
DeclaredType annoType = contractAnno.getAnnotationType();
if (types.isSameType(annoType, requiresQualifierTM)) {
return AnnotationUtils.getElementValueArray(
contractAnno, requiresQualifierExpressionElement, String.class);
} else if (types.isSameType(annoType, ensuresQualifierTM)) {
return AnnotationUtils.getElementValueArray(
contractAnno, ensuresQualifierExpressionElement, String.class);
} else if (types.isSameType(annoType, ensuresQualifierIfTM)) {
return AnnotationUtils.getElementValueArray(
contractAnno, ensuresQualifierIfExpressionElement, String.class);
} else {
throw new BugInCF("Not a contract annotation: " + contractAnno);
}
}
/**
* Get the {@code value} field/element of the given contract list annotation.
*
* @param contractListAnno a {@link RequiresQualifier.List}, {@link EnsuresQualifier.List}, or
* {@link EnsuresQualifier.List}
* @return the {@code value} field/element of the given annotation
*/
public List getContractListValues(AnnotationMirror contractListAnno) {
DeclaredType annoType = contractListAnno.getAnnotationType();
if (types.isSameType(annoType, requiresQualifierListTM)) {
return AnnotationUtils.getElementValueArray(
contractListAnno, requiresQualifierListValueElement, AnnotationMirror.class);
} else if (types.isSameType(annoType, ensuresQualifierListTM)) {
return AnnotationUtils.getElementValueArray(
contractListAnno, ensuresQualifierListValueElement, AnnotationMirror.class);
} else if (types.isSameType(annoType, ensuresQualifierIfListTM)) {
return AnnotationUtils.getElementValueArray(
contractListAnno, ensuresQualifierIfListValueElement, AnnotationMirror.class);
} else {
throw new BugInCF("Not a contract list annotation: " + contractListAnno);
}
}
/**
* Returns true if the type is immutable. Subclasses can override this method to add types that
* are mutable, but the annotated type of an object is immutable.
*
* @param type type to test.
* @return true if the type is immutable
*/
public boolean isImmutable(TypeMirror type) {
if (type.getKind().isPrimitive()) {
return true;
}
return ImmutableTypes.isImmutable(TypeAnnotationUtils.unannotatedType(type).toString());
}
}