package org.checkerframework.checker.initialization;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
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.dataflow.analysis.FlowExpressions;
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.dataflow.cfg.CFGVisualizer;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.flow.CFAbstractStore;
import org.checkerframework.framework.flow.CFAbstractValue;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.javacutil.AnnotationUtils;
/**
* A store that extends {@code CFAbstractStore} and additionally tracks which fields of the 'self'
* reference have been initialized.
*
* @author Stefan Heule
* @see InitializationTransfer
*/
public class InitializationStore<V extends CFAbstractValue<V>, S extends InitializationStore<V, S>>
extends CFAbstractStore<V, S> {
/** The list of fields that are initialized. */
protected final Set<VariableElement> initializedFields;
public InitializationStore(CFAbstractAnalysis<V, S, ?> analysis, boolean sequentialSemantics) {
super(analysis, sequentialSemantics);
initializedFields = new HashSet<>();
}
/**
* {@inheritDoc}
*
* <p>If the receiver is a field, and has an invariant annotation, then it can be considered
* initialized.
*/
@Override
public void insertValue(Receiver r, V value) {
if (value == null) {
// No need to insert a null abstract value because it represents
// top and top is also the default value.
return;
}
super.insertValue(r, value);
InitializationAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory =
(InitializationAnnotatedTypeFactory<?, ?, ?, ?>) analysis.getTypeFactory();
QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
AnnotationMirror invariantAnno = atypeFactory.getFieldInvariantAnnotation();
for (AnnotationMirror a : value.getAnnotations()) {
if (qualifierHierarchy.isSubtype(a, invariantAnno)) {
if (r instanceof FieldAccess) {
FieldAccess fa = (FieldAccess) r;
if (fa.getReceiver() instanceof ThisReference
|| fa.getReceiver() instanceof ClassName) {
addInitializedField(fa.getField());
}
}
}
}
}
/**
* {@inheritDoc}
*
* <p>Additionally, the {@link InitializationStore} keeps all field values for fields that have
* the 'invariant' annotation.
*/
@Override
public void updateForMethodCall(
MethodInvocationNode n, AnnotatedTypeFactory atypeFactory, V val) {
AnnotationMirror fieldInvariantAnnotation =
((InitializationAnnotatedTypeFactory<?, ?, ?, ?>) atypeFactory)
.getFieldInvariantAnnotation();
// Are there fields that have the 'invariant' annotations and are in the
// store?
List<FlowExpressions.FieldAccess> invariantFields = new ArrayList<>();
for (Entry<FlowExpressions.FieldAccess, V> e : fieldValues.entrySet()) {
FlowExpressions.FieldAccess fieldAccess = e.getKey();
Set<AnnotationMirror> declaredAnnos =
atypeFactory.getAnnotatedType(fieldAccess.getField()).getAnnotations();
if (AnnotationUtils.containsSame(declaredAnnos, fieldInvariantAnnotation)) {
invariantFields.add(fieldAccess);
}
}
super.updateForMethodCall(n, atypeFactory, val);
// Add invariant annotation again.
for (FieldAccess invariantField : invariantFields) {
insertValue(invariantField, fieldInvariantAnnotation);
}
}
/** A copy constructor. */
public InitializationStore(S other) {
super(other);
initializedFields = new HashSet<>(other.initializedFields);
}
/**
* Mark the field identified by the element {@code field} as initialized (if it belongs to the
* current class, or is static (in which case there is no aliasing issue and we can just add all
* static fields).
*/
public void addInitializedField(FieldAccess field) {
boolean fieldOnThisReference = field.getReceiver() instanceof ThisReference;
boolean staticField = field.isStatic();
if (fieldOnThisReference || staticField) {
initializedFields.add(field.getField());
}
}
/**
* Mark the field identified by the element {@code f} as initialized (the caller needs to ensure
* that the field belongs to the current class, or is a static field).
*/
public void addInitializedField(VariableElement f) {
initializedFields.add(f);
}
/** Is the field identified by the element {@code f} initialized? */
public boolean isFieldInitialized(Element f) {
return initializedFields.contains(f);
}
@Override
protected boolean supersetOf(CFAbstractStore<V, S> o) {
if (!(o instanceof InitializationStore)) {
return false;
}
@SuppressWarnings("unchecked")
S other = (S) o;
for (Element field : other.initializedFields) {
if (!initializedFields.contains(field)) {
return false;
}
}
return super.supersetOf(other);
}
@Override
public S leastUpperBound(S other) {
S result = super.leastUpperBound(other);
// Set intersection for initializedFields.
result.initializedFields.addAll(other.initializedFields);
result.initializedFields.retainAll(initializedFields);
return result;
}
@Override
protected void internalVisualize(CFGVisualizer<V, S, ?> viz) {
super.internalVisualize(viz);
viz.visualizeStoreKeyVal("initialized fields", initializedFields);
}
public Map<FieldAccess, V> getFieldValues() {
return fieldValues;
}
public CFAbstractAnalysis<V, S, ?> getAnalysis() {
return analysis;
}
}