blob: 1e7041452e12fd6d26cc69216d3978ed31334402 [file] [log] [blame]
package org.checkerframework.framework.type;
import com.google.common.collect.Ordering;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
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.NewClassTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import java.lang.annotation.Annotation;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.StringJoiner;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.checkerframework.checker.formatter.qual.FormatMethod;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.wholeprograminference.WholeProgramInferenceImplementation;
import org.checkerframework.common.wholeprograminference.WholeProgramInferenceJavaParserStorage;
import org.checkerframework.common.wholeprograminference.WholeProgramInferenceScenesStorage;
import org.checkerframework.dataflow.analysis.Analysis;
import org.checkerframework.dataflow.analysis.Analysis.BeforeOrAfter;
import org.checkerframework.dataflow.analysis.AnalysisResult;
import org.checkerframework.dataflow.analysis.TransferInput;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.ControlFlowGraph;
import org.checkerframework.dataflow.cfg.UnderlyingAST;
import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGLambda;
import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod;
import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGStatement;
import org.checkerframework.dataflow.cfg.node.AssignmentNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.ObjectCreationNode;
import org.checkerframework.dataflow.cfg.node.ReturnNode;
import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer;
import org.checkerframework.dataflow.cfg.visualize.DOTCFGVisualizer;
import org.checkerframework.dataflow.expression.FieldAccess;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.expression.LocalVariable;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.flow.CFAbstractStore;
import org.checkerframework.framework.flow.CFAbstractTransfer;
import org.checkerframework.framework.flow.CFAbstractValue;
import org.checkerframework.framework.flow.CFAnalysis;
import org.checkerframework.framework.flow.CFCFGBuilder;
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.flow.CFTransfer;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.framework.qual.DefaultFor;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
import org.checkerframework.framework.qual.EnsuresQualifier;
import org.checkerframework.framework.qual.EnsuresQualifierIf;
import org.checkerframework.framework.qual.MonotonicQualifier;
import org.checkerframework.framework.qual.QualifierForLiterals;
import org.checkerframework.framework.qual.RelevantJavaTypes;
import org.checkerframework.framework.qual.RequiresQualifier;
import org.checkerframework.framework.qual.TypeUseLocation;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.poly.DefaultQualifierPolymorphism;
import org.checkerframework.framework.type.poly.QualifierPolymorphism;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.framework.type.typeannotator.DefaultForTypeAnnotator;
import org.checkerframework.framework.type.typeannotator.DefaultQualifierForUseTypeAnnotator;
import org.checkerframework.framework.type.typeannotator.IrrelevantTypeAnnotator;
import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator;
import org.checkerframework.framework.type.typeannotator.PropagationTypeAnnotator;
import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.Contract;
import org.checkerframework.framework.util.ContractsFromMethod;
import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
import org.checkerframework.framework.util.StringToJavaExpression;
import org.checkerframework.framework.util.defaults.QualifierDefaults;
import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper;
import org.checkerframework.framework.util.dependenttypes.DependentTypesTreeAnnotator;
import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil;
import org.checkerframework.javacutil.AnnotationBuilder;
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.TypeSystemError;
import org.checkerframework.javacutil.TypesUtils;
import org.checkerframework.javacutil.UserError;
import org.plumelib.reflection.Signatures;
import org.plumelib.util.CollectionsPlume;
import org.plumelib.util.SystemPlume;
import scenelib.annotations.el.AField;
import scenelib.annotations.el.AMethod;
/**
* A factory that extends {@link AnnotatedTypeFactory} to optionally use flow-sensitive qualifier
* inference.
*
* <p>It also adds other features: qualifier polymorphism, default annotations via {@link
* DefaultFor}, user-specified defaults via {@link DefaultQualifier}, standardization via {@link
* DependentTypesHelper}, etc. Those features, and {@link #addComputedTypeAnnotations} (other than
* the part related to flow-sensitivity), could and should be in the superclass {@link
* AnnotatedTypeFactory}; it is not clear why they are defined in this class.
*/
public abstract class GenericAnnotatedTypeFactory<
Value extends CFAbstractValue<Value>,
Store extends CFAbstractStore<Value, Store>,
TransferFunction extends CFAbstractTransfer<Value, Store, TransferFunction>,
FlowAnalysis extends CFAbstractAnalysis<Value, Store, TransferFunction>>
extends AnnotatedTypeFactory {
/** Should use flow by default. */
protected static boolean flowByDefault = true;
/** To cache the supported monotonic type qualifiers. */
private Set<Class<? extends Annotation>> supportedMonotonicQuals;
/** to annotate types based on the given tree */
protected TypeAnnotator typeAnnotator;
/** for use in addAnnotationsFromDefaultForType */
private DefaultQualifierForUseTypeAnnotator defaultQualifierForUseTypeAnnotator;
/** for use in addAnnotationsFromDefaultForType */
private DefaultForTypeAnnotator defaultForTypeAnnotator;
/** to annotate types based on the given un-annotated types */
protected TreeAnnotator treeAnnotator;
/** to handle any polymorphic types */
protected QualifierPolymorphism poly;
/** to handle defaults specified by the user */
protected QualifierDefaults defaults;
/** To handle dependent type annotations and contract expressions. */
protected DependentTypesHelper dependentTypesHelper;
/** to handle method pre- and postconditions */
protected ContractsFromMethod contractsUtils;
/**
* The Java types on which users may write this type system's type annotations. null means no
* restrictions. Arrays are handled by separate field {@code #arraysAreRelevant}.
*
* <p>If the relevant type is generic, this contains its erasure.
*
* <p>Although a {@code Class<?>} object exists for every element, this does not contain those
* {@code Class<?>} objects because the elements will be compared to TypeMirrors for which Class
* objects may not exist (they might not be on the classpath).
*/
public @Nullable Set<TypeMirror> relevantJavaTypes;
/**
* Whether users may write type annotations on arrays. Ignored unless {@link #relevantJavaTypes}
* is non-null.
*/
boolean arraysAreRelevant = false;
// Flow related fields
/**
* Should use flow-sensitive type refinement analysis? This value can be changed when an
* AnnotatedTypeMirror without annotations from data flow is required.
*
* @see #getAnnotatedTypeLhs(Tree)
*/
private boolean useFlow;
/** Is this type factory configured to use flow-sensitive type refinement? */
private final boolean everUseFlow;
/**
* Should the local variable default annotation be applied to type variables?
*
* <p>It is initialized to true if data flow is used by the checker. It is set to false when
* getting the assignment context for type argument inference.
*
* @see GenericAnnotatedTypeFactory#getAnnotatedTypeLhsNoTypeVarDefault
*/
private boolean shouldDefaultTypeVarLocals;
/**
* Elements representing variables for which the type of the initializer is being determined in
* order to apply qualifier parameter defaults.
*
* <p>Local variables with a qualifier parameter get their declared type from the type of their
* initializer. Sometimes the initializer's type depends on the type of the variable, such as
* during type variable inference or when a variable is used in its own initializer as in "Object
* o = (o = null)". This creates a circular dependency resulting in infinite recursion. To prevent
* this, variables in this set should not be typed based on their initializer, but by using normal
* defaults.
*
* <p>This set should only be modified in
* GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults which clears variables
* after computing their initializer types.
*
* @see GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults
*/
private Set<VariableElement> variablesUnderInitialization;
/**
* Caches types of initializers for local variables with a qualifier parameter, so that they
* aren't computed each time the type of a variable is looked up.
*
* @see GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults
*/
private Map<Tree, AnnotatedTypeMirror> initializerCache;
/**
* Should the analysis assume that side effects to a value can change the type of aliased
* references?
*
* <p>For many type systems, once a local variable's type is refined, side effects to the
* variable's value do not change the variable's type annotations. For some type systems, a side
* effect to the value could change them; set this field to true.
*/
// Not final so that subclasses can set it.
public boolean sideEffectsUnrefineAliases = false;
/**
* True if this checker either has one or more subcheckers, or if this checker is a subchecker.
* False otherwise. All uses of the methods {@link #addSharedCFGForTree(Tree, ControlFlowGraph)}
* and {@link #getSharedCFGForTree(Tree)} should be guarded by a check that this is true.
*/
public final boolean hasOrIsSubchecker;
/** An empty store. */
// Set in postInit only
protected Store emptyStore;
// Set in postInit only
protected FlowAnalysis analysis;
// Set in postInit only
protected TransferFunction transfer;
// Maintain for every class the store that is used when we analyze initialization code
protected Store initializationStore;
// Maintain for every class the store that is used when we analyze static initialization code
protected Store initializationStaticStore;
/**
* Caches for {@link AnalysisResult#runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput,
* IdentityHashMap, Map)}. This cache is enabled if {@link #shouldCache} is true. The cache size
* is derived from {@link #getCacheSize()}.
*
* @see AnalysisResult#runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput,
* IdentityHashMap, Map)
*/
protected final Map<
TransferInput<Value, Store>, IdentityHashMap<Node, TransferResult<Value, Store>>>
flowResultAnalysisCaches;
/**
* Subcheckers share the same ControlFlowGraph for each analyzed code statement. This maps from
* code statements to the shared control flow graphs. This map is null in all subcheckers (i.e.
* any checker for which getParentChecker() returns non-null). This map is also unused (and
* therefore null) for a checker with no subcheckers with which it can share CFGs.
*
* <p>The initial capacity of the map is set by {@link #getCacheSize()}.
*/
protected @Nullable Map<Tree, ControlFlowGraph> subcheckerSharedCFG;
/**
* If true, {@link #setRoot(CompilationUnitTree)} should clear the {@link #subcheckerSharedCFG}
* map, freeing memory.
*
* <p>For each compilation unit, all the subcheckers run first and finally the ultimate parent
* checker runs. The ultimate parent checker's {@link #setRoot(CompilationUnitTree)} (the last to
* run) sets this field to true.
*
* <p>In first subchecker to run for the next compilation unit, {@link
* #setRoot(CompilationUnitTree)} observes the true value, clears the {@link #subcheckerSharedCFG}
* map, and sets this field back to false. That first subchecker will create a CFG and re-populate
* the map, and subsequent subcheckers will use the map.
*/
protected boolean shouldClearSubcheckerSharedCFGs = true;
/**
* Creates a type factory. Its compilation unit is not yet set.
*
* @param checker the checker to which this type factory belongs
* @param useFlow whether flow analysis should be performed
*/
protected GenericAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) {
super(checker);
this.everUseFlow = useFlow;
this.shouldDefaultTypeVarLocals = useFlow;
this.useFlow = useFlow;
this.variablesUnderInitialization = new HashSet<>();
this.scannedClasses = new HashMap<>();
this.flowResult = null;
this.regularExitStores = null;
this.exceptionalExitStores = null;
this.methodInvocationStores = null;
this.returnStatementStores = null;
this.initializationStore = null;
this.initializationStaticStore = null;
this.cfgVisualizer = createCFGVisualizer();
if (shouldCache) {
int cacheSize = getCacheSize();
flowResultAnalysisCaches = CollectionUtils.createLRUCache(cacheSize);
initializerCache = CollectionUtils.createLRUCache(cacheSize);
} else {
flowResultAnalysisCaches = null;
initializerCache = null;
}
RelevantJavaTypes relevantJavaTypesAnno =
checker.getClass().getAnnotation(RelevantJavaTypes.class);
if (relevantJavaTypesAnno == null) {
this.relevantJavaTypes = null;
this.arraysAreRelevant = true;
} else {
Types types = getChecker().getTypeUtils();
Elements elements = getElementUtils();
Class<?>[] classes = relevantJavaTypesAnno.value();
this.relevantJavaTypes = new HashSet<>(CollectionsPlume.mapCapacity(classes.length));
this.arraysAreRelevant = false;
for (Class<?> clazz : classes) {
if (clazz == Object[].class) {
arraysAreRelevant = true;
} else if (clazz.isArray()) {
throw new TypeSystemError(
"Don't use arrays other than Object[] in @RelevantJavaTypes on "
+ this.getClass().getSimpleName());
} else {
TypeMirror relevantType = TypesUtils.typeFromClass(clazz, types, elements);
relevantJavaTypes.add(types.erasure(relevantType));
}
}
}
contractsUtils = createContractsFromMethod();
hasOrIsSubchecker =
!this.getChecker().getSubcheckers().isEmpty()
|| this.getChecker().getParentChecker() != null;
// Every subclass must call postInit, but it must be called after
// all other initialization is finished.
}
@Override
protected void postInit() {
super.postInit();
this.dependentTypesHelper = createDependentTypesHelper();
this.defaults = createAndInitQualifierDefaults();
this.treeAnnotator = createTreeAnnotator();
this.typeAnnotator = createTypeAnnotator();
this.defaultQualifierForUseTypeAnnotator = createDefaultForUseTypeAnnotator();
this.defaultForTypeAnnotator = createDefaultForTypeAnnotator();
this.poly = createQualifierPolymorphism();
this.analysis = createFlowAnalysis(new ArrayList<>());
this.transfer = analysis.getTransferFunction();
this.emptyStore = analysis.createEmptyStore(transfer.usesSequentialSemantics());
this.parseAnnotationFiles();
}
/**
* Performs flow-sensitive type refinement on {@code classTree} if this type factory is configured
* to do so.
*
* @param classTree tree on which to perform flow-sensitive type refinement
*/
@Override
public void preProcessClassTree(ClassTree classTree) {
if (this.everUseFlow) {
checkAndPerformFlowAnalysis(classTree);
}
}
/**
* Creates a type factory. Its compilation unit is not yet set.
*
* @param checker the checker to which this type factory belongs
*/
protected GenericAnnotatedTypeFactory(BaseTypeChecker checker) {
this(checker, flowByDefault);
}
@Override
public void setRoot(@Nullable CompilationUnitTree root) {
super.setRoot(root);
this.scannedClasses.clear();
this.flowResult = null;
this.regularExitStores = null;
this.exceptionalExitStores = null;
this.methodInvocationStores = null;
this.returnStatementStores = null;
this.initializationStore = null;
this.initializationStaticStore = null;
if (shouldCache) {
this.flowResultAnalysisCaches.clear();
this.initializerCache.clear();
this.defaultQualifierForUseTypeAnnotator.clearCache();
if (this.checker.getParentChecker() == null) {
// This is an ultimate parent checker, so after it runs the shared CFG it is using
// will no longer be needed, and can be cleared.
this.shouldClearSubcheckerSharedCFGs = true;
if (this.checker.getSubcheckers().isEmpty()) {
// If this checker has no subcheckers, then any maps that are currently
// being maintained should be cleared right away.
clearSharedCFG(this);
}
} else {
GenericAnnotatedTypeFactory<?, ?, ?, ?> ultimateParentATF =
this.checker.getUltimateParentChecker().getTypeFactory();
clearSharedCFG(ultimateParentATF);
}
}
}
/**
* Clears the caches associated with the shared CFG for the given type factory, if it is safe to
* do so.
*
* @param factory a type factory
*/
private void clearSharedCFG(GenericAnnotatedTypeFactory<?, ?, ?, ?> factory) {
if (factory.shouldClearSubcheckerSharedCFGs) {
// This is the first subchecker running in a group that share CFGs, so it must clear its
// ultimate parent's shared CFG before adding a new shared CFG.
factory.shouldClearSubcheckerSharedCFGs = false;
if (factory.subcheckerSharedCFG != null) {
factory.subcheckerSharedCFG.clear();
}
// The same applies to this map.
factory.artificialTreeToEnclosingElementMap.clear();
}
}
// **********************************************************************
// Factory Methods for the appropriate annotator classes
// **********************************************************************
/**
* Returns an immutable set of the <em>monotonic</em> type qualifiers supported by this checker.
*
* @return the monotonic type qualifiers supported this processor, or an empty set if none
* @see MonotonicQualifier
*/
public final Set<Class<? extends Annotation>> getSupportedMonotonicTypeQualifiers() {
if (supportedMonotonicQuals == null) {
supportedMonotonicQuals = new HashSet<>();
for (Class<? extends Annotation> anno : getSupportedTypeQualifiers()) {
MonotonicQualifier mono = anno.getAnnotation(MonotonicQualifier.class);
if (mono != null) {
supportedMonotonicQuals.add(anno);
}
}
}
return supportedMonotonicQuals;
}
/**
* Returns a {@link TreeAnnotator} that adds annotations to a type based on the contents of a
* tree.
*
* <p>The default tree annotator is a {@link ListTreeAnnotator} of the following:
*
* <ol>
* <li>{@link PropagationTreeAnnotator}: Propagates annotations from subtrees
* <li>{@link LiteralTreeAnnotator}: Adds annotations based on {@link QualifierForLiterals}
* meta-annotations
* <li>{@link DependentTypesTreeAnnotator}: Adapts dependent annotations based on context
* </ol>
*
* <p>Subclasses may override this method to specify additional tree annotators, for example:
*
* <pre>
* new ListTreeAnnotator(super.createTreeAnnotator(), new KeyLookupTreeAnnotator(this));
* </pre>
*
* @return a tree annotator
*/
protected TreeAnnotator createTreeAnnotator() {
List<TreeAnnotator> treeAnnotators = new ArrayList<>(2);
treeAnnotators.add(new PropagationTreeAnnotator(this));
treeAnnotators.add(new LiteralTreeAnnotator(this).addStandardLiteralQualifiers());
if (dependentTypesHelper.hasDependentAnnotations()) {
treeAnnotators.add(dependentTypesHelper.createDependentTypesTreeAnnotator());
}
return new ListTreeAnnotator(treeAnnotators);
}
/**
* Returns a {@link DefaultForTypeAnnotator} that adds annotations to a type based on the content
* of the type itself.
*
* <p>Subclass may override this method. The default type annotator is a {@link ListTypeAnnotator}
* of the following:
*
* <ol>
* <li>{@link IrrelevantTypeAnnotator}: Adds top to types not listed in the {@code @}{@link
* RelevantJavaTypes} annotation on the checker.
* <li>{@link PropagationTypeAnnotator}: Propagates annotation onto wildcards.
* </ol>
*
* @return a type annotator
*/
protected TypeAnnotator createTypeAnnotator() {
List<TypeAnnotator> typeAnnotators = new ArrayList<>(1);
if (relevantJavaTypes != null) {
typeAnnotators.add(
new IrrelevantTypeAnnotator(this, getQualifierHierarchy().getTopAnnotations()));
}
typeAnnotators.add(new PropagationTypeAnnotator(this));
return new ListTypeAnnotator(typeAnnotators);
}
/**
* Creates an {@link DefaultQualifierForUseTypeAnnotator}.
*
* @return a new {@link DefaultQualifierForUseTypeAnnotator}
*/
protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() {
return new DefaultQualifierForUseTypeAnnotator(this);
}
/**
* Creates an {@link DefaultForTypeAnnotator}.
*
* @return a new {@link DefaultForTypeAnnotator}
*/
protected DefaultForTypeAnnotator createDefaultForTypeAnnotator() {
return new DefaultForTypeAnnotator(this);
}
/**
* Returns the {@link DefaultForTypeAnnotator}.
*
* @return the {@link DefaultForTypeAnnotator}
*/
public DefaultForTypeAnnotator getDefaultForTypeAnnotator() {
return defaultForTypeAnnotator;
}
/**
* Returns the appropriate flow analysis class that is used for the org.checkerframework.dataflow
* analysis.
*
* <p>This implementation uses the checker naming convention to create the appropriate analysis.
* If no transfer function is found, it returns an instance of {@link CFAnalysis}.
*
* <p>Subclasses have to override this method to create the appropriate analysis if they do not
* follow the checker naming convention.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
protected FlowAnalysis createFlowAnalysis(List<Pair<VariableElement, Value>> fieldValues) {
// Try to reflectively load the visitor.
Class<?> checkerClass = checker.getClass();
while (checkerClass != BaseTypeChecker.class) {
FlowAnalysis result =
BaseTypeChecker.invokeConstructorFor(
BaseTypeChecker.getRelatedClassName(checkerClass, "Analysis"),
new Class<?>[] {BaseTypeChecker.class, this.getClass(), List.class},
new Object[] {checker, this, fieldValues});
if (result != null) {
return result;
}
checkerClass = checkerClass.getSuperclass();
}
// If an analysis couldn't be loaded reflectively, return the default.
List<Pair<VariableElement, CFValue>> tmp =
CollectionsPlume.mapList(
(Pair<VariableElement, Value> fieldVal) ->
Pair.of(fieldVal.first, (CFValue) fieldVal.second),
fieldValues);
return (FlowAnalysis) new CFAnalysis(checker, (GenericAnnotatedTypeFactory) this, tmp);
}
/**
* Returns the appropriate transfer function that is used for the org.checkerframework.dataflow
* analysis.
*
* <p>This implementation uses the checker naming convention to create the appropriate transfer
* function. If no transfer function is found, it returns an instance of {@link CFTransfer}.
*
* <p>Subclasses have to override this method to create the appropriate transfer function if they
* do not follow the checker naming convention.
*/
// A more precise type for the parameter would be FlowAnalysis, which
// is the type parameter bounded by the current parameter type CFAbstractAnalysis<Value, Store,
// TransferFunction>.
// However, we ran into issues in callers of the method if we used that type.
public TransferFunction createFlowTransferFunction(
CFAbstractAnalysis<Value, Store, TransferFunction> analysis) {
// Try to reflectively load the visitor.
Class<?> checkerClass = checker.getClass();
while (checkerClass != BaseTypeChecker.class) {
TransferFunction result =
BaseTypeChecker.invokeConstructorFor(
BaseTypeChecker.getRelatedClassName(checkerClass, "Transfer"),
new Class<?>[] {analysis.getClass()},
new Object[] {analysis});
if (result != null) {
return result;
}
checkerClass = checkerClass.getSuperclass();
}
// If a transfer function couldn't be loaded reflectively, return the default.
@SuppressWarnings("unchecked")
TransferFunction ret =
(TransferFunction)
new CFTransfer((CFAbstractAnalysis<CFValue, CFStore, CFTransfer>) analysis);
return ret;
}
/**
* Creates a {@link DependentTypesHelper} and returns it. Use {@link #getDependentTypesHelper} to
* access the value.
*
* @return a new {@link DependentTypesHelper}
*/
protected DependentTypesHelper createDependentTypesHelper() {
return new DependentTypesHelper(this);
}
/**
* Returns the DependentTypesHelper.
*
* @return the DependentTypesHelper
*/
public DependentTypesHelper getDependentTypesHelper() {
return dependentTypesHelper;
}
/**
* Creates an {@link ContractsFromMethod} and returns it.
*
* @return a new {@link ContractsFromMethod}
*/
protected ContractsFromMethod createContractsFromMethod() {
return new ContractsFromMethod(this);
}
/**
* Returns the helper for method pre- and postconditions.
*
* @return the helper for method pre- and postconditions
*/
public ContractsFromMethod getContractsFromMethod() {
return contractsUtils;
}
@Override
public AnnotatedDeclaredType fromNewClass(NewClassTree newClassTree) {
AnnotatedDeclaredType superResult = super.fromNewClass(newClassTree);
dependentTypesHelper.atExpression(superResult, newClassTree);
return superResult;
}
/**
* Create {@link QualifierDefaults} which handles checker specified defaults, and initialize the
* created {@link QualifierDefaults}. Subclasses should override {@link
* GenericAnnotatedTypeFactory#addCheckedCodeDefaults(QualifierDefaults defs)} to add more
* defaults or use different defaults.
*
* @return the QualifierDefaults object
*/
// TODO: When changing this method, also look into
// {@link
// org.checkerframework.common.wholeprograminference.WholeProgramInferenceScenesHelper#shouldIgnore}.
// Both methods should have some functionality merged into a single location.
// See Issue 683
// https://github.com/typetools/checker-framework/issues/683
protected final QualifierDefaults createAndInitQualifierDefaults() {
QualifierDefaults defs = createQualifierDefaults();
addCheckedCodeDefaults(defs);
addCheckedStandardDefaults(defs);
addUncheckedStandardDefaults(defs);
checkForDefaultQualifierInHierarchy(defs);
return defs;
}
/**
* Create {@link QualifierDefaults} which handles checker specified defaults. Sub-classes override
* this method to provide a different {@code QualifierDefault} implementation.
*/
protected QualifierDefaults createQualifierDefaults() {
return new QualifierDefaults(elements, this);
}
/**
* Creates and returns a string containing the number of qualifiers and the canonical class names
* of each qualifier that has been added to this checker's supported qualifier set. The names are
* alphabetically sorted.
*
* @return a string containing the number of qualifiers and canonical names of each qualifier
*/
protected final String getSortedQualifierNames() {
Set<Class<? extends Annotation>> stq = getSupportedTypeQualifiers();
if (stq.isEmpty()) {
return "No qualifiers examined";
}
if (stq.size() == 1) {
return "1 qualifier examined: " + stq.iterator().next().getCanonicalName();
}
// Create a list of the supported qualifiers and sort the list alphabetically
List<Class<? extends Annotation>> sortedSupportedQuals = new ArrayList<>(stq);
sortedSupportedQuals.sort(Comparator.comparing(Class::getCanonicalName));
// display the number of qualifiers as well as the names of each qualifier.
StringJoiner sj =
new StringJoiner(", ", sortedSupportedQuals.size() + " qualifiers examined: ", "");
for (Class<? extends Annotation> qual : sortedSupportedQuals) {
sj.add(qual.getCanonicalName());
}
return sj.toString();
}
/**
* Adds default qualifiers for type-checked code by reading {@link DefaultFor} and {@link
* DefaultQualifierInHierarchy} meta-annotations. Subclasses may override this method to add
* defaults that cannot be specified with a {@link DefaultFor} or {@link
* DefaultQualifierInHierarchy} meta-annotations.
*
* @param defs QualifierDefault object to which defaults are added
*/
protected void addCheckedCodeDefaults(QualifierDefaults defs) {
// Add defaults from @DefaultFor and @DefaultQualifierInHierarchy
for (Class<? extends Annotation> qual : getSupportedTypeQualifiers()) {
DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class);
if (defaultFor != null) {
final TypeUseLocation[] locations = defaultFor.value();
defs.addCheckedCodeDefaults(AnnotationBuilder.fromClass(elements, qual), locations);
}
if (qual.getAnnotation(DefaultQualifierInHierarchy.class) != null) {
defs.addCheckedCodeDefault(
AnnotationBuilder.fromClass(elements, qual), TypeUseLocation.OTHERWISE);
}
}
}
/**
* Adds the standard CLIMB defaults that do not conflict with previously added defaults.
*
* @param defs {@link QualifierDefaults} object to which defaults are added
*/
protected void addCheckedStandardDefaults(QualifierDefaults defs) {
if (this.everUseFlow) {
defs.addClimbStandardDefaults();
}
}
/**
* Adds standard unchecked defaults that do not conflict with previously added defaults.
*
* @param defs {@link QualifierDefaults} object to which defaults are added
*/
protected void addUncheckedStandardDefaults(QualifierDefaults defs) {
defs.addUncheckedStandardDefaults();
}
/**
* Check that a default qualifier (in at least one hierarchy) has been set and issue an error if
* not.
*
* @param defs {@link QualifierDefaults} object to which defaults are added
*/
protected void checkForDefaultQualifierInHierarchy(QualifierDefaults defs) {
if (!defs.hasDefaultsForCheckedCode()) {
throw new BugInCF(
"GenericAnnotatedTypeFactory.createQualifierDefaults: "
+ "@DefaultQualifierInHierarchy or @DefaultFor(TypeUseLocation.OTHERWISE) not found. "
+ "Every checker must specify a default qualifier. "
+ getSortedQualifierNames());
}
// If a default unchecked code qualifier isn't specified, the defaults
// for checked code will be used.
}
/**
* Creates the {@link QualifierPolymorphism} instance which supports the QualifierPolymorphism
* mechanism.
*
* @return the QualifierPolymorphism instance to use
*/
protected QualifierPolymorphism createQualifierPolymorphism() {
return new DefaultQualifierPolymorphism(processingEnv, this);
}
/**
* Gives the current {@link QualifierPolymorphism} instance which supports the
* QualifierPolymorphism mechanism.
*
* @return the QualifierPolymorphism instance to use
*/
public QualifierPolymorphism getQualifierPolymorphism() {
return this.poly;
}
// **********************************************************************
// Factory Methods for the appropriate annotator classes
// **********************************************************************
@Override
protected void postDirectSuperTypes(
AnnotatedTypeMirror type, List<? extends AnnotatedTypeMirror> supertypes) {
super.postDirectSuperTypes(type, supertypes);
if (type.getKind() == TypeKind.DECLARED) {
for (AnnotatedTypeMirror supertype : supertypes) {
Element elt = ((DeclaredType) supertype.getUnderlyingType()).asElement();
addComputedTypeAnnotations(elt, supertype);
}
}
}
/**
* Gets the type of the resulting constructor call of a MemberReferenceTree.
*
* @param memberReferenceTree MemberReferenceTree where the member is a constructor
* @param constructorType AnnotatedExecutableType of the declaration of the constructor
* @return AnnotatedTypeMirror of the resulting type of the constructor
*/
public AnnotatedTypeMirror getResultingTypeOfConstructorMemberReference(
MemberReferenceTree memberReferenceTree, AnnotatedExecutableType constructorType) {
assert memberReferenceTree.getMode() == MemberReferenceTree.ReferenceMode.NEW;
// The return type for constructors should only have explicit annotations from the
// constructor. Recreate some of the logic from TypeFromTree.visitNewClass here.
// The return type of the constructor will be the type of the expression of the member
// reference tree.
AnnotatedDeclaredType constructorReturnType =
(AnnotatedDeclaredType) fromTypeTree(memberReferenceTree.getQualifierExpression());
// Keep only explicit annotations and those from @Poly
AnnotatedTypes.copyOnlyExplicitConstructorAnnotations(
this, constructorReturnType, constructorType);
// Now add back defaulting.
addComputedTypeAnnotations(memberReferenceTree.getQualifierExpression(), constructorReturnType);
return constructorReturnType;
}
/**
* Returns the primary annotation on expression if it were evaluated at path.
*
* @param expression a Java expression
* @param tree current tree
* @param path location at which expression is evaluated
* @param clazz class of the annotation
* @return the annotation on expression or null if one does not exist
* @throws JavaExpressionParseException thrown if the expression cannot be parsed
*/
public AnnotationMirror getAnnotationFromJavaExpressionString(
String expression, Tree tree, TreePath path, Class<? extends Annotation> clazz)
throws JavaExpressionParseException {
JavaExpression expressionObj = parseJavaExpressionString(expression, path);
return getAnnotationFromJavaExpression(expressionObj, tree, clazz);
}
/**
* Returns the primary annotation on an expression, at a particular location.
*
* @param expr the expression for which the annotation is returned
* @param tree current tree
* @param clazz the Class of the annotation
* @return the annotation on expression or null if one does not exist
*/
public AnnotationMirror getAnnotationFromJavaExpression(
JavaExpression expr, Tree tree, Class<? extends Annotation> clazz) {
return getAnnotationByClass(getAnnotationsFromJavaExpression(expr, tree), clazz);
}
/**
* Returns the primary annotations on an expression, at a particular location.
*
* @param expr the expression for which the annotation is returned
* @param tree current tree
* @return the annotation on expression or null if one does not exist
*/
public Set<AnnotationMirror> getAnnotationsFromJavaExpression(JavaExpression expr, Tree tree) {
// Look in the store
if (CFAbstractStore.canInsertJavaExpression(expr)) {
Store store = getStoreBefore(tree);
// `store` can be null if the tree is in a field initializer.
if (store != null) {
Value value = store.getValue(expr);
if (value != null) {
// Is it possible that this lacks some annotations that appear in the type factory?
return value.getAnnotations();
}
}
}
// Look in the type factory, if not found in the store.
if (expr instanceof LocalVariable) {
Element ele = ((LocalVariable) expr).getElement();
// Because of
// https://github.com/eisop/checker-framework/issues/14
// and the workaround in
// org.checkerframework.framework.type.ElementAnnotationApplier.applyInternal
// The annotationMirror may not contain all explicitly written annotations.
return getAnnotatedType(ele).getAnnotations();
} else if (expr instanceof FieldAccess) {
Element ele = ((FieldAccess) expr).getField();
return getAnnotatedType(ele).getAnnotations();
} else {
return Collections.emptySet();
}
}
/**
* Produces the JavaExpression as if {@code expression} were written at {@code currentPath}.
*
* @param expression a Java expression
* @param currentPath the current path
* @return the JavaExpression associated with expression on currentPath
* @throws JavaExpressionParseException thrown if the expression cannot be parsed
*/
public JavaExpression parseJavaExpressionString(String expression, TreePath currentPath)
throws JavaExpressionParseException {
return StringToJavaExpression.atPath(expression, currentPath, checker);
}
/**
* Produces the JavaExpression and offset associated with an expression. For instance, "n+1" has
* no associated JavaExpression, but this method produces a pair of a JavaExpression (for "n") and
* an offset ("1").
*
* @param expression a Java expression, possibly with a constant offset
* @param currentPath location at which expression is evaluated
* @return the JavaExpression and offset for the given expression
* @throws JavaExpressionParseException thrown if the expression cannot be parsed
*/
public Pair<JavaExpression, String> getExpressionAndOffsetFromJavaExpressionString(
String expression, TreePath currentPath) throws JavaExpressionParseException {
Pair<String, String> p = getExpressionAndOffset(expression);
JavaExpression r = parseJavaExpressionString(p.first, currentPath);
return Pair.of(r, p.second);
}
/**
* Returns the annotation mirror from dataflow for {@code expression}.
*
* <p>This will output a different annotation than {@link
* #getAnnotationFromJavaExpressionString(String, Tree, TreePath, Class)}, because if the
* specified annotation isn't found in the store, the type from the factory is used.
*
* @param expression a Java expression
* @param tree the tree at the location to parse the expression
* @param currentPath location at which expression is evaluated
* @throws JavaExpressionParseException thrown if the expression cannot be parsed
* @return an AnnotationMirror representing the type in the store at the given location from this
* type factory's type system, or null if one is not available
*/
public AnnotationMirror getAnnotationMirrorFromJavaExpressionString(
String expression, Tree tree, TreePath currentPath) throws JavaExpressionParseException {
JavaExpression je = parseJavaExpressionString(expression, currentPath);
if (je == null || !CFAbstractStore.canInsertJavaExpression(je)) {
return null;
}
Store store = getStoreBefore(tree);
Value value = store.getValue(je);
return value != null ? value.getAnnotations().iterator().next() : null;
}
/**
* Track the state of org.checkerframework.dataflow analysis scanning for each class tree in the
* compilation unit.
*/
protected enum ScanState {
/** Dataflow analysis in progress. */
IN_PROGRESS,
/** Dataflow analysis finished. */
FINISHED
}
/** Map from ClassTree to their dataflow analysis state. */
protected final Map<ClassTree, ScanState> scannedClasses;
/**
* The result of the flow analysis. Invariant:
*
* <pre>
* scannedClasses.get(c) == FINISHED for some class c &rArr; flowResult != null
* </pre>
*
* Note that flowResult contains analysis results for Trees from multiple classes which are
* produced by multiple calls to performFlowAnalysis.
*/
protected AnalysisResult<Value, Store> flowResult;
/**
* A mapping from methods (or other code blocks) to their regular exit store (used to check
* postconditions).
*/
protected IdentityHashMap<Tree, Store> regularExitStores;
/** A mapping from methods (or other code blocks) to their exceptional exit store. */
protected IdentityHashMap<Tree, Store> exceptionalExitStores;
/** A mapping from methods to a list with all return statements and the corresponding store. */
protected IdentityHashMap<MethodTree, List<Pair<ReturnNode, TransferResult<Value, Store>>>>
returnStatementStores;
/**
* A mapping from methods to their a list with all return statements and the corresponding store.
*/
protected IdentityHashMap<MethodInvocationTree, Store> methodInvocationStores;
/**
* Returns the regular exit store for a method or another code block (such as static
* initializers).
*
* @param tree a MethodTree or other code block, such as a static initializer
* @return the regular exit store, or {@code null}, if there is no such store (because the method
* cannot exit through the regular exit block).
*/
public @Nullable Store getRegularExitStore(Tree tree) {
return regularExitStores.get(tree);
}
/**
* Returns the exceptional exit store for a method or another code block (such as static
* initializers).
*
* @param tree a MethodTree or other code block, such as a static initializer
* @return the exceptional exit store, or {@code null}, if there is no such store
*/
public @Nullable Store getExceptionalExitStore(Tree tree) {
return exceptionalExitStores.get(tree);
}
/**
* Returns a list of all return statements of {@code method} paired with their corresponding
* {@link TransferResult}. If {@code method} has no return statement, then the empty list is
* returned.
*
* @param methodTree method whose return statements should be returned
* @return a list of all return statements of {@code method} paired with their corresponding
* {@link TransferResult} or an empty list if {@code method} has no return statements
*/
public List<Pair<ReturnNode, TransferResult<Value, Store>>> getReturnStatementStores(
MethodTree methodTree) {
assert returnStatementStores.containsKey(methodTree);
return returnStatementStores.get(methodTree);
}
/**
* Returns the store immediately before a given {@link Tree}.
*
* @return the store immediately before a given {@link Tree}
*/
public Store getStoreBefore(Tree tree) {
if (!analysis.isRunning()) {
return flowResult.getStoreBefore(tree);
}
Set<Node> nodes = analysis.getNodesForTree(tree);
if (nodes != null) {
return getStoreBefore(nodes);
} else {
return flowResult.getStoreBefore(tree);
}
}
/**
* Returns the store immediately before a given Set of {@link Node}s.
*
* @return the store immediately before a given Set of {@link Node}s
*/
public Store getStoreBefore(Set<Node> nodes) {
Store merge = null;
for (Node aNode : nodes) {
Store s = getStoreBefore(aNode);
if (merge == null) {
merge = s;
} else if (s != null) {
merge = merge.leastUpperBound(s);
}
}
return merge;
}
/**
* Returns the store immediately before a given node.
*
* @param node a node whose pre-store to return
* @return the store immediately before {@code node}
*/
public Store getStoreBefore(Node node) {
if (!analysis.isRunning()) {
return flowResult.getStoreBefore(node);
}
TransferInput<Value, Store> prevStore = analysis.getInput(node.getBlock());
if (prevStore == null) {
return null;
}
Store store =
AnalysisResult.runAnalysisFor(
node,
Analysis.BeforeOrAfter.BEFORE,
prevStore,
analysis.getNodeValues(),
flowResultAnalysisCaches);
return store;
}
/**
* Returns the store immediately after a given tree.
*
* <p>May return null; for example, after a {@code return} statement.
*
* @param tree the tree whose post-store to return
* @return the store immediately after a given tree
*/
public Store getStoreAfter(Tree tree) {
if (!analysis.isRunning()) {
return flowResult.getStoreAfter(tree);
}
Set<Node> nodes = analysis.getNodesForTree(tree);
return getStoreAfter(nodes);
}
/**
* Returns the store immediately after a given set of nodes.
*
* @param nodes the nodes whose post-stores to LUB
* @return the LUB of the stores store immediately after {@code nodes}
*/
public Store getStoreAfter(Set<Node> nodes) {
Store merge = null;
for (Node node : nodes) {
Store s = getStoreAfter(node);
if (merge == null) {
merge = s;
} else if (s != null) {
merge = merge.leastUpperBound(s);
}
}
return merge;
}
/**
* Returns the store immediately after a given {@link Node}.
*
* @param node node after which the store is returned
* @return the store immediately after a given {@link Node}
*/
public Store getStoreAfter(Node node) {
if (!analysis.isRunning()) {
return flowResult.getStoreAfter(node);
}
Store res =
AnalysisResult.runAnalysisFor(
node,
Analysis.BeforeOrAfter.AFTER,
analysis.getInput(node.getBlock()),
analysis.getNodeValues(),
flowResultAnalysisCaches);
return res;
}
/**
* See {@link org.checkerframework.dataflow.analysis.AnalysisResult#getNodesForTree(Tree)}.
*
* @return the {@link Node}s for a given {@link Tree}
* @see org.checkerframework.dataflow.analysis.AnalysisResult#getNodesForTree(Tree)
*/
public Set<Node> getNodesForTree(Tree tree) {
return flowResult.getNodesForTree(tree);
}
/**
* Return the first {@link Node} for a given {@link Tree} that has class {@code kind}.
*
* <p>You probably don't want to use this function: iterate over the result of {@link
* #getNodesForTree(Tree)} yourself or ask for a conservative approximation of the store using
* {@link #getStoreBefore(Tree)} or {@link #getStoreAfter(Tree)}. This method is for code that
* uses a {@link Node} in a rather unusual way. Callers should probably be rewritten to not use a
* {@link Node} at all.
*
* @param <T> the class of the node to return
* @param tree a tree in which to search for a node of class {@code kind}
* @param kind the class of the node to return
* @return the first {@link Node} for a given {@link Tree} that has class {@code kind}
* @see #getNodesForTree(Tree)
* @see #getStoreBefore(Tree)
* @see #getStoreAfter(Tree)
*/
public <T extends Node> T getFirstNodeOfKindForTree(Tree tree, Class<T> kind) {
Set<Node> nodes = getNodesForTree(tree);
for (Node node : nodes) {
if (node.getClass() == kind) {
return kind.cast(node);
}
}
return null;
}
/**
* Returns the value of effectively final local variables.
*
* @return the value of effectively final local variables
*/
public HashMap<Element, Value> getFinalLocalValues() {
return flowResult.getFinalLocalValues();
}
/**
* Perform a org.checkerframework.dataflow analysis over a single class tree and its nested
* classes.
*
* @param classTree the class to analyze
*/
protected void performFlowAnalysis(ClassTree classTree) {
if (flowResult == null) {
regularExitStores = new IdentityHashMap<>();
exceptionalExitStores = new IdentityHashMap<>();
returnStatementStores = new IdentityHashMap<>();
flowResult = new AnalysisResult<>(flowResultAnalysisCaches);
}
// no need to scan annotations
if (classTree.getKind() == Kind.ANNOTATION_TYPE) {
// Mark finished so that default annotations will be applied.
scannedClasses.put(classTree, ScanState.FINISHED);
return;
}
Queue<Pair<ClassTree, Store>> queue = new ArrayDeque<>();
List<Pair<VariableElement, Value>> fieldValues = new ArrayList<>();
// No captured store for top-level classes.
queue.add(Pair.of(classTree, null));
while (!queue.isEmpty()) {
final Pair<ClassTree, Store> qel = queue.remove();
final ClassTree ct = qel.first;
final Store capturedStore = qel.second;
scannedClasses.put(ct, ScanState.IN_PROGRESS);
TreePath preTreePath = visitorState.getPath();
AnnotatedDeclaredType preClassType = visitorState.getClassType();
ClassTree preClassTree = visitorState.getClassTree();
AnnotatedDeclaredType preAMT = visitorState.getMethodReceiver();
MethodTree preMT = visitorState.getMethodTree();
// Don't use getPath, because that depends on the visitorState path.
visitorState.setPath(TreePath.getPath(this.root, ct));
visitorState.setClassType(getAnnotatedType(TreeUtils.elementFromDeclaration(ct)));
visitorState.setClassTree(ct);
visitorState.setMethodReceiver(null);
visitorState.setMethodTree(null);
// start with the captured store as initialization store
initializationStaticStore = capturedStore;
initializationStore = capturedStore;
Queue<Pair<LambdaExpressionTree, Store>> lambdaQueue = new ArrayDeque<>();
try {
List<CFGMethod> methods = new ArrayList<>();
for (Tree m : ct.getMembers()) {
switch (m.getKind()) {
case METHOD:
MethodTree mt = (MethodTree) m;
// Skip abstract and native methods because they have no body.
Set<Modifier> flags = mt.getModifiers().getFlags();
if (flags.contains(Modifier.ABSTRACT) || flags.contains(Modifier.NATIVE)) {
break;
}
// Abstract methods in an interface have a null body but do not have an
// ABSTRACT flag.
if (mt.getBody() == null) {
break;
}
// Wait with scanning the method until all other members
// have been processed.
CFGMethod met = new CFGMethod(mt, ct);
methods.add(met);
break;
case VARIABLE:
VariableTree vt = (VariableTree) m;
ExpressionTree initializer = vt.getInitializer();
// analyze initializer if present
if (initializer != null) {
boolean isStatic = vt.getModifiers().getFlags().contains(Modifier.STATIC);
analyze(
queue,
lambdaQueue,
new CFGStatement(vt, ct),
fieldValues,
classTree,
true,
true,
isStatic,
capturedStore);
Value value = flowResult.getValue(initializer);
if (vt.getModifiers().getFlags().contains(Modifier.FINAL) && value != null) {
// Store the abstract value for the field.
VariableElement element = TreeUtils.elementFromDeclaration(vt);
fieldValues.add(Pair.of(element, value));
}
}
break;
case CLASS:
case ANNOTATION_TYPE:
case INTERFACE:
case ENUM:
// Visit inner and nested class trees.
// TODO: Use no store for them? What can be captured?
queue.add(Pair.of((ClassTree) m, capturedStore));
break;
case BLOCK:
BlockTree b = (BlockTree) m;
analyze(
queue,
lambdaQueue,
new CFGStatement(b, ct),
fieldValues,
ct,
true,
true,
b.isStatic(),
capturedStore);
break;
default:
assert false : "Unexpected member: " + m.getKind();
break;
}
}
// Now analyze all methods.
// TODO: at this point, we don't have any information about
// fields of superclasses.
for (CFGMethod met : methods) {
analyze(
queue,
lambdaQueue,
met,
fieldValues,
classTree,
TreeUtils.isConstructor(met.getMethod()),
false,
false,
capturedStore);
}
while (!lambdaQueue.isEmpty()) {
Pair<LambdaExpressionTree, Store> lambdaPair = lambdaQueue.poll();
MethodTree mt =
(MethodTree) TreePathUtil.enclosingOfKind(getPath(lambdaPair.first), Kind.METHOD);
analyze(
queue,
lambdaQueue,
new CFGLambda(lambdaPair.first, classTree, mt),
fieldValues,
classTree,
false,
false,
false,
lambdaPair.second);
}
// by convention we store the static initialization store as the regular exit
// store of the class node, so that it can later be used to check
// that all fields are initialized properly.
// see InitializationVisitor.visitClass
if (initializationStaticStore == null) {
regularExitStores.put(ct, emptyStore);
} else {
regularExitStores.put(ct, initializationStaticStore);
}
} finally {
visitorState.setPath(preTreePath);
visitorState.setClassType(preClassType);
visitorState.setClassTree(preClassTree);
visitorState.setMethodReceiver(preAMT);
visitorState.setMethodTree(preMT);
}
scannedClasses.put(ct, ScanState.FINISHED);
}
}
/**
* Analyze the AST {@code ast} and store the result. Additional operations that should be
* performed after analysis should be implemented in {@link #postAnalyze(ControlFlowGraph)}.
*
* @param queue the queue for encountered class trees and their initial stores
* @param lambdaQueue the queue for encountered lambda expression trees and their initial stores
* @param ast the AST to analyze
* @param fieldValues the abstract values for all fields of the same class
* @param currentClass the class we are currently looking at
* @param isInitializationCode are we analyzing a (static/non-static) initializer block of a class
* @param updateInitializationStore should the initialization store be updated
* @param isStatic are we analyzing a static construct
* @param capturedStore the input Store to use for captured variables, e.g. in a lambda
* @see #postAnalyze(org.checkerframework.dataflow.cfg.ControlFlowGraph)
*/
protected void analyze(
Queue<Pair<ClassTree, Store>> queue,
Queue<Pair<LambdaExpressionTree, Store>> lambdaQueue,
UnderlyingAST ast,
List<Pair<VariableElement, Value>> fieldValues,
ClassTree currentClass,
boolean isInitializationCode,
boolean updateInitializationStore,
boolean isStatic,
Store capturedStore) {
ControlFlowGraph cfg = CFCFGBuilder.build(root, ast, checker, this, processingEnv);
if (isInitializationCode) {
Store initStore = !isStatic ? initializationStore : initializationStaticStore;
if (initStore != null) {
// we have already seen initialization code and analyzed it, and
// the analysis ended with the store initStore.
// use it to start the next analysis.
transfer.setFixedInitialStore(initStore);
} else {
transfer.setFixedInitialStore(capturedStore);
}
} else {
transfer.setFixedInitialStore(capturedStore);
}
analysis.performAnalysis(cfg, fieldValues);
AnalysisResult<Value, Store> result = analysis.getResult();
// store result
flowResult.combine(result);
if (ast.getKind() == UnderlyingAST.Kind.METHOD) {
// store exit store (for checking postconditions)
CFGMethod mast = (CFGMethod) ast;
MethodTree method = mast.getMethod();
Store regularExitStore = analysis.getRegularExitStore();
if (regularExitStore != null) {
regularExitStores.put(method, regularExitStore);
}
Store exceptionalExitStore = analysis.getExceptionalExitStore();
if (exceptionalExitStore != null) {
exceptionalExitStores.put(method, exceptionalExitStore);
}
returnStatementStores.put(method, analysis.getReturnStatementStores());
} else if (ast.getKind() == UnderlyingAST.Kind.ARBITRARY_CODE) {
CFGStatement block = (CFGStatement) ast;
Store regularExitStore = analysis.getRegularExitStore();
if (regularExitStore != null) {
regularExitStores.put(block.getCode(), regularExitStore);
}
Store exceptionalExitStore = analysis.getExceptionalExitStore();
if (exceptionalExitStore != null) {
exceptionalExitStores.put(block.getCode(), exceptionalExitStore);
}
} else if (ast.getKind() == UnderlyingAST.Kind.LAMBDA) {
// TODO: Postconditions?
CFGLambda block = (CFGLambda) ast;
Store regularExitStore = analysis.getRegularExitStore();
if (regularExitStore != null) {
regularExitStores.put(block.getCode(), regularExitStore);
}
Store exceptionalExitStore = analysis.getExceptionalExitStore();
if (exceptionalExitStore != null) {
exceptionalExitStores.put(block.getCode(), exceptionalExitStore);
}
} else {
assert false : "Unexpected AST kind: " + ast.getKind();
}
if (isInitializationCode && updateInitializationStore) {
Store newInitStore = analysis.getRegularExitStore();
if (!isStatic) {
initializationStore = newInitStore;
} else {
initializationStaticStore = newInitStore;
}
}
// add classes declared in CFG
for (ClassTree cls : cfg.getDeclaredClasses()) {
queue.add(Pair.of(cls, getStoreBefore(cls)));
}
// add lambdas declared in CFG
for (LambdaExpressionTree lambda : cfg.getDeclaredLambdas()) {
lambdaQueue.add(Pair.of(lambda, getStoreBefore(lambda)));
}
postAnalyze(cfg);
}
/**
* Perform any additional operations on a CFG. Called once per CFG, after the CFG has been
* analyzed by {@link #analyze(Queue, Queue, UnderlyingAST, List, ClassTree, boolean, boolean,
* boolean, CFAbstractStore)}. This method can be used to initialize additional state or to
* perform any analyses that are easier to perform on the CFG instead of the AST.
*
* @param cfg the CFG
* @see #analyze(java.util.Queue, java.util.Queue,
* org.checkerframework.dataflow.cfg.UnderlyingAST, java.util.List,
* com.sun.source.tree.ClassTree, boolean, boolean, boolean,
* org.checkerframework.framework.flow.CFAbstractStore)
*/
protected void postAnalyze(ControlFlowGraph cfg) {
handleCFGViz(cfg);
}
/**
* Handle the visualization of the CFG, if necessary.
*
* @param cfg the CFG
*/
protected void handleCFGViz(ControlFlowGraph cfg) {
if (checker.hasOption("flowdotdir") || checker.hasOption("cfgviz")) {
getCFGVisualizer().visualize(cfg, cfg.getEntryBlock(), analysis);
}
}
/**
* Returns the type of the left-hand side of an assignment without applying local variable
* defaults to type variables.
*
* <p>The type variables that are types of local variables are defaulted to top so that they can
* be refined by dataflow. When these types are used as context during type argument inference,
* this default is too conservative. So this method is used instead of {@link
* GenericAnnotatedTypeFactory#getAnnotatedTypeLhs(Tree)}.
*
* <p>{@link TypeArgInferenceUtil#assignedToVariable(AnnotatedTypeFactory, Tree)} explains why a
* different type is used.
*
* @param lhsTree left-hand side of an assignment
* @return AnnotatedTypeMirror of {@code lhsTree}
*/
public AnnotatedTypeMirror getAnnotatedTypeLhsNoTypeVarDefault(Tree lhsTree) {
boolean old = this.shouldDefaultTypeVarLocals;
shouldDefaultTypeVarLocals = false;
AnnotatedTypeMirror type = getAnnotatedTypeLhs(lhsTree);
this.shouldDefaultTypeVarLocals = old;
return type;
}
/**
* Returns the type of a left-hand side of an assignment.
*
* <p>The default implementation returns the type without considering dataflow type refinement.
* Subclass can override this method and add additional logic for computing the type of a LHS.
*
* @param lhsTree left-hand side of an assignment
* @return AnnotatedTypeMirror of {@code lhsTree}
*/
public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree lhsTree) {
AnnotatedTypeMirror res;
boolean oldUseFlow = useFlow;
boolean oldShouldCache = shouldCache;
useFlow = false;
// Don't cache the result because getAnnotatedType(lhsTree) could
// be called from elsewhere and would expect flow-sensitive type refinements.
shouldCache = false;
switch (lhsTree.getKind()) {
case VARIABLE:
case IDENTIFIER:
case MEMBER_SELECT:
case ARRAY_ACCESS:
res = getAnnotatedType(lhsTree);
break;
case PARENTHESIZED:
res = getAnnotatedTypeLhs(TreeUtils.withoutParens((ExpressionTree) lhsTree));
break;
default:
if (TreeUtils.isTypeTree(lhsTree)) {
// lhsTree is a type tree at the pseudo assignment of a returned expression to
// declared return type.
res = getAnnotatedType(lhsTree);
} else {
throw new BugInCF(
"GenericAnnotatedTypeFactory: Unexpected tree passed to getAnnotatedTypeLhs. "
+ "lhsTree: "
+ lhsTree
+ " Tree.Kind: "
+ lhsTree.getKind());
}
}
useFlow = oldUseFlow;
shouldCache = oldShouldCache;
return res;
}
/**
* Returns the type of a varargs array of a method invocation or a constructor invocation. Returns
* null only if private field {@code useFlow} is false.
*
* @param tree a method invocation or a constructor invocation
* @return AnnotatedTypeMirror of varargs array for a method or constructor invocation {@code
* tree}; returns null if private field {@code useFlow} is false
*/
public @Nullable AnnotatedTypeMirror getAnnotatedTypeVarargsArray(Tree tree) {
if (!useFlow) {
return null;
}
// Get the synthetic NewArray tree that dataflow creates as the last argument of a call to a
// vararg method. Do this by getting the MethodInvocationNode to which "tree" maps. The last
// argument node of the MethodInvocationNode stores the synthetic NewArray tree.
List<Node> args;
switch (tree.getKind()) {
case METHOD_INVOCATION:
args = getFirstNodeOfKindForTree(tree, MethodInvocationNode.class).getArguments();
break;
case NEW_CLASS:
args = getFirstNodeOfKindForTree(tree, ObjectCreationNode.class).getArguments();
break;
default:
throw new BugInCF("Unexpected kind of tree: " + tree);
}
assert !args.isEmpty() : "Arguments are empty";
Node varargsArray = args.get(args.size() - 1);
AnnotatedTypeMirror varargtype = getAnnotatedType(varargsArray.getTree());
return varargtype;
}
/* Returns the type of a right-hand side of an assignment for unary operation like prefix or
* postfix increment or decrement.
*
* @param tree unary operation tree for compound assignment
* @return AnnotatedTypeMirror of a right-hand side of an assignment for unary operation
*/
public AnnotatedTypeMirror getAnnotatedTypeRhsUnaryAssign(UnaryTree tree) {
if (!useFlow) {
return getAnnotatedType(tree);
}
AssignmentNode n = flowResult.getAssignForUnaryTree(tree);
return getAnnotatedType(n.getExpression().getTree());
}
@Override
public ParameterizedExecutableType constructorFromUse(NewClassTree tree) {
ParameterizedExecutableType mType = super.constructorFromUse(tree);
AnnotatedExecutableType method = mType.executableType;
dependentTypesHelper.atConstructorInvocation(method, tree);
return mType;
}
@Override
protected void constructorFromUsePreSubstitution(
NewClassTree tree, AnnotatedExecutableType type) {
poly.resolve(tree, type);
}
@Override
public AnnotatedTypeMirror getMethodReturnType(MethodTree m) {
AnnotatedTypeMirror returnType = super.getMethodReturnType(m);
dependentTypesHelper.atMethodBody(returnType, m);
return returnType;
}
@Override
public void addDefaultAnnotations(AnnotatedTypeMirror type) {
addAnnotationsFromDefaultForType(null, type);
typeAnnotator.visit(type, null);
defaults.annotate((Element) null, type);
}
/**
* This method is final; override {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror,
* boolean)} instead.
*
* <p>{@inheritDoc}
*/
@Override
protected final void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) {
addComputedTypeAnnotations(tree, type, this.useFlow);
}
/**
* Like {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}. Overriding implementations
* typically simply pass the boolean to calls to super.
*
* @param tree an AST node
* @param type the type obtained from tree
* @param iUseFlow whether to use information from dataflow analysis
*/
protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) {
if (root == null && ajavaTypes.isParsing()) {
return;
}
assert root != null
: "GenericAnnotatedTypeFactory.addComputedTypeAnnotations: "
+ " root needs to be set when used on trees; factory: "
+ this.getClass();
String thisClass = null;
String treeString = null;
if (debug) {
thisClass = this.getClass().getSimpleName();
if (thisClass.endsWith("AnnotatedTypeFactory")) {
thisClass = thisClass.substring(0, thisClass.length() - "AnnotatedTypeFactory".length());
}
treeString = TreeUtils.toStringTruncated(tree, 60);
}
log(
"%s GATF.addComputedTypeAnnotations#1(%s, %s, %s)%n",
thisClass, treeString, type, iUseFlow);
if (!TreeUtils.isExpressionTree(tree)) {
// Don't apply defaults to expressions. Their types may be computed from subexpressions
// in treeAnnotator.
addAnnotationsFromDefaultForType(TreeUtils.elementFromTree(tree), type);
log("%s GATF.addComputedTypeAnnotations#2(%s, %s)%n", thisClass, treeString, type);
}
applyQualifierParameterDefaults(tree, type);
log("%s GATF.addComputedTypeAnnotations#3(%s, %s)%n", thisClass, treeString, type);
treeAnnotator.visit(tree, type);
log("%s GATF.addComputedTypeAnnotations#4(%s, %s)%n", thisClass, treeString, type);
if (TreeUtils.isExpressionTree(tree)) {
// If a tree annotator, did not add a type, add the DefaultForUse default.
addAnnotationsFromDefaultForType(TreeUtils.elementFromTree(tree), type);
log("%s GATF.addComputedTypeAnnotations#5(%s, %s)%n", thisClass, treeString, type);
}
typeAnnotator.visit(type, null);
log("%s GATF.addComputedTypeAnnotations#6(%s, %s)%n", thisClass, treeString, type);
defaults.annotate(tree, type);
log("%s GATF.addComputedTypeAnnotations#7(%s, %s)%n", thisClass, treeString, type);
if (iUseFlow) {
Value as = getInferredValueFor(tree);
if (as != null) {
applyInferredAnnotations(type, as);
log(
"%s GATF.addComputedTypeAnnotations#8(%s, %s), as=%s%n",
thisClass, treeString, type, as);
}
}
log(
"%s GATF.addComputedTypeAnnotations#9(%s, %s, %s) done%n",
thisClass, treeString, type, iUseFlow);
}
/**
* Flow analysis will be performed if all of the following are true.
*
* <ul>
* <li>tree is a {@link ClassTree}
* <li>Flow analysis has not already been performed on tree
* </ul>
*
* @param tree the tree to check and possibly perform flow analysis on
*/
protected void checkAndPerformFlowAnalysis(Tree tree) {
// For performance reasons, we require that getAnnotatedType is called
// on the ClassTree before it's called on any code contained in the class,
// so that we can perform flow analysis on the class. Previously we
// used TreePath.getPath to find enclosing classes, but that call
// alone consumed more than 10% of execution time. See BaseTypeVisitor
// .visitClass for the call to getAnnotatedType that triggers analysis.
if (tree instanceof ClassTree) {
ClassTree classTree = (ClassTree) tree;
if (!scannedClasses.containsKey(classTree)) {
performFlowAnalysis(classTree);
}
}
}
/**
* Returns the inferred value (by the org.checkerframework.dataflow analysis) for a given tree.
*/
public Value getInferredValueFor(Tree tree) {
if (tree == null) {
throw new BugInCF("GenericAnnotatedTypeFactory.getInferredValueFor called with null tree");
}
Value as = null;
if (analysis.isRunning()) {
as = analysis.getValue(tree);
}
if (as == null) {
as = flowResult.getValue(tree);
}
return as;
}
/**
* Applies the annotations inferred by the org.checkerframework.dataflow analysis to the type
* {@code type}.
*/
protected void applyInferredAnnotations(AnnotatedTypeMirror type, Value as) {
DefaultInferredTypesApplier applier =
new DefaultInferredTypesApplier(getQualifierHierarchy(), this);
applier.applyInferredType(type, as.getAnnotations(), as.getUnderlyingType());
}
/**
* Applies defaults for types in a class with an qualifier parameter.
*
* <p>Within a class with {@code @HasQualifierParameter}, types with that class default to the
* polymorphic qualifier rather than the typical default. Local variables with a type that has a
* qualifier parameter are initialized to the type of their initializer, rather than the default
* for local variables.
*
* @param tree a Tree whose type is {@code type}
* @param type where the defaults are applied
*/
protected void applyQualifierParameterDefaults(Tree tree, AnnotatedTypeMirror type) {
applyQualifierParameterDefaults(TreeUtils.elementFromTree(tree), type);
}
/**
* Applies defaults for types in a class with an qualifier parameter.
*
* <p>Within a class with {@code @HasQualifierParameter}, types with that class default to the
* polymorphic qualifier rather than the typical default. Local variables with a type that has a
* qualifier parameter are initialized to the type of their initializer, rather than the default
* for local variables.
*
* @param elt an Element whose type is {@code type}
* @param type where the defaults are applied
*/
protected void applyQualifierParameterDefaults(@Nullable Element elt, AnnotatedTypeMirror type) {
if (elt == null) {
return;
}
switch (elt.getKind()) {
case CONSTRUCTOR:
case METHOD:
case FIELD:
case LOCAL_VARIABLE:
case PARAMETER:
break;
default:
return;
}
applyLocalVariableQualifierParameterDefaults(elt, type);
TypeElement enclosingClass = ElementUtils.enclosingTypeElement(elt);
Set<AnnotationMirror> tops;
if (enclosingClass != null) {
tops = getQualifierParameterHierarchies(enclosingClass);
} else {
return;
}
if (tops.isEmpty()) {
return;
}
Set<AnnotationMirror> polyWithQualParam = AnnotationUtils.createAnnotationSet();
for (AnnotationMirror top : tops) {
AnnotationMirror poly = qualHierarchy.getPolymorphicAnnotation(top);
if (poly != null) {
polyWithQualParam.add(poly);
}
}
new TypeAnnotator(this) {
@Override
public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) {
if (type.getUnderlyingType().asElement().equals(enclosingClass)) {
type.addMissingAnnotations(polyWithQualParam);
}
return super.visitDeclared(type, aVoid);
}
}.visit(type);
}
/**
* Defaults local variables with types that have a qualifier parameter to the type of their
* initializer, if an initializer is present. Does nothing for local variables with no
* initializer.
*
* @param elt an Element whose type is {@code type}
* @param type where the defaults are applied
*/
private void applyLocalVariableQualifierParameterDefaults(Element elt, AnnotatedTypeMirror type) {
if (elt.getKind() != ElementKind.LOCAL_VARIABLE
|| getQualifierParameterHierarchies(type).isEmpty()
|| variablesUnderInitialization.contains(elt)) {
return;
}
Tree declTree = declarationFromElement(elt);
if (declTree == null || declTree.getKind() != Kind.VARIABLE) {
return;
}
ExpressionTree initializer = ((VariableTree) declTree).getInitializer();
if (initializer == null) {
return;
}
VariableElement variableElt = (VariableElement) elt;
variablesUnderInitialization.add(variableElt);
AnnotatedTypeMirror initializerType;
if (shouldCache && initializerCache.containsKey(initializer)) {
initializerType = initializerCache.get(initializer);
} else {
// When this method is called by getAnnotatedTypeLhs, flow is turned off.
// Turn it back on so the type of the initializer is the refined type.
boolean oldUseFlow = useFlow;
useFlow = everUseFlow;
try {
initializerType = getAnnotatedType(initializer);
} finally {
useFlow = oldUseFlow;
}
}
Set<AnnotationMirror> qualParamTypes = AnnotationUtils.createAnnotationSet();
for (AnnotationMirror initializerAnnotation : initializerType.getAnnotations()) {
if (hasQualifierParameterInHierarchy(
type, qualHierarchy.getTopAnnotation(initializerAnnotation))) {
qualParamTypes.add(initializerAnnotation);
}
}
type.addMissingAnnotations(qualParamTypes);
variablesUnderInitialization.remove(variableElt);
if (shouldCache) {
initializerCache.put(initializer, initializerType);
}
}
/**
* To add annotations to the type of method or constructor parameters, add a {@link TypeAnnotator}
* using {@link #createTypeAnnotator()} and see the comment in {@link
* TypeAnnotator#visitExecutable(org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType,
* Void)}.
*
* @param elt an element
* @param type the type obtained from {@code elt}
*/
@Override
public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) {
addAnnotationsFromDefaultForType(elt, type);
applyQualifierParameterDefaults(elt, type);
typeAnnotator.visit(type, null);
defaults.annotate(elt, type);
dependentTypesHelper.atLocalVariable(type, elt);
}
@Override
public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) {
ParameterizedExecutableType mType = super.methodFromUse(tree);
AnnotatedExecutableType method = mType.executableType;
dependentTypesHelper.atMethodInvocation(method, tree);
return mType;
}
@Override
public void methodFromUsePreSubstitution(ExpressionTree tree, AnnotatedExecutableType type) {
super.methodFromUsePreSubstitution(tree, type);
if (tree instanceof MethodInvocationTree) {
poly.resolve((MethodInvocationTree) tree, type);
}
}
@Override
public List<AnnotatedTypeParameterBounds> typeVariablesFromUse(
AnnotatedDeclaredType type, TypeElement element) {
List<AnnotatedTypeParameterBounds> f = super.typeVariablesFromUse(type, element);
dependentTypesHelper.atParameterizedTypeUse(f, element);
return f;
}
/**
* Returns the empty store.
*
* @return the empty store
*/
public Store getEmptyStore() {
return emptyStore;
}
/**
* Returns the AnnotatedTypeFactory of the subchecker and copies the current visitor state to the
* sub-factory so that the types are computed properly. Because the visitor state is copied, call
* this method each time a subfactory is needed rather than store the returned subfactory in a
* field.
*
* @see BaseTypeChecker#getTypeFactoryOfSubchecker(Class)
*/
@SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse
public <T extends GenericAnnotatedTypeFactory<?, ?, ?, ?>, U extends BaseTypeChecker>
T getTypeFactoryOfSubchecker(Class<U> checkerClass) {
T subFactory = checker.getTypeFactoryOfSubchecker(checkerClass);
if (subFactory != null && subFactory.getVisitorState() != null) {
// Copy the visitor state so that the types are computed properly.
VisitorState subFactoryVisitorState = subFactory.getVisitorState();
subFactoryVisitorState.setPath(visitorState.getPath());
subFactoryVisitorState.setClassTree(visitorState.getClassTree());
subFactoryVisitorState.setClassType(visitorState.getClassType());
subFactoryVisitorState.setMethodTree(visitorState.getMethodTree());
subFactoryVisitorState.setMethodReceiver(visitorState.getMethodReceiver());
}
return subFactory;
}
/**
* Should the local variable default annotation be applied to type variables?
*
* <p>It is initialized to true if data flow is used by the checker. It is set to false when
* getting the assignment context for type argument inference.
*
* @see GenericAnnotatedTypeFactory#getAnnotatedTypeLhsNoTypeVarDefault
* @return shouldDefaultTypeVarLocals
*/
public boolean getShouldDefaultTypeVarLocals() {
return shouldDefaultTypeVarLocals;
}
/** The CFGVisualizer to be used by all CFAbstractAnalysis instances. */
protected final CFGVisualizer<Value, Store, TransferFunction> cfgVisualizer;
/**
* Create a new CFGVisualizer.
*
* @return a new CFGVisualizer, or null if none will be used on this run
*/
protected @Nullable CFGVisualizer<Value, Store, TransferFunction> createCFGVisualizer() {
if (checker.hasOption("flowdotdir")) {
String flowdotdir = checker.getOption("flowdotdir");
if (flowdotdir.equals("")) {
throw new UserError("Emtpy string provided for -Aflowdotdir command-line argument");
}
boolean verbose = checker.hasOption("verbosecfg");
Map<String, Object> args = new HashMap<>(2);
args.put("outdir", flowdotdir);
args.put("verbose", verbose);
args.put("checkerName", getCheckerName());
CFGVisualizer<Value, Store, TransferFunction> res = new DOTCFGVisualizer<>();
res.init(args);
return res;
} else if (checker.hasOption("cfgviz")) {
String cfgviz = checker.getOption("cfgviz");
if (cfgviz == null) {
throw new UserError(
"-Acfgviz specified without arguments, should be -Acfgviz=VizClassName[,opts,...]");
}
String[] opts = cfgviz.split(",");
String vizClassName = opts[0];
if (!Signatures.isBinaryName(vizClassName)) {
throw new UserError(
"Bad -Acfgviz class name \"%s\", should be a binary name.", vizClassName);
}
Map<String, Object> args = processCFGVisualizerOption(opts);
if (!args.containsKey("verbose")) {
boolean verbose = checker.hasOption("verbosecfg");
args.put("verbose", verbose);
}
args.put("checkerName", getCheckerName());
CFGVisualizer<Value, Store, TransferFunction> res =
BaseTypeChecker.invokeConstructorFor(vizClassName, null, null);
res.init(args);
return res;
}
// Nobody expected to use cfgVisualizer if neither option given.
return null;
}
/** A simple utility method to determine a short checker name to be used by CFG visualizations. */
private String getCheckerName() {
String checkerName = checker.getClass().getSimpleName();
if (checkerName.endsWith("Checker")) {
checkerName = checkerName.substring(0, checkerName.length() - "Checker".length());
} else if (checkerName.endsWith("Subchecker")) {
checkerName = checkerName.substring(0, checkerName.length() - "Subchecker".length());
}
return checkerName;
}
/**
* Parse keys or key-value pairs into a map from key to value (to true if no value is provided).
*
* @param opts the CFG visualization options
* @return a map that represents the options
*/
private Map<String, Object> processCFGVisualizerOption(String[] opts) {
Map<String, Object> res = new HashMap<>(opts.length - 1);
// Index 0 is the visualizer class name and can be ignored.
for (int i = 1; i < opts.length; ++i) {
String opt = opts[i];
String[] split = opt.split("=");
switch (split.length) {
case 1:
res.put(split[0], true);
break;
case 2:
res.put(split[0], split[1]);
break;
default:
throw new UserError("Too many '=' in cfgviz option: " + opt);
}
}
return res;
}
/** The CFGVisualizer to be used by all CFAbstractAnalysis instances. */
public CFGVisualizer<Value, Store, TransferFunction> getCFGVisualizer() {
return cfgVisualizer;
}
@Override
public void postAsMemberOf(AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) {
super.postAsMemberOf(type, owner, element);
if (element.getKind() == ElementKind.FIELD) {
poly.resolve(((VariableElement) element), owner, type);
}
}
/**
* Adds default qualifiers based on the underlying type of {@code type} to {@code type}. If {@code
* element} is a local variable, then the defaults are not added.
*
* <p>(This uses both the {@link DefaultQualifierForUseTypeAnnotator} and {@link
* DefaultForTypeAnnotator}.)
*
* @param element possibly null element whose type is {@code type}
* @param type the type to which defaults are added
*/
protected void addAnnotationsFromDefaultForType(
@Nullable Element element, AnnotatedTypeMirror type) {
if (element != null && element.getKind() == ElementKind.LOCAL_VARIABLE) {
if (type.getKind() == TypeKind.DECLARED) {
// If this is a type for a local variable, don't apply the default to the primary location.
AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type;
if (declaredType.getEnclosingType() != null) {
defaultQualifierForUseTypeAnnotator.visit(declaredType.getEnclosingType());
defaultForTypeAnnotator.visit(declaredType.getEnclosingType());
}
for (AnnotatedTypeMirror typeArg : declaredType.getTypeArguments()) {
defaultQualifierForUseTypeAnnotator.visit(typeArg);
defaultForTypeAnnotator.visit(typeArg);
}
} else if (type.getKind().isPrimitive()) {
// Don't apply the default for local variables with primitive types.
} else {
defaultQualifierForUseTypeAnnotator.visit(type);
defaultForTypeAnnotator.visit(type);
}
} else {
defaultQualifierForUseTypeAnnotator.visit(type);
defaultForTypeAnnotator.visit(type);
}
}
// You can change this temporarily to produce verbose logs.
/** Whether to output verbose, low-level debugging messages. */
private static final boolean debug = false;
/**
* Output a message, if logging is on.
*
* @param format a format string
* @param args arguments to the format string
*/
@FormatMethod
private static void log(String format, Object... args) {
if (debug) {
SystemPlume.sleep(1); // logging can interleave with typechecker output
System.out.printf(format, args);
}
}
/**
* Cache of types found that are relevantTypes or subclass of supported types. Used so that
* isSubtype doesn't need to be called repeatedly on the same types.
*/
private Map<TypeMirror, Boolean> allFoundRelevantTypes = CollectionUtils.createLRUCache(300);
/**
* Returns true if users can write type annotations from this type system on the given Java type.
*
* @param tm a type
* @return true if users can write type annotations from this type system on the given Java type
*/
public boolean isRelevant(TypeMirror tm) {
tm = types.erasure(tm);
Boolean cachedResult = allFoundRelevantTypes.get(tm);
if (cachedResult != null) {
return cachedResult;
}
boolean result = isRelevantHelper(tm);
allFoundRelevantTypes.put(tm, result);
return result;
}
/**
* Returns true if users can write type annotations from this type system on the given Java type.
* Does not use a cache. Is a helper method for {@link #isRelevant}.
*
* @param tm a type
* @return true if users can write type annotations from this type system on the given Java type
*/
private boolean isRelevantHelper(TypeMirror tm) {
if (relevantJavaTypes.contains(tm)) {
return true;
}
switch (tm.getKind()) {
// Primitives have no subtyping relationships, but the lookup might have failed
// because tm has metadata such as annotations.
case BOOLEAN:
case BYTE:
case CHAR:
case DOUBLE:
case FLOAT:
case INT:
case LONG:
case SHORT:
for (TypeMirror relevantJavaType : relevantJavaTypes) {
if (types.isSameType(tm, relevantJavaType)) {
return true;
}
}
return false;
// Void is never relevant
case VOID:
return false;
case ARRAY:
return arraysAreRelevant;
case DECLARED:
for (TypeMirror relevantJavaType : relevantJavaTypes) {
if (types.isSubtype(relevantJavaType, tm) || types.isSubtype(tm, relevantJavaType)) {
return true;
}
}
return false;
case TYPEVAR:
return isRelevant(((TypeVariable) tm).getUpperBound());
default:
throw new BugInCF("isRelevantHelper(%s): Unexpected TypeKind %s", tm, tm.getKind());
}
}
/**
* Return the type of the default value of the given type. The default value is 0, false, or null.
*
* @param typeMirror a type
* @return the annotated type of {@code type}'s default value
*/
// TODO: Cache results to avoid recomputation.
public AnnotatedTypeMirror getDefaultValueAnnotatedType(TypeMirror typeMirror) {
AnnotatedTypeMirror defaultValue = AnnotatedTypeMirror.createType(typeMirror, this, false);
addComputedTypeAnnotations(
TreeUtils.getDefaultValueTree(typeMirror, processingEnv), defaultValue, false);
return defaultValue;
}
/**
* Return the contract annotations (that is, pre- and post-conditions) for the given AMethod. Does
* not modify the AMethod. This method must only be called when using WholeProgramInferenceScenes.
*
* @param m AFU representation of a method
* @return contract annotations for the method
*/
public List<AnnotationMirror> getContractAnnotations(AMethod m) {
List<AnnotationMirror> preconds = getPreconditionAnnotations(m);
List<AnnotationMirror> postconds = getPostconditionAnnotations(m, preconds);
List<AnnotationMirror> result = preconds;
result.addAll(postconds);
return result;
}
/**
* Return the precondition annotations for the given AMethod. Does not modify the AMethod. This
* method must only be called when using WholeProgramInferenceScenes.
*
* @param m AFU representation of a method
* @return precondition annotations for the method
*/
public List<AnnotationMirror> getPreconditionAnnotations(AMethod m) {
List<AnnotationMirror> result = new ArrayList<>(m.getPreconditions().size());
for (Map.Entry<VariableElement, AField> entry : m.getPreconditions().entrySet()) {
WholeProgramInferenceImplementation<?> wholeProgramInference =
(WholeProgramInferenceImplementation<?>) getWholeProgramInference();
WholeProgramInferenceScenesStorage storage =
(WholeProgramInferenceScenesStorage) wholeProgramInference.getStorage();
TypeMirror typeMirror = entry.getKey().asType();
AnnotatedTypeMirror inferredType =
storage.atmFromStorageLocation(typeMirror, entry.getValue().type);
result.addAll(getPreconditionAnnotation(entry.getKey(), inferredType));
}
Collections.sort(result, Ordering.usingToString());
return result;
}
/**
* Return the postcondition annotations for the given AMethod. Does not modify the AMethod. This
* method must only be called when using WholeProgramInferenceScenes.
*
* @param m AFU representation of a method
* @param preconds the precondition annotations for the method; used to suppress redundant
* postconditions
* @return postcondition annotations for the method
*/
public List<AnnotationMirror> getPostconditionAnnotations(
AMethod m, List<AnnotationMirror> preconds) {
List<AnnotationMirror> result = new ArrayList<>(m.getPostconditions().size());
for (Map.Entry<VariableElement, AField> entry : m.getPostconditions().entrySet()) {
WholeProgramInferenceImplementation<?> wholeProgramInference =
(WholeProgramInferenceImplementation<?>) getWholeProgramInference();
WholeProgramInferenceScenesStorage storage =
(WholeProgramInferenceScenesStorage) wholeProgramInference.getStorage();
TypeMirror typeMirror = entry.getKey().asType();
AnnotatedTypeMirror inferredType =
storage.atmFromStorageLocation(typeMirror, entry.getValue().type);
result.addAll(getPostconditionAnnotation(entry.getKey(), inferredType, preconds));
}
Collections.sort(result, Ordering.usingToString());
return result;
}
/**
* Return the contract annotations (that is, pre- and post-conditions) for the given
* CallableDeclarationAnnos. Does not modify the CallableDeclarationAnnos.
*
* @param methodAnnos annotation data for a method
* @return contract annotations for the method
*/
public List<AnnotationMirror> getContractAnnotations(
WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) {
List<AnnotationMirror> preconds = getPreconditionAnnotations(methodAnnos);
List<AnnotationMirror> postconds = getPostconditionAnnotations(methodAnnos, preconds);
List<AnnotationMirror> result = preconds;
result.addAll(postconds);
return result;
}
/**
* Return the precondition annotations for the given CallableDeclarationAnnos. Does not modify the
* CallableDeclarationAnnos.
*
* @param methodAnnos annotation data for a method
* @return precondition annotations for the method
*/
public List<AnnotationMirror> getPreconditionAnnotations(
WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) {
List<AnnotationMirror> result = new ArrayList<>();
for (Map.Entry<VariableElement, AnnotatedTypeMirror> entry :
methodAnnos.getFieldToPreconditions().entrySet()) {
result.addAll(getPreconditionAnnotation(entry.getKey(), entry.getValue()));
}
Collections.sort(result, Ordering.usingToString());
return result;
}
/**
* Return the postcondition annotations for the given CallableDeclarationAnnos. Does not modify
* the CallableDeclarationAnnos.
*
* @param methodAnnos annotation data for a method
* @param preconds the precondition annotations for the method; used to suppress redundant
* postconditions
* @return postcondition annotations for the method
*/
public List<AnnotationMirror> getPostconditionAnnotations(
WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos,
List<AnnotationMirror> preconds) {
List<AnnotationMirror> result = new ArrayList<>();
for (Map.Entry<VariableElement, AnnotatedTypeMirror> entry :
methodAnnos.getFieldToPostconditions().entrySet()) {
result.addAll(getPostconditionAnnotation(entry.getKey(), entry.getValue(), preconds));
}
Collections.sort(result, Ordering.usingToString());
return result;
}
/**
* Return a list of precondition annotations for the given field. Returns an empty list if none
* can be created, because the qualifier has elements/arguments, which {@code @RequiresQualifier}
* does not support.
*
* <p>This is of the form {@code @RequiresQualifier(expression="this.elt",
* qualifier=MyQual.class)} when elt is declared as {@code @A} or {@code @Poly*} and f contains
* {@code @B} which is a sub-qualifier of {@code @A}.
*
* @param elt element for a field, which is declared in the same class as the method
* @param inferredType the type of the field, on method entry
* @return precondition annotations for the element (possibly an empty list)
*/
public List<AnnotationMirror> getPreconditionAnnotation(
VariableElement elt, AnnotatedTypeMirror inferredType) {
return getPreOrPostconditionAnnotation(elt, inferredType, BeforeOrAfter.BEFORE, null);
}
/**
* Returns an {@code @EnsuresQualifier} annotation for the given field. Returns an empty list if
* none can be created, because the qualifier has elements/arguments, which
* {@code @EnsuresQualifier} does not support.
*
* <p>This implementation makes no assumptions about preconditions suppressing postconditions, but
* subclasses may do so.
*
* <p>This is of the form {@code @EnsuresQualifier(expression="this.elt", qualifier=MyQual.class)}
* when elt is declared as {@code @A} or {@code @Poly*} and f contains {@code @B} which is a
* sub-qualifier of {@code @A}.
*
* @param elt element for a field
* @param inferredType the type of the field, on method exit
* @param preconds the precondition annotations for the method; used to suppress redundant
* postconditions
* @return postcondition annotations for the element (possibly an empty list)
*/
public List<AnnotationMirror> getPostconditionAnnotation(
VariableElement elt, AnnotatedTypeMirror inferredType, List<AnnotationMirror> preconds) {
return getPreOrPostconditionAnnotation(elt, inferredType, BeforeOrAfter.AFTER, preconds);
}
/**
* Helper method for {@link #getPreconditionAnnotation} and {@link #getPostconditionAnnotation}.
*
* <p>Returns a {@code @RequiresQualifier} or {@code @EnsuresQualifier} annotation for the given
* field. Returns an empty list if none can be created, because the qualifier has
* elements/arguments, which {@code @RequiresQualifier} and {@code @EnsuresQualifier} do not
* support.
*
* <p>This implementation makes no assumptions about preconditions suppressing postconditions, but
* subclasses may do so.
*
* @param elt element for a field
* @param inferredType the type of the field, on method entry or exit (depending on the value of
* {@code preOrPost})
* @param preOrPost whether to return preconditions or postconditions
* @param preconds the precondition annotations for the method; used to suppress redundant
* postconditions; non-null exactly when {@code preOrPost} is {@code AFTER}
* @return precondition or postcondition annotations for the element (possibly an empty list)
*/
protected List<AnnotationMirror> getPreOrPostconditionAnnotation(
VariableElement elt,
AnnotatedTypeMirror inferredType,
Analysis.BeforeOrAfter preOrPost,
@Nullable List<AnnotationMirror> preconds) {
assert (preOrPost == BeforeOrAfter.BEFORE) == (preconds == null);
if (getWholeProgramInference() == null) {
return Collections.emptyList();
}
AnnotatedTypeMirror declaredType = fromElement(elt);
// TODO: should this only check the top-level annotations?
if (declaredType.equals(inferredType)) {
return Collections.emptyList();
}
List<AnnotationMirror> result = new ArrayList<AnnotationMirror>();
for (AnnotationMirror inferredAm : inferredType.getAnnotations()) {
AnnotationMirror declaredAm = declaredType.getAnnotationInHierarchy(inferredAm);
if (declaredAm == null && declaredType.getKind() != TypeKind.TYPEVAR) {
// There is no explicitly-written annotation for the given qualifier hierarchy.
// Determine the default.
addComputedTypeAnnotations(elt, declaredType);
declaredAm = declaredType.getAnnotationInHierarchy(inferredAm);
if (declaredAm == null) {
throw new BugInCF(
"getPreOrPostconditionAnnotation(%s, %s): no defaulted annotation%n declaredType=%s"
+ " [%s %s]%n inferredType=%s [%s %s]%n",
elt,
inferredType,
declaredType.toString(true),
declaredType.getKind(),
declaredType.getClass(),
inferredType.toString(true),
inferredType.getKind(),
inferredType.getClass());
}
}
if (declaredAm == null || AnnotationUtils.areSame(inferredAm, declaredAm)) {
continue;
}
// inferredAm must be a subtype of declaredAm (since they are not equal).
AnnotationMirror anno = requiresOrEnsuresQualifierAnno(elt, inferredAm, preOrPost);
if (anno != null) {
result.add(anno);
}
}
return result;
}
/**
* Returns a {@code RequiresQualifier("...")} or {@code EnsuresQualifier("...")} annotation for
* the given field. Returns null if none can be created, because the qualifier has
* elements/arguments, which {@code @RequiresQualifier} and {@code @EnsuresQualifier} do not
* support.
*
* <p>This is of the form {@code @RequiresQualifier(expression="this.elt",
* qualifier=MyQual.class)} or {@code @EnsuresQualifier(expression="this.elt",
* qualifier=MyQual.class)} when elt is declared as {@code @A} or {@code @Poly*} and f contains
* {@code @B} which is a sub-qualifier of {@code @A}.
*
* @param fieldElement a field
* @param qualifier the qualifier that must be present
* @param preOrPost whether to return a precondition or postcondition annotation
* @return a {@code RequiresQualifier("...")} or {@code EnsuresQualifier("...")} annotation for
* the given field, or null
*/
protected @Nullable AnnotationMirror requiresOrEnsuresQualifierAnno(
VariableElement fieldElement, AnnotationMirror qualifier, Analysis.BeforeOrAfter preOrPost) {
if (!qualifier.getElementValues().isEmpty()) {
// @RequiresQualifier does not yet support annotations with elements/arguments.
return null;
}
AnnotationBuilder builder =
new AnnotationBuilder(
processingEnv,
preOrPost == BeforeOrAfter.BEFORE ? RequiresQualifier.class : EnsuresQualifier.class);
String receiver = JavaExpression.getImplicitReceiver(fieldElement).toString();
String expression = receiver + "." + fieldElement.getSimpleName();
builder.setValue("expression", new String[] {expression});
builder.setValue("qualifier", AnnotationUtils.annotationMirrorToClass(qualifier));
return builder.build();
}
/**
* Add a new entry to the shared CFG. If this is a subchecker, this method delegates to the
* superchecker's GenericAnnotatedTypeFactory, if it exists. Duplicate keys must map to the same
* CFG.
*
* <p>Calls to this method should be guarded by checking {@link #hasOrIsSubchecker}; it is
* nonsensical to have a shared CFG when a checker is running alone.
*
* @param tree the source code corresponding to cfg
* @param cfg the control flow graph to use for tree
* @return whether a shared CFG was found to actually add to (duplicate keys also return true)
*/
public boolean addSharedCFGForTree(Tree tree, ControlFlowGraph cfg) {
if (!shouldCache) {
return false;
}
BaseTypeChecker parentChecker = this.checker.getUltimateParentChecker();
@SuppressWarnings("interning") // Checking reference equality.
boolean parentIsThisChecker = parentChecker == this.checker;
if (parentIsThisChecker) {
// This is the ultimate parent.
if (this.subcheckerSharedCFG == null) {
this.subcheckerSharedCFG = new HashMap<>(getCacheSize());
}
if (!this.subcheckerSharedCFG.containsKey(tree)) {
this.subcheckerSharedCFG.put(tree, cfg);
} else {
assert this.subcheckerSharedCFG.get(tree).equals(cfg);
}
return true;
}
// This is a subchecker.
if (parentChecker != null) {
GenericAnnotatedTypeFactory<?, ?, ?, ?> parentAtf = parentChecker.getTypeFactory();
return parentAtf.addSharedCFGForTree(tree, cfg);
} else {
return false;
}
}
/**
* Get the shared control flow graph used for {@code tree} by this checker's topmost superchecker.
* Returns null if no information is available about the given tree, or if this checker has a
* parent checker that does not have a GenericAnnotatedTypeFactory.
*
* <p>Calls to this method should be guarded by checking {@link #hasOrIsSubchecker}; it is
* nonsensical to have a shared CFG when a checker is running alone.
*
* @param tree the tree whose CFG should be looked up
* @return the CFG stored by this checker's uppermost superchecker for tree, or null if it is not
* available
*/
public @Nullable ControlFlowGraph getSharedCFGForTree(Tree tree) {
if (!shouldCache) {
return null;
}
BaseTypeChecker parentChecker = this.checker.getUltimateParentChecker();
@SuppressWarnings("interning") // Checking reference equality.
boolean parentIsThisChecker = parentChecker == this.checker;
if (parentIsThisChecker) {
// This is the ultimate parent;
return this.subcheckerSharedCFG == null
? null
: this.subcheckerSharedCFG.getOrDefault(tree, null);
}
// This is a subchecker.
if (parentChecker != null) {
GenericAnnotatedTypeFactory<?, ?, ?, ?> parentAtf = parentChecker.getTypeFactory();
return parentAtf.getSharedCFGForTree(tree);
} else {
return null;
}
}
/**
* If kind = CONDITIONALPOSTCONDITION, return the result element, e.g. {@link
* EnsuresQualifierIf#result}. Otherwise, return null.
*
* @param kind the kind of {@code contractAnnotation}
* @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link
* EnsuresQualifierIf}
* @return the {@code result} element of {@code contractAnnotation}, or null if it doesn't have a
* {@code result} element
*/
public Boolean getEnsuresQualifierIfResult(
Contract.Kind kind, AnnotationMirror contractAnnotation) {
if (kind == Contract.Kind.CONDITIONALPOSTCONDITION) {
if (contractAnnotation instanceof EnsuresQualifierIf) {
// It's the framework annotation @EnsuresQualifierIf
return AnnotationUtils.getElementValueBoolean(
contractAnnotation, ensuresQualifierIfResultElement, /*default is irrelevant*/ false);
} else {
// It's a checker-specific annotation such as @EnsuresMinLenIf
@SuppressWarnings("deprecation") // concrete annotation class is not known
Boolean result =
AnnotationUtils.getElementValue(contractAnnotation, "result", Boolean.class, false);
return result;
}
} else {
return null;
}
}
/**
* If {@code contractAnnotation} is a framework annotation, return its {@code expression} element.
* Otherwise, {@code contractAnnotation} is defined in a checker. If kind =
* CONDITIONALPOSTCONDITION, return its {@code expression} element, else return its {@code value}
* element.
*
* @param kind the kind of {@code contractAnnotation}
* @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link
* EnsuresQualifierIf}
* @return the {@code result} element of {@code contractAnnotation}, or null if it doesn't have a
* {@code result} element
*/
public List<String> getContractExpressions(
Contract.Kind kind, AnnotationMirror contractAnnotation) {
// First, handle framework annotations.
if (contractAnnotation instanceof RequiresQualifier) {
return AnnotationUtils.getElementValueArray(
contractAnnotation, requiresQualifierExpressionElement, String.class);
} else if (contractAnnotation instanceof EnsuresQualifier) {
return AnnotationUtils.getElementValueArray(
contractAnnotation, ensuresQualifierExpressionElement, String.class);
} else if (contractAnnotation instanceof EnsuresQualifierIf) {
return AnnotationUtils.getElementValueArray(
contractAnnotation, ensuresQualifierIfExpressionElement, String.class);
}
// `contractAnnotation` is defined in a checker.
String elementName = kind == Contract.Kind.CONDITIONALPOSTCONDITION ? "expression" : "value";
@SuppressWarnings("deprecation") // concrete annotation class is not known
List<String> result =
AnnotationUtils.getElementValueArray(contractAnnotation, elementName, String.class, true);
return result;
}
}