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

checker.src.org.checkerframework.checker.initialization.InitializationVisitor 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.checker.initialization;

/*>>>
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.nullness.qual.Nullable;
*/

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.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;

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 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;

/**
 * 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,
        Value extends CFAbstractValue,
        Store extends InitializationStore>
    extends BaseTypeVisitor {

    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 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 annotationMirrors = atypeFactory.getAnnotatedType(
                    node).getExplicitAnnotations();
            // Fields cannot have commitment annotations.
            for (Class 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
        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;
                        }
                    }
                }
            }
        }
        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 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 t : store.getAnalysis().getFieldValues()) {
                    store.addInitializedField(t.first);
                }

                // Check that all static fields are initialized.
                List receiverAnnotations = Collections.emptyList();
                checkFieldsInitialized(node, isStatic, store, receiverAnnotations);
            }
        }
        return super.visitBlock(node, p);
    }

    protected final List initializedFields;

    @Override
    public Void visitClass(ClassTree node, Void p) {
        // call the ATF with any node from this class to trigger the org.checkerframework.dataflow
        // analysis.
        atypeFactory.getAnnotatedType(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 t : store.getAnalysis()
                            .getFieldValues()) {
                        store.addInitializedField(t.first);
                    }
                    final List init = atypeFactory
                            .getInitializedInvariantFields(store,
                                    getCurrentPath());
                    initializedFields.addAll(init);
                }
            }
        }

        Void result = super.visitClass(node, p);

        // 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 t : store.getAnalysis().getFieldValues()) {
                store.addInitializedField(t.first);
            }
            List receiverAnnotations = Collections.emptyList();
            checkFieldsInitialized(node, isStatic, store, receiverAnnotations);
        }

        return result;
    }

    @Override
    public Void visitMethod(MethodTree node, Void p) {
        if (TreeUtils.isConstructor(node)) {
            Collection returnTypeAnnotations = AnnotationUtils.getExplicitAnnotationsOnConstructorResult(node);
            // check for invalid constructor return type
            for (Class 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 receiverAnnotations = getAllReceiverAnnotations(node);
            checkFieldsInitialized(node, isStatic, store, receiverAnnotations);
        }
        return super.visitMethod(node, p);
    }

    /**
     * Returns the full list of annotations on the receiver.
     */
    private List 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 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. 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 receiverAnnotations) {
        // If the store is null, then the constructor cannot terminate
        // successfully
        if (store != null) {
            List 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 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);
            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy