package org.checkerframework.framework.type;
/*>>>
import org.checkerframework.checker.nullness.qual.Nullable;
*/
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.ModifiersTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ReturnTree;
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.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
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 org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.dataflow.analysis.AnalysisResult;
import org.checkerframework.dataflow.analysis.FlowExpressions;
import org.checkerframework.dataflow.analysis.FlowExpressions.FieldAccess;
import org.checkerframework.dataflow.analysis.FlowExpressions.LocalVariable;
import org.checkerframework.dataflow.analysis.TransferInput;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.CFGBuilder;
import org.checkerframework.dataflow.cfg.CFGVisualizer;
import org.checkerframework.dataflow.cfg.ControlFlowGraph;
import org.checkerframework.dataflow.cfg.DOTCFGVisualizer;
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.Node;
import org.checkerframework.dataflow.cfg.node.ReturnNode;
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.DefaultInUncheckedCodeFor;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
import org.checkerframework.framework.qual.DefaultQualifierInHierarchyInUncheckedCode;
import org.checkerframework.framework.qual.ImplicitFor;
import org.checkerframework.framework.qual.MonotonicQualifier;
import org.checkerframework.framework.qual.RelevantJavaTypes;
import org.checkerframework.framework.qual.TypeUseLocation;
import org.checkerframework.framework.qual.Unqualified;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.treeannotator.ImplicitsTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.framework.type.typeannotator.ImplicitsTypeAnnotator;
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.FlowExpressionParseUtil;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionParseException;
import org.checkerframework.framework.util.QualifierPolymorphism;
import org.checkerframework.framework.util.defaults.QualifierDefaults;
import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper;
import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ErrorReporter;
import org.checkerframework.javacutil.InternalUtils;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreeUtils;
/**
* A factory that extends {@link AnnotatedTypeFactory} to optionally use flow-sensitive qualifier
* inference, qualifier polymorphism, implicit annotations via {@link ImplicitFor}, and
* user-specified defaults via {@link DefaultQualifier}.
*/
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 FLOW_BY_DEFAULT = 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 addTypeImplicits */
private ImplicitsTypeAnnotator implicitsTypeAnnotator;
/** 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 */
protected DependentTypesHelper dependentTypesHelper;
// 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;
/** An empty store. */
private Store emptyStore;
/**
* Creates a type factory for checking the given compilation unit with respect to the given
* annotation.
*
* @param checker the checker to which this type factory belongs
* @param useFlow whether flow analysis should be performed
*/
public GenericAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) {
super(checker);
this.everUseFlow = useFlow;
this.shouldDefaultTypeVarLocals = useFlow;
this.useFlow = useFlow;
this.analyses = new LinkedList<>();
this.scannedClasses = new HashMap<>();
this.flowResult = null;
this.regularExitStores = null;
this.methodInvocationStores = null;
this.returnStatementStores = null;
this.initializationStore = null;
this.initializationStaticStore = null;
this.cfgVisualizer = createCFGVisualizer();
// Add common aliases.
// addAliasedDeclAnnotation(checkers.nullness.quals.Pure.class,
// Pure.class, AnnotationUtils.fromClass(elements, Pure.class));
// 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 = createQualifierDefaults();
this.treeAnnotator = createTreeAnnotator();
this.typeAnnotator = createTypeAnnotator();
this.poly = createQualifierPolymorphism();
this.parseStubFiles();
}
/**
* Preforms flow-sensitive type refinement on {@code classTree} if this type factory is
* configured to do so.
*
* @param classTree tree on which to preform flow-sensitive type refinement
*/
@Override
public void preProcessClassTree(ClassTree classTree) {
if (this.everUseFlow) {
checkAndPerformFlowAnalysis(classTree);
}
}
/**
* Creates a type factory for checking the given compilation unit with respect to the given
* annotation.
*
* @param checker the checker to which this type factory belongs
*/
public GenericAnnotatedTypeFactory(BaseTypeChecker checker) {
this(checker, FLOW_BY_DEFAULT);
}
@Override
public void setRoot(/*@Nullable*/ CompilationUnitTree root) {
super.setRoot(root);
this.analyses.clear();
this.scannedClasses.clear();
this.flowResult = null;
this.regularExitStores = null;
this.methodInvocationStores = null;
this.returnStatementStores = null;
this.initializationStore = null;
this.initializationStaticStore = null;
}
// **********************************************************************
// 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>Subclasses may override this method to specify a more appropriate {@link TreeAnnotator}.
* The default tree annotator is a {@link ListTreeAnnotator} of the following:
*
* <ol>
* <li>{@link PropagationTreeAnnotator}: Propagates annotations from subtrees.
* <li>{@link ImplicitsTreeAnnotator}: Adds annotations based on {@link ImplicitFor}
* meta-annotations
* </ol>
*
* @return a tree annotator
*/
protected TreeAnnotator createTreeAnnotator() {
List<TreeAnnotator> treeAnnotators = new ArrayList<>();
treeAnnotators.add(new PropagationTreeAnnotator(this));
treeAnnotators.add(new ImplicitsTreeAnnotator(this));
if (dependentTypesHelper != null) {
treeAnnotators.add(dependentTypesHelper.createDependentTypesTreeAnnotator(this));
}
return new ListTreeAnnotator(treeAnnotators);
}
/**
* Returns a {@link org.checkerframework.framework.type.typeannotator.ImplicitsTypeAnnotator}
* 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 {@link
* RelevantJavaTypes} annotation on the checker
* <li>{@link PropagationTypeAnnotator}: Propagates annotation onto wildcards
* <li>{@link ImplicitsTypeAnnotator}: Adds annotations based on {@link ImplicitFor}
* meta-annotations
* </ol>
*
* @return a type annotator
*/
protected TypeAnnotator createTypeAnnotator() {
List<TypeAnnotator> typeAnnotators = new ArrayList<>();
RelevantJavaTypes relevantJavaTypes =
checker.getClass().getAnnotation(RelevantJavaTypes.class);
if (relevantJavaTypes != null) {
Class<?>[] classes = relevantJavaTypes.value();
// Must be first in order to annotated all irrelevant types that are not explicilty
// annotated.
typeAnnotators.add(
new IrrelevantTypeAnnotator(
this, getQualifierHierarchy().getTopAnnotations(), classes));
}
typeAnnotators.add(new PropagationTypeAnnotator(this));
implicitsTypeAnnotator = new ImplicitsTypeAnnotator(this);
typeAnnotators.add(implicitsTypeAnnotator);
return new ListTypeAnnotator(typeAnnotators);
}
protected void addTypeNameImplicit(Class<?> clazz, AnnotationMirror implicitAnno) {
implicitsTypeAnnotator.addTypeName(clazz, implicitAnno);
}
/**
* 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) {
final String classToLoad =
checkerClass
.getName()
.replace("Checker", "Analysis")
.replace("Subchecker", "Analysis");
FlowAnalysis result =
BaseTypeChecker.invokeConstructorFor(
classToLoad,
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 = new ArrayList<>();
for (Pair<VariableElement, Value> fieldVal : fieldValues) {
assert fieldVal.second instanceof CFValue;
tmp.add(Pair.<VariableElement, CFValue>of(fieldVal.first, (CFValue) fieldVal.second));
}
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) {
final String classToLoad =
checkerClass
.getName()
.replace("Checker", "Transfer")
.replace("Subchecker", "Transfer");
TransferFunction result =
BaseTypeChecker.invokeConstructorFor(
classToLoad,
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 an {@link DependentTypesHelper} and returns it.
*
* @return a new {@link DependentTypesHelper}
*/
protected DependentTypesHelper createDependentTypesHelper() {
DependentTypesHelper helper = new DependentTypesHelper(this);
if (helper.hasDependentAnnotations()) {
return helper;
}
return null;
}
public DependentTypesHelper getDependentTypesHelper() {
return dependentTypesHelper;
}
@Override
public AnnotatedDeclaredType fromNewClass(NewClassTree newClassTree) {
AnnotatedDeclaredType superResult = super.fromNewClass(newClassTree);
if (dependentTypesHelper != null) {
dependentTypesHelper.standardizeNewClassTree(newClassTree, superResult);
}
return superResult;
}
/**
* Create {@link QualifierDefaults} which handles checker specified defaults. Subclasses should
* override {@link GenericAnnotatedTypeFactory#addCheckedCodeDefaults(QualifierDefaults defs)}
* or {@link GenericAnnotatedTypeFactory#addUncheckedCodeDefaults(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 createQualifierDefaults() {
QualifierDefaults defs = new QualifierDefaults(elements, this);
addCheckedCodeDefaults(defs);
addCheckedStandardDefaults(defs);
addUncheckedCodeDefaults(defs);
addUncheckedStandardDefaults(defs);
checkForDefaultQualifierInHierarchy(defs);
return defs;
}
/** Defines alphabetical sort ordering for qualifiers */
private static final Comparator<Class<? extends Annotation>> QUALIFIER_SORT_ORDERING =
new Comparator<Class<? extends Annotation>>() {
@Override
public int compare(Class<? extends Annotation> a1, Class<? extends Annotation> a2) {
return a1.getCanonicalName().compareTo(a2.getCanonicalName());
}
};
/**
* 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() {
// Create a list of the supported qualifiers and sort the list
// alphabetically
List<Class<? extends Annotation>> sortedSupportedQuals =
new ArrayList<Class<? extends Annotation>>();
sortedSupportedQuals.addAll(getSupportedTypeQualifiers());
Collections.sort(sortedSupportedQuals, QUALIFIER_SORT_ORDERING);
// display the number of qualifiers as well as the names of each
// qualifier.
StringBuilder sb = new StringBuilder();
sb.append(sortedSupportedQuals.size());
sb.append(" qualifiers examined");
if (sortedSupportedQuals.size() > 0) {
sb.append(": ");
// for each qualifier, add its canonical name, a comma and a space
// to the string.
for (Class<? extends Annotation> qual : sortedSupportedQuals) {
sb.append(qual.getCanonicalName());
sb.append(", ");
}
// remove last comma and space
return sb.substring(0, sb.length() - 2);
} else {
return sb.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) {
boolean foundOtherwise = false;
// 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(AnnotationUtils.fromClass(elements, qual), locations);
foundOtherwise =
foundOtherwise
|| Arrays.asList(locations).contains(TypeUseLocation.OTHERWISE);
}
if (qual.getAnnotation(DefaultQualifierInHierarchy.class) != null) {
defs.addCheckedCodeDefault(
AnnotationUtils.fromClass(elements, qual), TypeUseLocation.OTHERWISE);
foundOtherwise = true;
}
}
// If Unqualified is a supported qualifier, make it the default.
AnnotationMirror unqualified = AnnotationUtils.fromClass(elements, Unqualified.class);
if (!foundOtherwise && this.isSupportedQualifier(unqualified)) {
defs.addCheckedCodeDefault(unqualified, 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) {
Set<? extends AnnotationMirror> tops = this.qualHierarchy.getTopAnnotations();
Set<? extends AnnotationMirror> bottoms = this.qualHierarchy.getBottomAnnotations();
defs.addClimbStandardDefaults(tops, bottoms);
}
}
/**
* Adds default qualifiers for code that is not type-checked by reading
* {@code @DefaultInUncheckedCodeFor} and {@code @DefaultQualifierInHierarchyInUncheckedCode}
* meta-annotations. Then it applies the standard unchecked code defaults, if a default was not
* specified for a particular location.
*
* <p>Standard unchecked code default are: <br>
* top: {@code TypeUseLocation.RETURN,TypeUseLocation.FIELD,TypeUseLocation.UPPER_BOUND}<br>
* bottom: {@code TypeUseLocation.PARAMETER, TypeUseLocation.LOWER_BOUND}<br>
*
* <p>If {@code @DefaultQualifierInHierarchyInUncheckedCode} code is not found or a default for
* {@code TypeUseLocation.Otherwise} is not used, the defaults for checked code will be applied
* to locations without a default for unchecked code.
*
* <p>Subclasses may override this method to add defaults that cannot be specified with a
* {@code @DefaultInUncheckedCodeFor} or {@code @DefaultQualifierInHierarchyInUncheckedCode}
* meta-annotations or to change the standard defaults.
*
* @param defs {@link QualifierDefaults} object to which defaults are added
*/
protected void addUncheckedCodeDefaults(QualifierDefaults defs) {
for (Class<? extends Annotation> annotation : getSupportedTypeQualifiers()) {
DefaultInUncheckedCodeFor defaultInUncheckedCodeFor =
annotation.getAnnotation(DefaultInUncheckedCodeFor.class);
if (defaultInUncheckedCodeFor != null) {
final TypeUseLocation[] locations = defaultInUncheckedCodeFor.value();
defs.addUncheckedCodeDefaults(
AnnotationUtils.fromClass(elements, annotation), locations);
}
if (annotation.getAnnotation(DefaultQualifierInHierarchyInUncheckedCode.class)
!= null) {
defs.addUncheckedCodeDefault(
AnnotationUtils.fromClass(elements, annotation), TypeUseLocation.OTHERWISE);
}
}
}
/**
* 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) {
Set<? extends AnnotationMirror> tops = this.qualHierarchy.getTopAnnotations();
Set<? extends AnnotationMirror> bottoms = this.qualHierarchy.getBottomAnnotations();
defs.addUncheckedStandardDefaults(tops, bottoms);
}
/**
* 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()) {
ErrorReporter.errorAbort(
"GenericAnnotatedTypeFactory.createQualifierDefaults: "
+ "@DefaultQualifierInHierarchy or @DefaultFor(TypeUseLocation.OTHERWISE) not found. "
+ "Every checker must specify a default qualifier. "
+ getSortedQualifierNames());
}
// Don't require @DefaultQualifierInHierarchyInUncheckedCode or an
// unchecked default for TypeUseLocation.OTHERWISE.
// If a default unchecked code qualifier isn't specified, the defaults
// for checked code will be used.
}
/**
* Creates {@link QualifierPolymorphism} which supports QualifierPolymorphism mechanism
*
* @return the QualifierPolymorphism class
*/
protected QualifierPolymorphism createQualifierPolymorphism() {
return new QualifierPolymorphism(processingEnv, this);
}
// **********************************************************************
// 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 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 FlowExpressionParseException thrown if the expression cannot be parsed
*/
public AnnotationMirror getAnnotationFromJavaExpressionString(
String expression, Tree tree, TreePath path, Class<? extends Annotation> clazz)
throws FlowExpressionParseException {
FlowExpressions.Receiver expressionObj =
getReceiverFromJavaExpressionString(expression, path);
AnnotationMirror annotationMirror = null;
if (CFAbstractStore.canInsertReceiver(expressionObj)) {
Store store = getStoreBefore(tree);
if (store != null) {
Value value = store.getValue(expressionObj);
if (value != null) {
annotationMirror =
AnnotationUtils.getAnnotationByClass(value.getAnnotations(), clazz);
}
}
}
if (annotationMirror == null) {
if (expressionObj instanceof LocalVariable) {
Element ele = ((LocalVariable) expressionObj).getElement();
annotationMirror = getAnnotatedType(ele).getAnnotation(clazz);
} else if (expressionObj instanceof FieldAccess) {
Element ele = ((FieldAccess) expressionObj).getField();
annotationMirror = getAnnotatedType(ele).getAnnotation(clazz);
}
}
return annotationMirror;
}
/**
* Produces the receiver associated with expression on currentPath.
*
* @param expression Java expression
* @param currentPath location at which expression is evaluated
* @throws FlowExpressionParseException thrown if the expression cannot be parsed
*/
public FlowExpressions.Receiver getReceiverFromJavaExpressionString(
String expression, TreePath currentPath) throws FlowExpressionParseException {
TypeMirror enclosingClass = InternalUtils.typeOf(TreeUtils.enclosingClass(currentPath));
FlowExpressions.Receiver r =
FlowExpressions.internalRepOfPseudoReceiver(currentPath, enclosingClass);
FlowExpressionParseUtil.FlowExpressionContext context =
new FlowExpressionParseUtil.FlowExpressionContext(
r,
FlowExpressions.getParametersOfEnclosingMethod(this, currentPath),
this.getContext());
return FlowExpressionParseUtil.parse(expression, context, currentPath, true);
}
/**
* Track the state of org.checkerframework.dataflow analysis scanning for each class tree in the
* compilation unit.
*/
protected enum ScanState {
IN_PROGRESS,
FINISHED
};
protected final Map<ClassTree, ScanState> scannedClasses;
/**
* The result of the flow analysis. Invariant:
*
* <pre>
* scannedClasses.get(c) == FINISHED for some class c ⇒ 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 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).
*
* @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 t) {
return regularExitStores.get(t);
}
/** @return all return node and store pairs for a given method */
public List<Pair<ReturnNode, TransferResult<Value, Store>>> getReturnStatementStores(
MethodTree methodTree) {
assert returnStatementStores.containsKey(methodTree);
return returnStatementStores.get(methodTree);
}
/** @return the store immediately before a given {@link Tree}. */
public Store getStoreBefore(Tree tree) {
if (analyses.isEmpty()) {
return flowResult.getStoreBefore(tree);
}
FlowAnalysis analysis = analyses.getFirst();
Node node = analysis.getNodeForTree(tree);
if (node == null) {
// TODO: is there something better we can do? Check for
// lambda expressions. This fixes Issue 448, but might not
// be the best possible.
return null;
}
return getStoreBefore(node);
}
/** @return the store immediately before a given {@link Node}. */
public Store getStoreBefore(Node node) {
if (analyses.isEmpty()) {
return flowResult.getStoreBefore(node);
}
FlowAnalysis analysis = analyses.getFirst();
TransferInput<Value, Store> prevStore = analysis.getInput(node.getBlock());
if (prevStore == null) {
return null;
}
Store store = AnalysisResult.runAnalysisFor(node, true, prevStore);
return store;
}
/** @return the store immediately after a given {@link Tree}. */
public Store getStoreAfter(Tree tree) {
if (analyses.isEmpty()) {
return flowResult.getStoreAfter(tree);
}
FlowAnalysis analysis = analyses.getFirst();
Node node = analysis.getNodeForTree(tree);
Store store =
AnalysisResult.runAnalysisFor(node, false, analysis.getInput(node.getBlock()));
return store;
}
/** @return the {@link Node} for a given {@link Tree}. */
public Node getNodeForTree(Tree tree) {
return flowResult.getNodeForTree(tree);
}
/** @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.
*/
protected void performFlowAnalysis(ClassTree classTree) {
if (flowResult == null) {
regularExitStores = new IdentityHashMap<>();
returnStatementStores = new IdentityHashMap<>();
flowResult = new AnalysisResult<>();
}
// 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<ClassTree> queue = new LinkedList<>();
List<Pair<VariableElement, Value>> fieldValues = new ArrayList<>();
queue.add(classTree);
while (!queue.isEmpty()) {
ClassTree ct = queue.remove();
scannedClasses.put(ct, ScanState.IN_PROGRESS);
AnnotatedDeclaredType preClassType = visitorState.getClassType();
ClassTree preClassTree = visitorState.getClassTree();
AnnotatedDeclaredType preAMT = visitorState.getMethodReceiver();
MethodTree preMT = visitorState.getMethodTree();
visitorState.setClassType(getAnnotatedType(ct));
visitorState.setClassTree(ct);
visitorState.setMethodReceiver(null);
visitorState.setMethodTree(null);
// start without a initialization store
initializationStaticStore = null;
initializationStore = null;
Queue<Pair<LambdaExpressionTree, Store>> lambdaQueue = new LinkedList<>();
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.
ModifiersTree modifiers = mt.getModifiers();
if (modifiers != null) {
Set<Modifier> flags = modifiers.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);
Value value = flowResult.getValue(initializer);
if (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.
queue.add((ClassTree) m);
break;
case BLOCK:
BlockTree b = (BlockTree) m;
analyze(
queue,
lambdaQueue,
new CFGStatement(b, ct),
fieldValues,
ct,
true,
true,
b.isStatic());
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);
}
while (lambdaQueue.size() > 0) {
Pair<LambdaExpressionTree, Store> lambdaPair = lambdaQueue.poll();
analyze(
queue,
lambdaQueue,
new CFGLambda(lambdaPair.first),
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.setClassType(preClassType);
visitorState.setClassTree(preClassTree);
visitorState.setMethodReceiver(preAMT);
visitorState.setMethodTree(preMT);
}
scannedClasses.put(ct, ScanState.FINISHED);
}
}
// Maintain a deque of analyses to accommodate nested classes.
protected final Deque<FlowAnalysis> analyses;
// 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;
/**
* Analyze the AST {@code ast} and store the result.
*
* @param queue the queue to add more things to scan
* @param fieldValues the abstract values for all fields of the same class
* @param ast the AST to analyze
* @param currentClass the class we are currently looking at
* @param isInitializationCode are we analyzing a (non-static) initializer block of a class
*/
protected void analyze(
Queue<ClassTree> queue,
Queue<Pair<LambdaExpressionTree, Store>> lambdaQueue,
UnderlyingAST ast,
List<Pair<VariableElement, Value>> fieldValues,
ClassTree currentClass,
boolean isInitializationCode,
boolean updateInitializationStore,
boolean isStatic) {
analyze(
queue,
lambdaQueue,
ast,
fieldValues,
currentClass,
isInitializationCode,
updateInitializationStore,
isStatic,
null);
}
protected void analyze(
Queue<ClassTree> queue,
Queue<Pair<LambdaExpressionTree, Store>> lambdaQueue,
UnderlyingAST ast,
List<Pair<VariableElement, Value>> fieldValues,
ClassTree currentClass,
boolean isInitializationCode,
boolean updateInitializationStore,
boolean isStatic,
Store lambdaStore) {
CFGBuilder builder = new CFCFGBuilder(checker, this);
ControlFlowGraph cfg = builder.run(root, processingEnv, ast);
FlowAnalysis newAnalysis = createFlowAnalysis(fieldValues);
TransferFunction transfer = newAnalysis.getTransferFunction();
if (emptyStore == null) {
emptyStore = newAnalysis.createEmptyStore(transfer.usesSequentialSemantics());
}
analyses.addFirst(newAnalysis);
if (lambdaStore != null) {
transfer.setFixedInitialStore(lambdaStore);
} else {
Store initStore = !isStatic ? initializationStore : initializationStaticStore;
if (isInitializationCode) {
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);
}
}
}
analyses.getFirst().performAnalysis(cfg);
AnalysisResult<Value, Store> result = analyses.getFirst().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 = analyses.getFirst().getRegularExitStore();
if (regularExitStore != null) {
regularExitStores.put(method, regularExitStore);
}
returnStatementStores.put(method, analyses.getFirst().getReturnStatementStores());
} else if (ast.getKind() == UnderlyingAST.Kind.ARBITRARY_CODE) {
CFGStatement block = (CFGStatement) ast;
Store regularExitStore = analyses.getFirst().getRegularExitStore();
if (regularExitStore != null) {
regularExitStores.put(block.getCode(), regularExitStore);
}
} else if (ast.getKind() == UnderlyingAST.Kind.LAMBDA) {
// TODO: Postconditions?
CFGLambda block = (CFGLambda) ast;
Store regularExitStore = analyses.getFirst().getRegularExitStore();
if (regularExitStore != null) {
regularExitStores.put(block.getCode(), regularExitStore);
}
}
if (isInitializationCode && updateInitializationStore) {
Store newInitStore = analyses.getFirst().getRegularExitStore();
if (!isStatic) {
initializationStore = newInitStore;
} else {
initializationStaticStore = newInitStore;
}
}
if (checker.hasOption("flowdotdir") || checker.hasOption("cfgviz")) {
handleCFGViz();
}
analyses.removeFirst();
// add classes declared in method
queue.addAll(builder.getDeclaredClasses());
for (LambdaExpressionTree lambda : builder.getDeclaredLambdas()) {
lambdaQueue.add(Pair.of(lambda, getStoreBefore(lambda)));
}
}
/**
* Handle the visualization of the CFG, by calling {@code visualizeCFG} on the first analysis.
* This method gets invoked in {@code analyze} if on of the visualization options is provided.
*/
protected void handleCFGViz() {
analyses.getFirst().visualizeCFG();
}
/**
* 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 = null;
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;
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 {
ErrorReporter.errorAbort(
"GenericAnnotatedTypeFactory: Unexpected tree passed to getAnnotatedTypeLhs. "
+ "lhsTree: "
+ lhsTree
+ " Tree.Kind: "
+ lhsTree.getKind());
}
}
useFlow = oldUseFlow;
shouldCache = oldShouldCache;
return res;
}
@Override
public Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> constructorFromUse(
NewClassTree tree) {
Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> mfuPair =
super.constructorFromUse(tree);
AnnotatedExecutableType method = mfuPair.first;
if (dependentTypesHelper != null) {
dependentTypesHelper.viewpointAdaptConstructor(tree, method);
}
poly.annotate(tree, method);
return mfuPair;
}
@Override
public AnnotatedTypeMirror getMethodReturnType(MethodTree m) {
AnnotatedTypeMirror returnType = super.getMethodReturnType(m);
if (dependentTypesHelper != null) {
dependentTypesHelper.standardizeReturnType(m, returnType);
}
return returnType;
}
@Override
public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) {
AnnotatedTypeMirror returnType = super.getMethodReturnType(m, r);
if (dependentTypesHelper != null) {
dependentTypesHelper.standardizeReturnType(m, returnType);
}
return returnType;
}
/**
* 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 {#addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}. Overriding implementations
* typically simply pass the boolean to calls to super.
*/
protected void addComputedTypeAnnotations(
Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) {
assert root != null
: "GenericAnnotatedTypeFactory.addComputedTypeAnnotations: "
+ " root needs to be set when used on trees; factory: "
+ this.getClass();
treeAnnotator.visit(tree, type);
typeAnnotator.visit(type, null);
defaults.annotate(tree, type);
if (iUseFlow) {
Value as;
if (tree.getKind() == Kind.POSTFIX_DECREMENT
|| tree.getKind() == Kind.POSTFIX_INCREMENT) {
// Dataflow incorrectly treats postfix as prefix.
// See Issue 867: https://github.com/typetools/checker-framework/issues/867
as = getInferredValueFor(((UnaryTree) tree).getExpression());
} else {
as = getInferredValueFor(tree);
}
if (as != null) {
applyInferredAnnotations(type, as);
}
}
}
/**
* Flow analysis will be performed if:
*
* <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) {
ErrorReporter.errorAbort(
"GenericAnnotatedTypeFactory.getInferredValueFor called with null tree. Don't!");
return null; // dead code
}
Value as = null;
if (!analyses.isEmpty()) {
as = analyses.getFirst().getValue(tree);
}
if (as == null
&&
// TODO: this comparison shouldn't be needed, but
// Daikon check-nullness started failing without it.
flowResult != 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());
}
@Override
public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) {
typeAnnotator.visit(type, null);
defaults.annotate(elt, type);
if (dependentTypesHelper != null) {
dependentTypesHelper.standardizeVariable(type, elt);
}
}
@Override
public Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> methodFromUse(
MethodInvocationTree tree) {
Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> mfuPair =
super.methodFromUse(tree);
AnnotatedExecutableType method = mfuPair.first;
if (dependentTypesHelper != null) {
dependentTypesHelper.viewpointAdaptMethod(tree, method);
}
poly.annotate(tree, method);
return mfuPair;
}
@Override
public List<AnnotatedTypeParameterBounds> typeVariablesFromUse(
AnnotatedDeclaredType type, TypeElement element) {
List<AnnotatedTypeParameterBounds> f = super.typeVariablesFromUse(type, element);
if (dependentTypesHelper != null) {
dependentTypesHelper.viewpointAdaptTypeVariableBounds(
element, f, visitorState.getPath());
}
return f;
}
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 factory 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.setMethodTree(visitorState.getMethodTree());
}
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;
protected CFGVisualizer<Value, Store, TransferFunction> createCFGVisualizer() {
if (checker.hasOption("flowdotdir")) {
String flowdotdir = checker.getOption("flowdotdir");
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<Value, Store, TransferFunction>();
res.init(args);
return res;
} else if (checker.hasOption("cfgviz")) {
String cfgviz = checker.getOption("cfgviz");
if (cfgviz == null) {
ErrorReporter.errorAbort(
"-Acfgviz specified without arguments, should be -Acfgviz=VizClassName[,opts,...]");
}
String[] opts = cfgviz.split(",");
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(opts[0], 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.endsWith("checker")) {
checkerName = checkerName.substring(0, checkerName.length() - "checker".length());
}
return checkerName;
}
/* Parse values or key-value pairs into a map from value to true, respectively,
* from the value to the key.
*/
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:
ErrorReporter.errorAbort("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;
}
}