package org.checkerframework.checker.initialization; /*>>> import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; 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.MethodTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.TypeCastTree; import com.sun.source.tree.VariableTree; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.VariableElement; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.dataflow.analysis.FlowExpressions.ClassName; import org.checkerframework.dataflow.analysis.FlowExpressions.FieldAccess; import org.checkerframework.dataflow.analysis.FlowExpressions.LocalVariable; import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver; import org.checkerframework.dataflow.analysis.FlowExpressions.ThisReference; import org.checkerframework.framework.flow.CFAbstractStore; import org.checkerframework.framework.flow.CFAbstractValue; import org.checkerframework.framework.source.Result; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.framework.util.AnnotationFormatter; import org.checkerframework.framework.util.DefaultAnnotationFormatter; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.InternalUtils; import org.checkerframework.javacutil.Pair; import org.checkerframework.javacutil.TreeUtils; /** * The visitor for the freedom-before-commitment type-system. The freedom-before-commitment * type-system and this class are abstract and need to be combined with another type-system whose * safe initialization should be tracked. For an example, see the {@link NullnessChecker}. Also * supports rawness as a type-system for tracking initialization, though FBC is preferred. * * @author Stefan Heule */ public class InitializationVisitor< Factory extends InitializationAnnotatedTypeFactory<Value, Store, ?, ?>, Value extends CFAbstractValue<Value>, Store extends InitializationStore<Value, Store>> extends BaseTypeVisitor<Factory> { protected final AnnotationFormatter annoFormatter; // Error message keys private static final /*@CompilerMessageKey*/ String COMMITMENT_INVALID_CAST = "initialization.invalid.cast"; private static final /*@CompilerMessageKey*/ String COMMITMENT_FIELDS_UNINITIALIZED = "initialization.fields.uninitialized"; private static final /*@CompilerMessageKey*/ String COMMITMENT_INVALID_FIELD_TYPE = "initialization.invalid.field.type"; private static final /*@CompilerMessageKey*/ String COMMITMENT_INVALID_CONSTRUCTOR_RETURN_TYPE = "initialization.invalid.constructor.return.type"; private static final /*@CompilerMessageKey*/ String COMMITMENT_INVALID_FIELD_WRITE_UNCLASSIFIED = "initialization.invalid.field.write.unknown"; private static final /*@CompilerMessageKey*/ String COMMITMENT_INVALID_FIELD_WRITE_COMMITTED = "initialization.invalid.field.write.initialized"; public InitializationVisitor(BaseTypeChecker checker) { super(checker); annoFormatter = new DefaultAnnotationFormatter(); initializedFields = new ArrayList<>(); checkForAnnotatedJdk(); } @Override public void setRoot(CompilationUnitTree root) { // Clean up the cache of initialized fields once per compilation unit. // Alternatively, but harder to determine, this could be done once per // top-level class. initializedFields.clear(); super.setRoot(root); } @Override protected boolean checkConstructorInvocation( AnnotatedDeclaredType dt, AnnotatedExecutableType constructor, NewClassTree src) { // receiver annotations for constructors are forbidden, therefore no // check is necessary // TODO: nested constructors can have receivers! return true; } @Override protected void commonAssignmentCheck( Tree varTree, ExpressionTree valueExp, /*@CompilerMessageKey*/ String errorKey) { // field write of the form x.f = y if (TreeUtils.isFieldAccess(varTree)) { // cast is safe: a field access can only be an IdentifierTree or // MemberSelectTree ExpressionTree lhs = (ExpressionTree) varTree; ExpressionTree y = valueExp; Element el = TreeUtils.elementFromUse(lhs); AnnotatedTypeMirror xType = atypeFactory.getReceiverType(lhs); AnnotatedTypeMirror yType = atypeFactory.getAnnotatedType(y); // the special FBC rules do not apply if there is an explicit // UnknownInitialization annotation Set<AnnotationMirror> fieldAnnotations = atypeFactory.getAnnotatedType(TreeUtils.elementFromUse(lhs)).getAnnotations(); if (!AnnotationUtils.containsSameIgnoringValues( fieldAnnotations, atypeFactory.UNCLASSIFIED)) { if (!ElementUtils.isStatic(el) && !(atypeFactory.isCommitted(yType) || atypeFactory.isFree(xType) || atypeFactory.isFbcBottom(yType))) { /*@CompilerMessageKey*/ String err; if (atypeFactory.isCommitted(xType)) { err = COMMITMENT_INVALID_FIELD_WRITE_COMMITTED; } else { err = COMMITMENT_INVALID_FIELD_WRITE_UNCLASSIFIED; } checker.report(Result.failure(err, varTree), varTree); return; // prevent issuing another errow about subtyping } } } super.commonAssignmentCheck(varTree, valueExp, errorKey); } @Override public Void visitVariable(VariableTree node, Void p) { // is this a field (and not a local variable)? if (TreeUtils.elementFromDeclaration(node).getKind().isField()) { Set<AnnotationMirror> annotationMirrors = atypeFactory.getAnnotatedType(node).getExplicitAnnotations(); // Fields cannot have commitment annotations. for (Class<? extends Annotation> c : atypeFactory.getInitializationAnnotations()) { for (AnnotationMirror a : annotationMirrors) { if (atypeFactory.isUnclassified(a)) { continue; // unclassified is allowed } if (AnnotationUtils.areSameByClass(a, c)) { checker.report(Result.failure(COMMITMENT_INVALID_FIELD_TYPE, node), node); break; } } } } return super.visitVariable(node, p); } @Override protected boolean checkContract( Receiver expr, AnnotationMirror necessaryAnnotation, AnnotationMirror inferredAnnotation, CFAbstractStore<?, ?> store) { // also use the information about initialized fields to check contracts final AnnotationMirror invariantAnno = atypeFactory.getFieldInvariantAnnotation(); if (atypeFactory.getQualifierHierarchy().isSubtype(invariantAnno, necessaryAnnotation)) { if (expr instanceof FieldAccess) { FieldAccess fa = (FieldAccess) expr; if (fa.getReceiver() instanceof ThisReference || fa.getReceiver() instanceof ClassName) { @SuppressWarnings("unchecked") Store s = (Store) store; if (s.isFieldInitialized(fa.getField())) { AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(fa.getField()); // is this an invariant-field? if (AnnotationUtils.containsSame( fieldType.getAnnotations(), invariantAnno)) { return true; } } } else { Set<AnnotationMirror> recvAnnoSet; @SuppressWarnings("unchecked") Value value = (Value) store.getValue(fa.getReceiver()); if (value != null) { recvAnnoSet = value.getAnnotations(); } else if (fa.getReceiver() instanceof LocalVariable) { Element elem = ((LocalVariable) fa.getReceiver()).getElement(); AnnotatedTypeMirror recvType = atypeFactory.getAnnotatedType(elem); recvAnnoSet = recvType.getAnnotations(); } else { // Is there anything better we could do? return false; } boolean isRecvCommitted = false; for (AnnotationMirror anno : recvAnnoSet) { if (atypeFactory.isCommitted(anno)) { isRecvCommitted = true; } } AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(fa.getField()); // The receiver is fully initialized and the field type // has the invariant type. if (isRecvCommitted && AnnotationUtils.containsSame( fieldType.getAnnotations(), invariantAnno)) { return true; } } } } return super.checkContract(expr, necessaryAnnotation, inferredAnnotation, store); } @Override public Void visitTypeCast(TypeCastTree node, Void p) { AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(node.getExpression()); AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(node); AnnotationMirror exprAnno = null, castAnno = null; // find commitment annotation for (Class<? extends Annotation> a : atypeFactory.getInitializationAnnotations()) { if (castType.hasAnnotation(a)) { assert castAnno == null; castAnno = castType.getAnnotation(a); } if (exprType.hasAnnotation(a)) { assert exprAnno == null; exprAnno = exprType.getAnnotation(a); } } // TODO: this is most certainly unsafe!! (and may be hiding some // problems) // If we don't find a commitment annotation, then we just assume that // the subtyping is alright. // The case that has come up is with wildcards not getting a type for // some reason, even though the default is @Initialized. boolean isSubtype; if (exprAnno == null || castAnno == null) { isSubtype = true; } else { assert exprAnno != null && castAnno != null; isSubtype = atypeFactory.getQualifierHierarchy().isSubtype(exprAnno, castAnno); } if (!isSubtype) { checker.report( Result.failure( COMMITMENT_INVALID_CAST, annoFormatter.formatAnnotationMirror(exprAnno), annoFormatter.formatAnnotationMirror(castAnno)), node); return p; // suppress cast.unsafe warning } return super.visitTypeCast(node, p); } @Override public Void visitBlock(BlockTree node, Void p) { // Are we dealing with the last static initializer block? If so, // then check that all static fields have been initialized. if (node.isStatic()) { ClassTree enclosingClass = TreeUtils.enclosingClass(getCurrentPath()); boolean isStaticInitBlock = false; boolean isLastStaticInitBlock = true; for (Tree m : enclosingClass.getMembers()) { if (m == node) { isStaticInitBlock = true; continue; } if (isStaticInitBlock && m.getKind() == Kind.BLOCK && ((BlockTree) m).isStatic()) { isLastStaticInitBlock = false; } } if (isLastStaticInitBlock && isStaticInitBlock) { boolean isStatic = true; Store store = atypeFactory.getRegularExitStore(node); // Add field values for fields with an initializer. for (Pair<VariableElement, Value> t : store.getAnalysis().getFieldValues()) { store.addInitializedField(t.first); } // Check that all static fields are initialized. List<AnnotationMirror> receiverAnnotations = Collections.emptyList(); checkFieldsInitialized(node, isStatic, store, receiverAnnotations); } } return super.visitBlock(node, p); } protected final List<VariableTree> initializedFields; @Override public void processClassTree(ClassTree node) { // go through all members and look for initializers. // save all fields that are initialized and do not report errors about // them later when checking constructors. for (Tree member : node.getMembers()) { if (member instanceof BlockTree && !((BlockTree) member).isStatic()) { BlockTree block = (BlockTree) member; Store store = atypeFactory.getRegularExitStore(block); if (store != null) { // Add field values for fields with an initializer. for (Pair<VariableElement, Value> t : store.getAnalysis().getFieldValues()) { store.addInitializedField(t.first); } final List<VariableTree> init = atypeFactory.getInitializedInvariantFields(store, getCurrentPath()); initializedFields.addAll(init); } } } super.processClassTree(node); // Is there a static initializer block? boolean hasStaticInitializer = false; for (Tree t : node.getMembers()) { switch (t.getKind()) { case BLOCK: if (((BlockTree) t).isStatic()) { hasStaticInitializer = true; } break; default: break; } } // Warn about uninitialized static fields if there is no static // initializer (otherwise, errors are reported there). if (!hasStaticInitializer && node.getKind() == Kind.CLASS) { boolean isStatic = true; // See GenericAnnotatedTypeFactory.performFlowAnalysis for why we use // the regular exit store of the class here. Store store = atypeFactory.getRegularExitStore(node); // Add field values for fields with an initializer. for (Pair<VariableElement, Value> t : store.getAnalysis().getFieldValues()) { store.addInitializedField(t.first); } List<AnnotationMirror> receiverAnnotations = Collections.emptyList(); checkFieldsInitialized(node, isStatic, store, receiverAnnotations); } } @Override public Void visitMethod(MethodTree node, Void p) { if (TreeUtils.isConstructor(node)) { Collection<? extends AnnotationMirror> returnTypeAnnotations = AnnotationUtils.getExplicitAnnotationsOnConstructorResult(node); // check for invalid constructor return type for (Class<? extends Annotation> c : atypeFactory.getInvalidConstructorReturnTypeAnnotations()) { for (AnnotationMirror a : returnTypeAnnotations) { if (AnnotationUtils.areSameByClass(a, c)) { checker.report( Result.failure(COMMITMENT_INVALID_CONSTRUCTOR_RETURN_TYPE, node), node); break; } } } // Check that all fields have been initialized at the end of the // constructor. boolean isStatic = false; Store store = atypeFactory.getRegularExitStore(node); List<? extends AnnotationMirror> receiverAnnotations = getAllReceiverAnnotations(node); checkFieldsInitialized(node, isStatic, store, receiverAnnotations); } return super.visitMethod(node, p); } /** Returns the full list of annotations on the receiver. */ private List<? extends AnnotationMirror> getAllReceiverAnnotations(MethodTree node) { // TODO: get access to a Types instance and use it to get receiver type // Or, extend ExecutableElement with such a method. // Note that we cannot use the receiver type from // AnnotatedExecutableType, // because that would only have the nullness annotations; here we want // to // see all annotations on the receiver. List<? extends AnnotationMirror> rcvannos = null; if (TreeUtils.isConstructor(node)) { com.sun.tools.javac.code.Symbol meth = (com.sun.tools.javac.code.Symbol) TreeUtils.elementFromDeclaration(node); rcvannos = meth.getRawTypeAttributes(); if (rcvannos == null) { rcvannos = Collections.<AnnotationMirror>emptyList(); } } return rcvannos; } /** * Checks that all fields (all static fields if {@code staticFields} is true) are initialized in * the given store. */ // TODO: the code for checking if fields are initialized should be re-written, // as the current version contains quite a few ugly parts, is hard to understand, // and it is likely that it does not take full advantage of the information // about initialization we compute in // GenericAnnotatedTypeFactory.initializationStaticStore and // GenericAnnotatedTypeFactory.initializationStore. protected void checkFieldsInitialized( Tree blockNode, boolean staticFields, Store store, List<? extends AnnotationMirror> receiverAnnotations) { // If the store is null, then the constructor cannot terminate // successfully if (store != null) { List<VariableTree> violatingFields = atypeFactory.getUninitializedInvariantFields( store, getCurrentPath(), staticFields, receiverAnnotations); if (staticFields) { // TODO: Why is nothing done for static fields? // Do we need the following? // violatingFields.removeAll(store.initializedFields); } else { // remove fields that have already been initialized by an // initializer block violatingFields.removeAll(initializedFields); } // Remove fields with a relevant @SuppressWarnings annotation. Iterator<VariableTree> itor = violatingFields.iterator(); while (itor.hasNext()) { VariableTree f = itor.next(); Element e = InternalUtils.symbol(f); if (checker.shouldSuppressWarnings(e, COMMITMENT_FIELDS_UNINITIALIZED)) { itor.remove(); } } if (!violatingFields.isEmpty()) { StringBuilder fieldsString = new StringBuilder(); boolean first = true; for (VariableTree f : violatingFields) { if (!first) { fieldsString.append(", "); } first = false; fieldsString.append(f.getName()); } checker.report( Result.failure(COMMITMENT_FIELDS_UNINITIALIZED, fieldsString), blockNode); } } } }