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

framework.src.org.checkerframework.common.wholeprograminference.WholeProgramInferenceScenesHelper 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.42.0
Show newest version
package org.checkerframework.common.wholeprograminference;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.annotation.Target;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.type.MirroredTypesException;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

import org.checkerframework.framework.qual.DefaultFor;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
import org.checkerframework.framework.qual.ImplicitFor;
import org.checkerframework.framework.qual.InvisibleQualifier;
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.AnnotatedNullType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ErrorReporter;
import org.checkerframework.qualframework.qual.QualifierKey;

import annotations.Annotation;
import annotations.el.AClass;
import annotations.el.AField;
import annotations.el.AMethod;
import annotations.el.AScene;
import annotations.el.ATypeElement;
import annotations.el.DefException;
import annotations.el.InnerTypeLocation;
import annotations.io.IndexFileParser;
import annotations.io.IndexFileWriter;

import com.sun.tools.javac.code.TypeAnnotationPosition;

/** This class stores annotations for fields, method return types, and method
 * parameters.
 * 

* The set of annotations inferred for a certain class is stored in an * {@link annotations.el.AScene}, which {@link #writeScenesToJaif} can write * into a .jaif file. * For example, a class field of a class whose fully-qualified name is * {@code my.package.MyClass} will have its inferred type stored in a Scene, * and later written into a file named {@code my.package.MyClass.jaif}. *

* This class populates the initial Scenes by reading existing .jaif files * on the {@link #jaifFilesPath} directory. Having more information in those * initial .jaif files means that the precision achieved by the whole-program * inference analysis will be better. {@link #writeScenesToJaif} rewrites * the initial .jaif files, and may create new ones. */ public class WholeProgramInferenceScenesHelper { /** * Maps a TypeUseLocation to a set of names of annotations that should * not be added to .jaif files for that location. */ private final Map> annosToIgnore = new HashMap<>(); /** * Directory where .jaif files will be written to and read from. * This directory is relative to where the CF's javac command is executed. */ public static final String jaifFilesPath = "build" + File.separator + "whole-program-inference" + File.separator; /** * Indicates whether assignments where the rhs is null should be ignored. */ private final boolean ignoreNullAssignments; /** Maps .jaif file paths (Strings) to Scenes. Relatives to jaifFilesPath. */ private final Map scenes = new HashMap<>(); /** * Set representing Scenes that were modified since the last time all * Scenes were written into .jaif files. Each String element of this set * is a path to the .jaif file of the corresponding Scene in the set. It * is obtained by passing a class name as argument to the * {@link #getJaifPath} method. *

* Modifying a Scene means adding (or changing) a type annotation for a * field, method return type, or method parameter type in the Scene. * (Scenes are modified by the method {@link #updateAnnotationSetInScene}.) */ private final Set modifiedScenes = new HashSet<>(); public WholeProgramInferenceScenesHelper(boolean ignoreNullAssignments) { this.ignoreNullAssignments = ignoreNullAssignments; } /** * Write all modified scenes into .jaif files. * (Scenes are modified by the method {@link #updateAnnotationSetInScene}.) */ public void writeScenesToJaif() { // Create .jaif files directory if it doesn't exist already. File jaifDir = new File(jaifFilesPath); if (!jaifDir.exists()) { jaifDir.mkdirs(); } // Write scenes into .jaif files. for (String jaifPath : modifiedScenes) { try { AScene scene = scenes.get(jaifPath).clone(); removeIgnoredAnnosFromScene(scene); new File(jaifPath).delete(); if (!scene.prune()) { // Only write non-empty scenes into .jaif files. IndexFileWriter.write(scene, new FileWriter(jaifPath)); } } catch (IOException e) { ErrorReporter.errorAbort("Problem while reading file in: " + jaifPath + ". Exception message: " + e.getMessage(), e); } catch (DefException e) { ErrorReporter.errorAbort(e.getMessage(), e); } } modifiedScenes.clear(); } /** * Returns the String representing the .jaif path of a class given its name. */ protected String getJaifPath(String className) { String jaifPath = jaifFilesPath + className + ".jaif"; return jaifPath; } /** * Returns the Scene stored in a .jaif file path passed as input. * If the file does not exist, an empty Scene is created. */ protected AScene getScene(String jaifPath) { AScene scene; if (!scenes.containsKey(jaifPath)) { File jaifFile = new File(jaifPath); scene = new AScene(); if (jaifFile.exists()) { try { IndexFileParser.parseFile(jaifPath, scene); } catch (IOException e) { ErrorReporter.errorAbort("Problem while reading file in: " + jaifPath + "." + " Exception message: " + e.getMessage(), e); } } scenes.put(jaifPath, scene); } else { scene = scenes.get(jaifPath); } return scene; } /** * Returns the AClass in an AScene, given a className and a jaifPath. */ protected AClass getAClass(String className, String jaifPath) { // Possibly reads .jaif file to obtain a Scene. AScene scene = getScene(jaifPath); return scene.classes.vivify(className); } /** * Updates the set of annotations in a location of a Scene. *

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

* @param type ATypeElement of the Scene which will be modified. * @param atf the annotated type factory of a given type system, whose * type hierarchy will be used. * @param jaifPath used to identify a Scene. * @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 defLoc the location where the annotation will be added. */ protected void updateAnnotationSetInScene(ATypeElement type, AnnotatedTypeFactory atf, String jaifPath, AnnotatedTypeMirror rhsATM, AnnotatedTypeMirror lhsATM, TypeUseLocation defLoc) { if (rhsATM instanceof AnnotatedNullType && ignoreNullAssignments) { return; } AnnotatedTypeMirror atmFromJaif = AnnotatedTypeMirror.createType( rhsATM.getUnderlyingType(), atf, false); typeElementToATM(atmFromJaif, type, atf); updatesATMWithLUB(atf, rhsATM, atmFromJaif); 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 on the source code, halt. if (upperAnnos.size() == rhsATM.getAnnotations().size() && atf.getQualifierHierarchy().isSubtype( rhsATM.getAnnotations(), upperAnnos)) { return; } } updateTypeElementFromATM(rhsATM, lhsATM, atf, type, 1, defLoc); modifiedScenes.add(jaifPath); } /** * Removes all annotations that should be ignored from an AScene. * (See {@link #shouldIgnore}). */ private void removeIgnoredAnnosFromScene(AScene scene) { for (AClass aclass : scene.classes.values()) { for (AField field : aclass.fields.values()) { removeIgnoredAnnosFromATypeElement( field.type, TypeUseLocation.FIELD); } for (AMethod method : aclass.methods.values()) { // Return type removeIgnoredAnnosFromATypeElement( method.returnType, TypeUseLocation.RETURN); // Receiver type removeIgnoredAnnosFromATypeElement( method.receiver.type, TypeUseLocation.RECEIVER); // Parameter type for (AField param : method.parameters.values()) { removeIgnoredAnnosFromATypeElement( param.type, TypeUseLocation.PARAMETER); } } } } /** * Removes all annotations that should be ignored from an ATypeElement. * (See {@link #shouldIgnore}). */ private void removeIgnoredAnnosFromATypeElement(ATypeElement typeEl, TypeUseLocation loc) { Set annosToRemove = new HashSet<>(); Set annosToIgnoreForLocation = annosToIgnore.get(loc); if (annosToIgnoreForLocation == null) { // No annotations to ignore for that position. return; } for (Annotation anno : typeEl.tlAnnotationsHere) { if (annosToIgnoreForLocation.contains(anno.def().name)) { annosToRemove.add(anno); } } typeEl.tlAnnotationsHere.removeAll(annosToRemove); // Remove annotations recursively for inner types. for (ATypeElement innerType : typeEl.innerTypes.values()) { removeIgnoredAnnosFromATypeElement(innerType, loc); } } /** * Updates sourceCodeATM to contain the LUB between sourceCodeATM and * jaifATM, ignoring missing AnnotationMirrors from jaifATM -- it considers * the LUB between an AnnotationMirror am and a missing AnnotationMirror to be am. * The results are stored in sourceCodeATM. * @param atf the annotated type factory of a given type system, whose * type hierarchy will be used. * @param sourceCodeATM the annotated type on the source code. * @param jaifATM the annotated type on the .jaif file. */ private void updatesATMWithLUB(AnnotatedTypeFactory atf, AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror jaifATM) { switch (sourceCodeATM.getKind()) { case TYPEVAR: updatesATMWithLUB(atf, ((AnnotatedTypeVariable) sourceCodeATM).getLowerBound(), ((AnnotatedTypeVariable) jaifATM).getLowerBound()); updatesATMWithLUB(atf, ((AnnotatedTypeVariable) sourceCodeATM).getUpperBound(), ((AnnotatedTypeVariable) jaifATM).getUpperBound()); break; // case WILDCARD: // Because inferring type arguments is not supported, wildcards won't be encoutered // updatesATMWithLUB(atf, ((AnnotatedWildcardType) sourceCodeATM).getExtendsBound(), // ((AnnotatedWildcardType) jaifATM).getExtendsBound()); // updatesATMWithLUB(atf, ((AnnotatedWildcardType) sourceCodeATM).getSuperBound(), // ((AnnotatedWildcardType) jaifATM).getSuperBound()); // break; case ARRAY: updatesATMWithLUB(atf, ((AnnotatedArrayType) sourceCodeATM).getComponentType(), ((AnnotatedArrayType) jaifATM).getComponentType()); break; // case DECLARED: // inferring annotations on type arguments is not supported, so no need to recur on // generic types. If this was every implemented, this method would need 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<>(); for (AnnotationMirror amSource : sourceCodeATM.getAnnotations()) { AnnotationMirror amJaif = jaifATM.getAnnotationInHierarchy(amSource); // amJaif only contains annotations from the jaif, so it might be missing // an annotation in the hierarchy if (amJaif != null) { amSource = atf.getQualifierHierarchy().leastUpperBound( amSource, amJaif); } annosToReplace.add(amSource); } sourceCodeATM.replaceAnnotations(annosToReplace); } /** * Returns true if am should not be inserted in source code, for example * {@link org.checkerframework.common.value.qual.BottomVal}. This happens * when am cannot be inserted in source code or is the default for the * location passed as argument. *

* Invisible qualifiers, which are annotations that contain the * {@link org.checkerframework.framework.qual.InvisibleQualifier} * meta-annotation, also return true. *

* TODO: Merge functionality somewhere else with * {@link org.checkerframework.framework.type.GenericAnnotatedTypeFactory#createQualifierDefaults}. * Look into the createQualifierDefaults method before changing anything here. * See Issue 683 * https://github.com/typetools/checker-framework/issues/683 */ private boolean shouldIgnore(AnnotationMirror am, TypeUseLocation location, AnnotatedTypeFactory atf, AnnotatedTypeMirror atm) { Element elt = am.getAnnotationType().asElement(); // Checks if am is an implementation detail (a type qualifier used // internally by the type system and not meant to be seen by the user.) Target target = elt.getAnnotation(Target.class); if (target != null && target.value().length == 0) return true; if (elt.getAnnotation(InvisibleQualifier.class) != null) return true; // Checks if am is default if (elt.getAnnotation(DefaultQualifierInHierarchy.class) != null) { return true; } DefaultQualifier defaultQual = elt.getAnnotation(DefaultQualifier.class); if (defaultQual != null) { for (TypeUseLocation loc : defaultQual.locations()) { if (loc == TypeUseLocation.ALL || loc == location) { return true; } } } DefaultFor defaultQualForLocation = elt.getAnnotation(DefaultFor.class); if (defaultQualForLocation != null) { for (TypeUseLocation loc : defaultQualForLocation.value()) { if (loc == TypeUseLocation.ALL || loc == location) { return true; } } } // Checks if am is an implicit annotation // TODO: Handles cases of implicit annotations added via an // org.checkerframework.framework.type.treeannotator.ImplicitsTreeAnnotator ImplicitFor implicitFor = elt.getAnnotation(ImplicitFor.class); if (implicitFor != null) { TypeKind[] types = implicitFor.types(); for (TypeKind tk : types) { if (tk == atm.getKind()) { return true; } } try { Class[] names = implicitFor.typeNames(); for (Class c : names) { TypeMirror underlyingtype = atm.getUnderlyingType(); while (underlyingtype instanceof javax.lang.model.type.ArrayType) { underlyingtype = ((javax.lang.model.type.ArrayType)underlyingtype). getComponentType(); } if (c.getCanonicalName().equals( atm.getUnderlyingType().toString())) { return true; } } } catch (MirroredTypesException e) {} } // Special cases that should be ignored: // {@link org.checkerframework.framework.qual.QualifierKey} if (AnnotationUtils.areSameByClass(am, QualifierKey.class)) { return true; } return false; } /** * Returns a subset of annosSet, consisting of the annotations supported * by atf. */ private Set getSupportedAnnosInSet(Set annosSet, AnnotatedTypeFactory atf) { Set output = new HashSet<>(); Set> supportedAnnos = atf.getSupportedTypeQualifiers(); for (Annotation anno: annosSet) { for (Class clazz : supportedAnnos) { // TODO: Remove comparison by name, and make this routine more efficient. if (clazz.getName().equals(anno.def.name)) { output.add(anno); } } } return output; } /** * Updates an {@link org.checkerframework.framework.type.AnnotatedTypeMirror} * to contain the {@link annotations.Annotation}s of an * {@link annotations.el.ATypeElement}. * @param atm the AnnotatedTypeMirror to be modified * @param type the {@link annotations.el.ATypeElement}. * @param atf the annotated type factory of a given type system, whose * type hierarchy will be used. */ private void typeElementToATM(AnnotatedTypeMirror atm, ATypeElement type, AnnotatedTypeFactory atf) { Set annos = getSupportedAnnosInSet(type.tlAnnotationsHere, atf); for (Annotation anno: annos) { AnnotationMirror am = AnnotationConverter.annotationToAnnotationMirror( anno, atf.getProcessingEnv()); atm.addAnnotation(am); } if (atm.getKind() == TypeKind.ARRAY) { AnnotatedArrayType aat = (AnnotatedArrayType) atm; for (ATypeElement innerType : type.innerTypes.values()) { typeElementToATM(aat.getComponentType(), innerType, atf); } } if (atm.getKind() == TypeKind.TYPEVAR) { AnnotatedTypeVariable atv = (AnnotatedTypeVariable) atm; for (ATypeElement innerType : type.innerTypes.values()) { typeElementToATM(atv.getUpperBound(), innerType, atf); } } } /** * Updates an {@link annotations.el.ATypeElement} to have the annotations of an * {@link org.checkerframework.framework.type.AnnotatedTypeMirror} passed * as argument. Annotations in the original set that should be ignored * (see {@link #shouldIgnore}) are not added to the resulting set. * This method also checks if the AnnotatedTypeMirror has explicit * annotations in source code, and if that is the case no annotations are * added for that location. *

* This method removes from the ATypeElement all annotations supported by atf * before inserting new ones. It is assumed that every time this method is * called, the AnnotatedTypeMirror has a better type estimate for the * ATypeElement. Therefore, it is not a problem to remove all annotations * before inserting the new annotations. * * @param newATM the AnnotatedTypeMirror whose annotations will be added to * the ATypeElement. * @param curATM used to check if the element which will be updated has * explicit annotations in source code. * @param atf the annotated type factory of a given type system, whose * type hierarchy will be used. * @param typeToUpdate the ATypeElement which will be updated. * @param idx used to write annotations on compound types of an ATypeElement. * @param defLoc the location where the annotation will be added. */ private void updateTypeElementFromATM(AnnotatedTypeMirror newATM, AnnotatedTypeMirror curATM, AnnotatedTypeFactory atf, ATypeElement typeToUpdate, int idx, TypeUseLocation defLoc) { // Clears only the annotations that are supported by atf. // The others stay intact. if (idx == 1) { // This if avoids clearing the annotations multiple times in cases // of type variables and compound types. Set annosToRemove = getSupportedAnnosInSet( typeToUpdate.tlAnnotationsHere, atf); // This method may be called consecutive times for the same ATypeElement. // Each time it is called, the AnnotatedTypeMirror has a better type // estimate for the ATypeElement. Therefore, it is not a problem to remove // all annotations before inserting the new annotations. typeToUpdate.tlAnnotationsHere.removeAll(annosToRemove); } // Only update the ATypeElement if there are no explicit annotations if (curATM.getExplicitAnnotations().size() == 0) { for (AnnotationMirror am : newATM.getAnnotations()) { Annotation anno = AnnotationConverter.annotationMirrorToAnnotation(am); if (anno != null) { if (shouldIgnore(am, defLoc, atf, newATM)) { Set annosIgnored = annosToIgnore.get(defLoc); if (annosIgnored == null) { annosIgnored = new HashSet<>(); annosToIgnore.put(defLoc, annosIgnored); } annosIgnored.add(anno.def().name); } typeToUpdate.tlAnnotationsHere.add(anno); } } } else if (curATM.getKind() == TypeKind.TYPEVAR) { // getExplicitAnnotations will be non-empty for type vars whose bounds are explicitly annotated. // So instead, only insert the annotation if there is not primary annotation of the same hierarchy. // #shouldIgnore prevent annotations that are subtypes of type vars upper bound from being inserted. for (AnnotationMirror am : newATM.getAnnotations()) { if (curATM.getAnnotationInHierarchy(am) != null) { // Don't insert if the type is already has a primary annotation // in the same hierarchy. break; } Annotation anno = AnnotationConverter.annotationMirrorToAnnotation(am); if (anno != null) { if (shouldIgnore(am, defLoc, atf, newATM)) { Set annosIgnored = annosToIgnore.get(defLoc); if (annosIgnored == null) { annosIgnored = new HashSet<>(); annosToIgnore.put(defLoc, annosIgnored); } annosIgnored.add(anno.def().name); } typeToUpdate.tlAnnotationsHere.add(anno); } } } // Recursively update compound type and type variable type if they exist. if (newATM.getKind() == TypeKind.ARRAY && curATM.getKind() == TypeKind.ARRAY) { AnnotatedArrayType newAAT = (AnnotatedArrayType) newATM; AnnotatedArrayType oldAAT = (AnnotatedArrayType) curATM; updateTypeElementFromATM(newAAT.getComponentType(), oldAAT.getComponentType(), atf, typeToUpdate.innerTypes.vivify(new InnerTypeLocation( TypeAnnotationPosition.getTypePathFromBinary( Collections.nCopies(2 * idx, 0)))), idx+1, defLoc); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy