blob: 9db9cb6a6003d54c0650361e8a46c38c6f1ad465 [file] [log] [blame]
package org.checkerframework.checker.initialization;
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.MethodInvocationTree;
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.List;
import java.util.Set;
import java.util.StringJoiner;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.nullness.NullnessChecker;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.common.wholeprograminference.WholeProgramInference;
import org.checkerframework.dataflow.expression.ClassName;
import org.checkerframework.dataflow.expression.FieldAccess;
import org.checkerframework.dataflow.expression.JavaExpression;
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.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.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}.
*/
public class InitializationVisitor<
Factory extends InitializationAnnotatedTypeFactory<Value, Store, ?, ?>,
Value extends CFAbstractValue<Value>,
Store extends InitializationStore<Value, Store>>
extends BaseTypeVisitor<Factory> {
/** The annotation formatter. */
protected final AnnotationFormatter annoFormatter;
/**
* Creates a new InitializationVisitor.
*
* @param checker the associated type-checker
*/
public InitializationVisitor(BaseTypeChecker checker) {
super(checker);
annoFormatter = new DefaultAnnotationFormatter();
initializedFields = new ArrayList<>();
}
@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 void checkConstructorInvocation(
AnnotatedDeclaredType dt, AnnotatedExecutableType constructor, NewClassTree src) {
// Receiver annotations for constructors are forbidden, therefore no check is necessary.
// TODO: nested constructors can have receivers!
}
@Override
protected void checkConstructorResult(
AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {
// Nothing to check
}
@Override
protected void checkThisOrSuperConstructorCall(
MethodInvocationTree superCall, @CompilerMessageKey String errorKey) {
// Nothing to check
}
@Override
protected void commonAssignmentCheck(
Tree varTree,
ExpressionTree valueExp,
@CompilerMessageKey String errorKey,
Object... extraArgs) {
// 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.containsSameByName(
fieldAnnotations, atypeFactory.UNKNOWN_INITIALIZATION)) {
if (!ElementUtils.isStatic(el)
&& !(atypeFactory.isInitialized(yType)
|| atypeFactory.isUnderInitialization(xType)
|| atypeFactory.isFbcBottom(yType))) {
@CompilerMessageKey String err;
if (atypeFactory.isInitialized(xType)) {
err = "initialization.field.write.initialized";
} else {
err = "initialization.field.write.unknown";
}
checker.reportError(varTree, err, varTree);
return; // prevent issuing another errow about subtyping
}
}
}
super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs);
}
@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.isUnknownInitialization(a)) {
continue; // unknown initialization is allowed
}
if (atypeFactory.areSameByClass(a, c)) {
checker.reportError(node, "initialization.field.type", node);
break;
}
}
}
}
return super.visitVariable(node, p);
}
@Override
protected boolean checkContract(
JavaExpression 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)
|| !(expr instanceof FieldAccess)) {
return super.checkContract(expr, necessaryAnnotation, inferredAnnotation, store);
}
if (expr.containsUnknown()) {
return false;
}
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 {
@SuppressWarnings("unchecked")
Value value = (Value) store.getValue(fa.getReceiver());
Set<AnnotationMirror> receiverAnnoSet;
if (value != null) {
receiverAnnoSet = value.getAnnotations();
} else if (fa.getReceiver() instanceof LocalVariable) {
Element elem = ((LocalVariable) fa.getReceiver()).getElement();
AnnotatedTypeMirror receiverType = atypeFactory.getAnnotatedType(elem);
receiverAnnoSet = receiverType.getAnnotations();
} else {
// Is there anything better we could do?
return false;
}
boolean isReceiverInitialized = false;
for (AnnotationMirror anno : receiverAnnoSet) {
if (atypeFactory.isInitialized(anno)) {
isReceiverInitialized = true;
}
}
AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(fa.getField());
// The receiver is fully initialized and the field type
// has the invariant type.
if (isReceiverInitialized
&& 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.reportError(
node,
"initialization.cast",
annoFormatter.formatAnnotationMirror(exprAnno),
annoFormatter.formatAnnotationMirror(castAnno));
return p; // suppress cast.unsafe warning
}
return super.visitTypeCast(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.getKind() == Tree.Kind.BLOCK && !((BlockTree) member).isStatic()) {
BlockTree block = (BlockTree) member;
Store store = atypeFactory.getRegularExitStore(block);
// 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);
// Warn about uninitialized static fields.
if (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 (atypeFactory.areSameByClass(a, c)) {
checker.reportError(node, "initialization.constructor.return.type", 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.emptyList();
}
}
return rcvannos;
}
/**
* Checks that all fields (all static fields if {@code staticFields} is true) are initialized in
* the given store.
*
* @param node a {@link ClassTree} if {@code staticFields} is true; a {@link MethodTree} for a
* constructor if {@code staticFields} is false. This is where errors are reported, if they
* are not reported at the fields themselves
* @param staticFields whether to check static fields or instance fields
* @param store the store
* @param receiverAnnotations the annotations on the receiver
*/
// 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 node,
boolean staticFields,
Store store,
List<? extends AnnotationMirror> receiverAnnotations) {
// If the store is null, then the constructor cannot terminate successfully
if (store == null) {
return;
}
Pair<List<VariableTree>, List<VariableTree>> uninitializedFields =
atypeFactory.getUninitializedFields(
store, getCurrentPath(), staticFields, receiverAnnotations);
List<VariableTree> violatingFields = uninitializedFields.first;
List<VariableTree> nonviolatingFields = uninitializedFields.second;
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);
nonviolatingFields.removeAll(initializedFields);
}
// Errors are issued at the field declaration if the field is static or if the constructor
// is the default constructor.
// Errors are issued at the constructor declaration if the field is non-static and the
// constructor is non-default.
boolean errorAtField = staticFields || TreeUtils.isSynthetic((MethodTree) node);
String FIELDS_UNINITIALIZED_KEY =
(staticFields
? "initialization.static.field.uninitialized"
: errorAtField
? "initialization.field.uninitialized"
: "initialization.fields.uninitialized");
// Remove fields with a relevant @SuppressWarnings annotation.
violatingFields.removeIf(
f ->
checker.shouldSuppressWarnings(TreeUtils.elementFromTree(f), FIELDS_UNINITIALIZED_KEY));
nonviolatingFields.removeIf(
f ->
checker.shouldSuppressWarnings(TreeUtils.elementFromTree(f), FIELDS_UNINITIALIZED_KEY));
if (!violatingFields.isEmpty()) {
if (errorAtField) {
// Issue each error at the relevant field
for (VariableTree f : violatingFields) {
checker.reportError(f, FIELDS_UNINITIALIZED_KEY, f.getName());
}
} else {
// Issue all the errors at the relevant constructor
StringJoiner fieldsString = new StringJoiner(", ");
for (VariableTree f : violatingFields) {
fieldsString.add(f.getName());
}
checker.reportError(node, FIELDS_UNINITIALIZED_KEY, fieldsString);
}
}
// Support -Ainfer command-line argument.
WholeProgramInference wpi = atypeFactory.getWholeProgramInference();
if (wpi != null) {
// For each uninitialized field, treat it as if the default value is assigned to it.
List<VariableTree> uninitFields = new ArrayList<>(violatingFields);
uninitFields.addAll(nonviolatingFields);
for (VariableTree fieldTree : uninitFields) {
Element elt = TreeUtils.elementFromTree(fieldTree);
wpi.updateFieldFromType(
fieldTree,
elt,
fieldTree.getName().toString(),
atypeFactory.getDefaultValueAnnotatedType(elt.asType()));
}
}
}
}