All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.checkerframework.common.wholeprograminference.WholeProgramInferenceImplementation Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.44.0
Show newest version
package org.checkerframework.common.wholeprograminference;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.ElementFilter;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.dataflow.analysis.Analysis;
import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
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.expression.ClassName;
import org.checkerframework.dataflow.expression.FieldAccess;
import org.checkerframework.dataflow.expression.LocalVariable;
import org.checkerframework.dataflow.expression.ThisReference;
import org.checkerframework.framework.flow.CFAbstractStore;
import org.checkerframework.framework.flow.CFAbstractValue;
import org.checkerframework.framework.qual.IgnoreInWholeProgramInference;
import org.checkerframework.framework.qual.TypeUseLocation;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
import scenelib.annotations.util.JVMNames;

/**
 * This is the primary implementation of {@link
 * org.checkerframework.common.wholeprograminference.WholeProgramInference}. It uses an instance of
 * {@link WholeProgramInferenceStorage} to store annotations and to create output files.
 *
 * 

This class does not perform inference for an element if the element has explicit annotations. * That is, calling an {@code update*} method on an explicitly annotated field, method return, or * method parameter has no effect. * *

In addition, whole program inference ignores inferred types in a few scenarios. When * discovering a use, WPI ignores an inferred type if: * *

    *
  1. The inferred type of an element that should be written into a file is a subtype of the * upper bounds of this element's written type in the source code. *
  2. The annotation annotates a {@code null} literal, except when doing inference for the * NullnessChecker. (The rationale for this is that {@code null} is a frequently-used default * value, and it would be undesirable to infer the bottom type if {@code null} were the only * value passed as an argument.) *
* * When outputting a file, WPI ignores an inferred type if: * *
    *
  1. The @Target annotation does not permit the annotation to be written at this location. *
  2. The @RelevantJavaTypes annotation does not permit the annotation to be written at this * location. *
  3. The inferred annotation has the @InvisibleQualifier meta-annotation. *
  4. The inferred annotation would be the same annotation applied via defaulting — that * is, if omitting it has the same effect as writing it. *
* * @param the type used by the storage to store annotations. See {@link * WholeProgramInferenceStorage} */ // TODO: We could add an option to update the type of explicitly annotated elements, but this // currently is not recommended since the insert-annotations-to-source tool, which adds annotations // from .jaif files into source code, adds annotations on top of existing annotations. See // https://github.com/typetools/annotation-tools/issues/105 . // TODO: Ensure that annotations are inserted deterministically into files. This is important for // debugging and comparison; otherwise running the whole-program inference on the same set of files // can yield different results (order of annotations). public class WholeProgramInferenceImplementation implements WholeProgramInference { /** The type factory associated with this. */ protected final AnnotatedTypeFactory atypeFactory; /** * Whether to print debugging information when an inference is attempted, but cannot be completed. * An inference can be attempted without success for example because the current storage system * does not support placing annotation in the location for which an annotation was inferred. */ private final boolean showWpiFailedInferences; /** The storage for the inferred annotations. */ private WholeProgramInferenceStorage storage; /** Whether to ignore assignments where the rhs is null. */ private final boolean ignoreNullAssignments; /** * Constructs a new {@code WholeProgramInferenceImplementation} that has not yet inferred any * annotations. * * @param atypeFactory the associated type factory * @param storage the storage used for inferred annotations and for writing output files * @param showWpiFailedInferences whether the {@code -AshowWpiFailedInferences} argument was * passed to the checker, and therefore whether to print debugging messages when inference * fails */ public WholeProgramInferenceImplementation( AnnotatedTypeFactory atypeFactory, WholeProgramInferenceStorage storage, boolean showWpiFailedInferences) { this.atypeFactory = atypeFactory; this.storage = storage; boolean isNullness = atypeFactory.getClass().getSimpleName().equals("NullnessAnnotatedTypeFactory"); this.ignoreNullAssignments = !isNullness; this.showWpiFailedInferences = showWpiFailedInferences; } /** * Returns the storage for inferred annotations. * * @return the storage for the inferred annotations */ public WholeProgramInferenceStorage getStorage() { return storage; } @Override public void updateFromObjectCreation( ObjectCreationNode objectCreationNode, ExecutableElement constructorElt, CFAbstractStore store) { // Don't infer types for code that isn't presented as source. if (!ElementUtils.isElementFromSourceCode(constructorElt)) { return; } // Don't infer types for code that can't be annotated anyway. if (!storage.hasStorageLocationForMethod(constructorElt)) { if (showWpiFailedInferences) { printFailedInferenceDebugMessage( "WPI could not store information" + "about this constructor: " + JVMNames.getJVMMethodSignature(constructorElt)); } return; } List arguments = objectCreationNode.getArguments(); updateInferredExecutableParameterTypes(constructorElt, arguments); updateContracts(Analysis.BeforeOrAfter.BEFORE, constructorElt, store); } @Override public void updateFromMethodInvocation( MethodInvocationNode methodInvNode, ExecutableElement methodElt, CFAbstractStore store) { // Don't infer types for code that isn't presented as source. if (!ElementUtils.isElementFromSourceCode(methodElt)) { return; } if (!storage.hasStorageLocationForMethod(methodElt)) { return; } // Don't infer formal parameter types from recursive calls. // // When performing WPI on a library, if there are no external calls (only recursive calls), then // each iteration of WPI would make the formal parameter types more restrictive, leading to an // infinite (or very long) loop. // // Consider // void myMethod(int x) { ... myMethod(x-1) ... }` // On one iteration, if x has type IntRange(to=100), the recursive call's argument has type // IntRange(to=99). If that is the only call to `MyMethod`, then the formal parameter type // would be updated. On the next iteration it would be refined again to @IntRange(to=98), and // so forth. A recursive call should never restrict a formal parameter type. if (isRecursiveCall(methodInvNode)) { return; } List arguments = methodInvNode.getArguments(); updateInferredExecutableParameterTypes(methodElt, arguments); updateContracts(Analysis.BeforeOrAfter.BEFORE, methodElt, store); } /** * Returns true if the given call is a recursive call. * * @param methodInvNode a method invocation * @return true if the given call is a recursive call */ private boolean isRecursiveCall(MethodInvocationNode methodInvNode) { MethodTree enclosingMethod = TreePathUtil.enclosingMethod(methodInvNode.getTreePath()); if (enclosingMethod == null) { return false; } ExecutableElement methodInvocEle = TreeUtils.elementFromUse(methodInvNode.getTree()); ExecutableElement methodDeclEle = TreeUtils.elementFromDeclaration(enclosingMethod); return methodDeclEle.equals(methodInvocEle); } /** * Updates inferred parameter types based on a call to a method or constructor. * * @param methodElt the element of the method or constructor being invoked * @param arguments the arguments of the invocation */ private void updateInferredExecutableParameterTypes( ExecutableElement methodElt, List arguments) { String file = storage.getFileForElement(methodElt); for (int i = 0; i < arguments.size(); i++) { Node arg = arguments.get(i); Tree argTree = arg.getTree(); VariableElement ve; boolean varargsParam = i >= methodElt.getParameters().size() - 1 && methodElt.isVarArgs(); if (varargsParam && this.atypeFactory.wpiOutputFormat == OutputFormat.JAIF) { // The AFU's annotator.Main produces a non-compilable source file when JAIF-based WPI // tries to output an annotated varargs parameter, such as when running the test // checker/tests/ainfer-testchecker/non-annotated/AnonymousAndInnerClass.java. // Until that bug is fixed, do not attempt to infer information about varargs parameters // in JAIF mode. if (showWpiFailedInferences) { printFailedInferenceDebugMessage( "Annotations cannot be placed on varargs parametersin -Ainfer=jaifs mode, because the" + " JAIF format does not correctly support it.\n" + "The signature of the method whose varargs parameter was not annotated is: " + JVMNames.getJVMMethodSignature(methodElt)); } return; } if (varargsParam) { ve = methodElt.getParameters().get(methodElt.getParameters().size() - 1); } else { ve = methodElt.getParameters().get(i); } AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(ve); AnnotatedTypeMirror argATM = atypeFactory.getAnnotatedType(argTree); if (varargsParam) { // Check whether argATM needs to be turned into an array type, so that the type structure // matches paramATM. boolean expandArgATM = false; if (argATM.getKind() == TypeKind.ARRAY) { int argATMDepth = AnnotatedTypes.getArrayDepth((AnnotatedArrayType) argATM); // This unchecked cast is safe because the declared type of a varargs parameter // is guaranteed to be an array of some kind. int paramATMDepth = AnnotatedTypes.getArrayDepth((AnnotatedArrayType) paramATM); if (paramATMDepth != argATMDepth) { assert argATMDepth + 1 == paramATMDepth; expandArgATM = true; } } else { expandArgATM = true; } if (expandArgATM) { AnnotatedTypeMirror argArray = AnnotatedTypeMirror.createType( TypesUtils.createArrayType(argATM.getUnderlyingType(), atypeFactory.types), atypeFactory, false); ((AnnotatedArrayType) argArray).setComponentType(argATM); argATM = argArray; } } atypeFactory.wpiAdjustForUpdateNonField(argATM); // If storage.getParameterAnnotations receives an index that's larger than the size // of the parameter list, scenes-backed inference can create duplicate entries // for the varargs parameter (it indexes inferred annotations by the parameter number). int paramIndex = varargsParam ? methodElt.getParameters().size() - 1 : i; T paramAnnotations = storage.getParameterAnnotations(methodElt, paramIndex, paramATM, ve, atypeFactory); updateAnnotationSet(paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file); } } @Override public void updateContracts( Analysis.BeforeOrAfter preOrPost, ExecutableElement methodElt, CFAbstractStore store) { // Don't infer types for code that isn't presented as source. if (!ElementUtils.isElementFromSourceCode(methodElt)) { return; } if (store == null) { throw new BugInCF( "updateContracts(%s, %s, null) for %s", preOrPost, methodElt, atypeFactory.getClass().getSimpleName()); } if (!storage.hasStorageLocationForMethod(methodElt)) { return; } // TODO: Probably move some part of this into the AnnotatedTypeFactory. // This code handles fields of "this" and method parameters (including the receiver parameter // "this"), for now. In the future, extend it to other expressions. TypeElement containingClass = (TypeElement) methodElt.getEnclosingElement(); ThisReference thisReference = new ThisReference(containingClass.asType()); ClassName classNameReceiver = new ClassName(containingClass.asType()); // Fields of "this": for (VariableElement fieldElement : ElementFilter.fieldsIn(containingClass.getEnclosedElements())) { if (atypeFactory.wpiOutputFormat == OutputFormat.JAIF && containingClass.getNestingKind().isNested()) { // Don't infer facts about fields of inner classes, because IndexFileWriter // places the annotations incorrectly on the class declarations. continue; } FieldAccess fa = new FieldAccess( (ElementUtils.isStatic(fieldElement) ? classNameReceiver : thisReference), fieldElement.asType(), fieldElement); CFAbstractValue v = store.getFieldValue(fa); AnnotatedTypeMirror fieldDeclType = atypeFactory.getAnnotatedType(fieldElement); AnnotatedTypeMirror inferredType; if (v != null) { // This field is in the store. inferredType = convertCFAbstractValueToAnnotatedTypeMirror(v, fieldDeclType); atypeFactory.wpiAdjustForUpdateNonField(inferredType); } else { // This field is not in the store. Use the declared type. inferredType = fieldDeclType; } T preOrPostConditionAnnos = storage.getPreOrPostconditions( preOrPost, methodElt, fa.toString(), fieldDeclType, atypeFactory); if (preOrPostConditionAnnos == null) { continue; } String file = storage.getFileForElement(methodElt); updateAnnotationSet( preOrPostConditionAnnos, TypeUseLocation.FIELD, inferredType, fieldDeclType, file, false); } // Method parameters (other than the receiver parameter "this"): // This loop is 1-indexed to match the syntax used in annotation arguments. for (int index = 1; index <= methodElt.getParameters().size(); index++) { VariableElement paramElt = methodElt.getParameters().get(index - 1); // Do not infer information about non-effectively-final method parameters, to avoid // spurious flowexpr.parameter.not.final warnings. if (!ElementUtils.isEffectivelyFinal(paramElt)) { continue; } LocalVariable param = new LocalVariable(paramElt); CFAbstractValue v = store.getValue(param); AnnotatedTypeMirror declType = atypeFactory.getAnnotatedType(paramElt); AnnotatedTypeMirror inferredType; if (v != null) { // This parameter is in the store. inferredType = convertCFAbstractValueToAnnotatedTypeMirror(v, declType); atypeFactory.wpiAdjustForUpdateNonField(inferredType); } else { // The parameter is not in the store, so don't attempt to create a postcondition for it, // since anything other than its default type would not be verifiable. (Only postconditions // are supported for parameters.) continue; } T preOrPostConditionAnnos = storage.getPreOrPostconditions(preOrPost, methodElt, "#" + index, declType, atypeFactory); if (preOrPostConditionAnnos != null) { String file = storage.getFileForElement(methodElt); updateAnnotationSet( preOrPostConditionAnnos, TypeUseLocation.PARAMETER, inferredType, declType, file, false); } } // Receiver parameter ("this"): if (!ElementUtils.isStatic(methodElt)) { // Static methods do not have a receiver. CFAbstractValue v = store.getValue(thisReference); if (v != null) { // This parameter is in the store. AnnotatedTypeMirror declaredType = atypeFactory.getAnnotatedType(methodElt).getReceiverType(); if (declaredType == null) { // declaredType is null when the method being analyzed is a constructor (which doesn't // have a receiver). return; } AnnotatedTypeMirror inferredType = AnnotatedTypeMirror.createType(declaredType.getUnderlyingType(), atypeFactory, false); inferredType.replaceAnnotations(v.getAnnotations()); atypeFactory.wpiAdjustForUpdateNonField(inferredType); T preOrPostConditionAnnos = storage.getPreOrPostconditions( preOrPost, methodElt, "this", declaredType, atypeFactory); if (preOrPostConditionAnnos != null) { String file = storage.getFileForElement(methodElt); updateAnnotationSet( preOrPostConditionAnnos, TypeUseLocation.PARAMETER, inferredType, declaredType, file, false); } } } } /** * Converts a CFAbstractValue to an AnnotatedTypeMirror. * * @param v a value to convert to an AnnotatedTypeMirror * @param fieldType an {@code AnnotatedTypeMirror} with the same underlying type as {@code v} that * is copied, then the copy is updated to use {@code v}'s annotations * @return a copy of {@code fieldType} with {@code v}'s annotations */ private AnnotatedTypeMirror convertCFAbstractValueToAnnotatedTypeMirror( CFAbstractValue v, AnnotatedTypeMirror fieldType) { AnnotatedTypeMirror result = fieldType.deepCopy(); result.replaceAnnotations(v.getAnnotations()); return result; } @Override public void updateFromOverride( MethodTree methodTree, ExecutableElement methodElt, AnnotatedExecutableType overriddenMethod) { // Don't infer types for code that isn't presented as source. if (!ElementUtils.isElementFromSourceCode(methodElt)) { return; } String file = storage.getFileForElement(methodElt); for (int i = 0; i < overriddenMethod.getParameterTypes().size(); i++) { VariableElement ve = methodElt.getParameters().get(i); AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(ve); AnnotatedTypeMirror argATM = overriddenMethod.getParameterTypes().get(i); atypeFactory.wpiAdjustForUpdateNonField(argATM); T paramAnnotations = storage.getParameterAnnotations(methodElt, i, paramATM, ve, atypeFactory); updateAnnotationSet(paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file); } AnnotatedDeclaredType argADT = overriddenMethod.getReceiverType(); if (argADT != null) { AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(methodTree).getReceiverType(); if (paramATM != null) { T receiver = storage.getReceiverAnnotations(methodElt, paramATM, atypeFactory); updateAnnotationSet(receiver, TypeUseLocation.RECEIVER, argADT, paramATM, file); } } } @Override public void updateFromFormalParameterAssignment( LocalVariableNode lhs, Node rhs, VariableElement paramElt) { // Don't infer types for code that isn't presented as source. if (!ElementUtils.isElementFromSourceCode(lhs.getElement())) { return; } Tree rhsTree = rhs.getTree(); if (rhsTree == null) { // TODO: Handle variable-length list as parameter. // An ArrayCreationNode with a null tree is created when the // parameter is a variable-length list. We are ignoring it for now. // See Issue 682: https://github.com/typetools/checker-framework/issues/682 if (showWpiFailedInferences) { printFailedInferenceDebugMessage( "Could not update from formal parameter " + "assignment, because an ArrayCreationNode with a null tree is created when " + "the parameter is a variable-length list.\nParameter: " + paramElt); } return; } ExecutableElement methodElt = (ExecutableElement) paramElt.getEnclosingElement(); int i = methodElt.getParameters().indexOf(paramElt); if (i == -1) { // When paramElt is the parameter of a lambda contained in another // method body, the enclosing element is the outer method body // rather than the lambda itself (which has no element). WPI // does not support inferring types for lambda parameters, so // ignore it. if (showWpiFailedInferences) { printFailedInferenceDebugMessage( "Could not update from formal " + "parameter assignment inside a lambda expression, because lambda parameters " + "cannot be annotated.\nParameter: " + paramElt); } return; } AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(paramElt); AnnotatedTypeMirror argATM = atypeFactory.getAnnotatedType(rhsTree); atypeFactory.wpiAdjustForUpdateNonField(argATM); T paramAnnotations = storage.getParameterAnnotations(methodElt, i, paramATM, paramElt, atypeFactory); String file = storage.getFileForElement(methodElt); updateAnnotationSet(paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file); } @Override public void updateFromFieldAssignment(Node lhs, Node rhs) { Element element; String fieldName; if (lhs instanceof FieldAccessNode) { element = ((FieldAccessNode) lhs).getElement(); fieldName = ((FieldAccessNode) lhs).getFieldName(); } else if (lhs instanceof LocalVariableNode) { element = ((LocalVariableNode) lhs).getElement(); fieldName = ((LocalVariableNode) lhs).getName(); } else { throw new BugInCF( "updateFromFieldAssignment received an unexpected node type: " + lhs.getClass()); } // TODO: For a primitive such as long, this is yielding just @GuardedBy rather than // @GuardedBy({}). AnnotatedTypeMirror rhsATM = atypeFactory.getAnnotatedType(rhs.getTree()); atypeFactory.wpiAdjustForUpdateField(lhs.getTree(), element, fieldName, rhsATM); updateFieldFromType(lhs.getTree(), element, fieldName, rhsATM); } @Override public void updateFieldFromType( Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) { if (ignoreFieldInWPI(element, fieldName)) { return; } // Don't infer types for code that isn't presented as source. if (!ElementUtils.isElementFromSourceCode(element)) { return; } String file = storage.getFileForElement(element); AnnotatedTypeMirror lhsATM = atypeFactory.getAnnotatedType(lhsTree); T fieldAnnotations = storage.getFieldAnnotations(element, fieldName, lhsATM, atypeFactory); updateAnnotationSet(fieldAnnotations, TypeUseLocation.FIELD, rhsATM, lhsATM, file); } /** * Returns true if an assignment to the given field should be ignored by WPI. * * @param element the field's element * @param fieldName the field's name * @return true if an assignment to the given field should be ignored by WPI */ protected boolean ignoreFieldInWPI(Element element, String fieldName) { // Do not attempt to infer types for fields that do not have valid names. For example, // compiler-generated temporary variables will have invalid names. Recording facts about fields // with invalid names causes jaif-based WPI to crash when reading the .jaif file, and stub-based // WPI to generate unparsable stub files. See // https://github.com/typetools/checker-framework/issues/3442 if (!SourceVersion.isIdentifier(fieldName)) { return true; } // Don't infer types if the inferred field has a declaration annotation with the // @IgnoreInWholeProgramInference meta-annotation. if (atypeFactory.getDeclAnnotation(element, IgnoreInWholeProgramInference.class) != null || atypeFactory .getDeclAnnotationWithMetaAnnotation(element, IgnoreInWholeProgramInference.class) .size() > 0) { return true; } // Don't infer types for code that isn't presented as source. if (!ElementUtils.isElementFromSourceCode(element)) { return true; } return false; } @Override public void updateFromReturn( ReturnNode retNode, ClassSymbol classSymbol, MethodTree methodDeclTree, Map overriddenMethods) { // Don't infer types for code that isn't presented as source. if (methodDeclTree == null || !ElementUtils.isElementFromSourceCode( TreeUtils.elementFromDeclaration(methodDeclTree))) { return; } // Whole-program inference ignores some locations. See Issue 682: // https://github.com/typetools/checker-framework/issues/682 if (classSymbol == null) { // TODO: Handle anonymous classes. return; } ExecutableElement methodElt = TreeUtils.elementFromDeclaration(methodDeclTree); String file = storage.getFileForElement(methodElt); AnnotatedTypeMirror lhsATM = atypeFactory.getAnnotatedType(methodDeclTree).getReturnType(); // Type of the expression returned AnnotatedTypeMirror rhsATM = atypeFactory.getAnnotatedType(retNode.getTree().getExpression()); atypeFactory.wpiAdjustForUpdateNonField(rhsATM); DependentTypesHelper dependentTypesHelper = ((GenericAnnotatedTypeFactory) atypeFactory).getDependentTypesHelper(); dependentTypesHelper.delocalize(rhsATM, methodDeclTree); T returnTypeAnnos = storage.getReturnAnnotations(methodElt, lhsATM, atypeFactory); updateAnnotationSet(returnTypeAnnos, TypeUseLocation.RETURN, rhsATM, lhsATM, file); // Now, update return types of overridden methods based on the implementation we just saw. // This inference is similar to the inference procedure for method parameters: both are // updated based only on the implementations (in this case) or call-sites (for method // parameters) that are available to WPI. // // An alternative implementation would be to: // * update only the method (not overridden methods) // * when finished, propagate the final result to overridden methods // for (Map.Entry pair : overriddenMethods.entrySet()) { AnnotatedDeclaredType superclassDecl = pair.getKey(); ExecutableElement overriddenMethodElement = pair.getValue(); // Don't infer types for code that isn't presented as source. if (!ElementUtils.isElementFromSourceCode(overriddenMethodElement)) { continue; } AnnotatedExecutableType overriddenMethod = AnnotatedTypes.asMemberOf( atypeFactory.getProcessingEnv().getTypeUtils(), atypeFactory, superclassDecl, overriddenMethodElement); String superClassFile = storage.getFileForElement(overriddenMethodElement); AnnotatedTypeMirror overriddenMethodReturnType = overriddenMethod.getReturnType(); T storedOverriddenMethodReturnTypeAnnotations = storage.getReturnAnnotations( overriddenMethodElement, overriddenMethodReturnType, atypeFactory); updateAnnotationSet( storedOverriddenMethodReturnTypeAnnotations, TypeUseLocation.RETURN, rhsATM, overriddenMethodReturnType, superClassFile); } } @Override public void addMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno) { // Do not infer types for library code, only for type-checked source code. if (!ElementUtils.isElementFromSourceCode(methodElt)) { return; } String file = storage.getFileForElement(methodElt); boolean isNewAnnotation = storage.addMethodDeclarationAnnotation(methodElt, anno); if (isNewAnnotation) { storage.setFileModified(file); } } @Override public void addFieldDeclarationAnnotation(Element field, AnnotationMirror anno) { if (!ElementUtils.isElementFromSourceCode(field)) { return; } String file = storage.getFileForElement(field); boolean isNewAnnotation = storage.addFieldDeclarationAnnotation(field, anno); if (isNewAnnotation) { storage.setFileModified(file); } } /** * Updates the set of annotations in a location in a program. * *
    *
  • If there was no previous annotation for that location, then the updated set will be the * annotations in rhsATM. *
  • If there was a previous annotation, the updated set will be the LUB between the previous * annotation and rhsATM. *
* *

Subclasses can customize this behavior. * * @param annotationsToUpdate the type whose annotations are modified by this method * @param defLoc the location where the annotation will be added * @param rhsATM the RHS of the annotated type on the source code * @param lhsATM the LHS of the annotated type on the source code * @param file the annotation file containing the executable; used for marking the scene as * modified (needing to be written to disk) */ protected void updateAnnotationSet( T annotationsToUpdate, TypeUseLocation defLoc, AnnotatedTypeMirror rhsATM, AnnotatedTypeMirror lhsATM, String file) { updateAnnotationSet(annotationsToUpdate, defLoc, rhsATM, lhsATM, file, true); } /** * Updates the set of annotations in a location in a program. * *

    *
  • If there was no previous annotation for that location, then the updated set will be the * annotations in rhsATM. *
  • If there was a previous annotation, the updated set will be the LUB between the previous * annotation and rhsATM. *
* *

Subclasses can customize this behavior. * * @param annotationsToUpdate the type whose annotations are modified by this method * @param defLoc the location where the annotation will be added * @param rhsATM the RHS of the annotated type on the source code * @param lhsATM the LHS of the annotated type on the source code * @param file annotation file containing the executable; used for marking the scene as modified * (needing to be written to disk) * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the * source code */ protected void updateAnnotationSet( T annotationsToUpdate, TypeUseLocation defLoc, AnnotatedTypeMirror rhsATM, AnnotatedTypeMirror lhsATM, String file, boolean ignoreIfAnnotated) { if (rhsATM instanceof AnnotatedNullType && ignoreNullAssignments) { return; } // If the rhsATM and the lhsATM have different kinds (which can happen e.g. when // an array type is substituted for a type parameter), do not attempt to update // the inferred type, because this method is written with the assumption // that rhsATM and lhsATM are the same kind. if (rhsATM.getKind() != lhsATM.getKind()) { // The one difference in kinds situation that this method can account for is the RHS being // a literal null expression. if (!(rhsATM instanceof AnnotatedNullType)) { if (showWpiFailedInferences) { printFailedInferenceDebugMessage( "type structure mismatch, so cannot transfer inferred type" + "to declared type.\nDeclared type kind: " + rhsATM.getKind() + "\nInferred type kind: " + lhsATM.getKind()); } return; } } AnnotatedTypeMirror atmFromStorage = storage.atmFromStorageLocation(rhsATM.getUnderlyingType(), annotationsToUpdate); updateAtmWithLub(rhsATM, atmFromStorage); if (lhsATM instanceof AnnotatedTypeVariable) { Set upperAnnos = ((AnnotatedTypeVariable) lhsATM).getUpperBound().getEffectiveAnnotations(); // If the inferred type is a subtype of the upper bounds of the // current type in the source code, do nothing. if (upperAnnos.size() == rhsATM.getAnnotations().size() && atypeFactory.getQualifierHierarchy().isSubtype(rhsATM.getAnnotations(), upperAnnos)) { return; } } storage.updateStorageLocationFromAtm( rhsATM, lhsATM, annotationsToUpdate, defLoc, ignoreIfAnnotated); storage.setFileModified(file); } /** * Prints a debugging message about a failed inference. Must only be called after {@link * #showWpiFailedInferences} has been checked, to avoid constructing the debugging message * eagerly. * * @param reason a message describing the reason an inference was unsuccessful, which will be * displayed to the user */ private void printFailedInferenceDebugMessage(String reason) { assert showWpiFailedInferences; // TODO: it would be nice if this message also included a line number // for the file being analyzed, but I don't know how to get that information // here, given that this message is called from places where only the annotated // type mirrors for the LHS and RHS of some pseduo-assignment are available. System.out.println("WPI failed to make an inference: " + reason); } /** * Updates sourceCodeATM to contain the LUB between sourceCodeATM and ajavaATM, ignoring missing * AnnotationMirrors from ajavaATM -- it considers the LUB between an AnnotationMirror am and a * missing AnnotationMirror to be am. The results are stored in sourceCodeATM. * * @param sourceCodeATM the annotated type on the source code; side effected by this method * @param ajavaATM the annotated type on the ajava file */ private void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror ajavaATM) { switch (sourceCodeATM.getKind()) { case TYPEVAR: updateAtmWithLub( ((AnnotatedTypeVariable) sourceCodeATM).getLowerBound(), ((AnnotatedTypeVariable) ajavaATM).getLowerBound()); updateAtmWithLub( ((AnnotatedTypeVariable) sourceCodeATM).getUpperBound(), ((AnnotatedTypeVariable) ajavaATM).getUpperBound()); break; case WILDCARD: break; // throw new BugInCF("This can't happen"); // TODO: This comment is wrong: the wildcard case does get entered. // Because inferring type arguments is not supported, wildcards won't be encountered. // updateATMWithLUB( // atf, // ((AnnotatedWildcardType) sourceCodeATM).getExtendsBound(), // ((AnnotatedWildcardType) ajavaATM).getExtendsBound()); // updateATMWithLUB( // atf, // ((AnnotatedWildcardType) sourceCodeATM).getSuperBound(), // ((AnnotatedWildcardType) ajavaATM).getSuperBound()); // break; case ARRAY: updateAtmWithLub( ((AnnotatedArrayType) sourceCodeATM).getComponentType(), ((AnnotatedArrayType) ajavaATM).getComponentType()); break; // case DECLARED: // Inferring annotations on type arguments is not supported, so no need to recur on generic // types. If this was ever implemented, this method would need a VisitHistory object to // prevent infinite recursion on types such as T extends List. default: // ATM only has primary annotations break; } // LUB primary annotations Set annosToReplace = new HashSet<>(sourceCodeATM.getAnnotations().size()); for (AnnotationMirror amSource : sourceCodeATM.getAnnotations()) { AnnotationMirror amAjava = ajavaATM.getAnnotationInHierarchy(amSource); // amAjava only contains annotations from the ajava file, so it might be missing // an annotation in the hierarchy. if (amAjava != null) { amSource = atypeFactory.getQualifierHierarchy().leastUpperBound(amSource, amAjava); } annosToReplace.add(amSource); } sourceCodeATM.replaceAnnotations(annosToReplace); } @Override public void writeResultsToFile(OutputFormat outputFormat, BaseTypeChecker checker) { storage.writeResultsToFile(outputFormat, checker); } @Override public void preprocessClassTree(ClassTree classTree) { storage.preprocessClassTree(classTree); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy