org.checkerframework.common.accumulation.AccumulationValue Maven / Gradle / Ivy
package org.checkerframework.common.accumulation;
import java.util.HashSet;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.flow.CFAbstractValue;
import org.checkerframework.javacutil.AnnotationMirrorSet;
/**
* AccumulationValue holds additional information about accumulated facts ("values", not to be
* confused with "Value" in the name of this class) that cannot be stored in the accumulation type,
* because they are not a refinement of that type. This situation occurs for type variables and
* wildcards, for which calling {@link AccumulationTransfer#accumulate(Node, TransferResult,
* String...)} would otherwise have no effect (since the types are invariant: T is not a supertype
* of Accumulator(a) T unless both bounds of T are supertypes of Accumulator(a)). This enables an
* accumulation checker (or, typically, a client of that accumulation checker) to resolve
* accumulated facts even on types that are type variables. For example, the Resource Leak Checker
* uses this facility to check that calls to close() on variables whose type is a type variable have
* actually occurred, such as in this example:
*
*
* public static <T extends java.io.Closeable> void close(
* @Owning @MustCall("close") T value) throws Exception {
* value.close();
* }
*
*/
public class AccumulationValue extends CFAbstractValue {
/**
* If the underlying type is a type variable or a wildcard, then this is a set of accumulated
* values. Otherwise, it is null.
*/
private @Nullable Set accumulatedValues = null;
/**
* Creates a new CFAbstractValue.
*
* @param analysis the analysis class this value belongs to
* @param annotations the annotations in this abstract value
* @param underlyingType the underlying (Java) type in this abstract value
*/
protected AccumulationValue(
CFAbstractAnalysis analysis,
AnnotationMirrorSet annotations,
TypeMirror underlyingType) {
super(analysis, annotations, underlyingType);
if (underlyingType.getKind() == TypeKind.TYPEVAR
|| underlyingType.getKind() == TypeKind.WILDCARD) {
AccumulationAnnotatedTypeFactory typeFactory =
(AccumulationAnnotatedTypeFactory) analysis.getTypeFactory();
AnnotationMirror accumulator = null;
for (AnnotationMirror anm : annotations) {
if (typeFactory.isAccumulatorAnnotation(anm)) {
accumulator = anm;
break;
}
}
if (accumulator != null) {
accumulatedValues = new HashSet<>(typeFactory.getAccumulatedValues(accumulator));
}
}
}
/**
* If the underlying type is a type variable or a wildcard, then this is a set of accumulated
* values. Otherwise, it is null.
*
* @return the set (this is not a copy of the set, but an alias)
*/
public @Nullable Set getAccumulatedValues() {
return accumulatedValues;
}
@Override
protected AccumulationValue upperBound(
@Nullable AccumulationValue other, TypeMirror upperBoundTypeMirror, boolean shouldWiden) {
AccumulationValue lub = super.upperBound(other, upperBoundTypeMirror, shouldWiden);
if (other == null || other.accumulatedValues == null || this.accumulatedValues == null) {
return lub;
}
// Lub the accumulatedValues by intersecting the lists as if they were sets.
lub.accumulatedValues = new HashSet<>(this.accumulatedValues.size());
lub.accumulatedValues.addAll(this.accumulatedValues);
lub.accumulatedValues.retainAll(other.accumulatedValues);
if (lub.accumulatedValues.isEmpty()) {
lub.accumulatedValues = null;
}
return lub;
}
@Override
public @Nullable AccumulationValue mostSpecific(
AccumulationValue other, AccumulationValue backup) {
if (other == null) {
return this;
}
AccumulationValue mostSpecific = super.mostSpecific(other, backup);
if (mostSpecific != null) {
mostSpecific.addAccumulatedValues(this.accumulatedValues);
mostSpecific.addAccumulatedValues(other.accumulatedValues);
return mostSpecific;
}
// mostSpecific is null if the two types are not comparable. This is normally
// because one of this or other is a type variable and annotations is empty, but the
// other annotations are not empty. In this case, copy the accumulatedValues to the
// value with no annotations and return it as most specific.
if (other.getAnnotations().isEmpty()) {
mostSpecific =
new AccumulationValue(
analysis, AnnotationMirrorSet.emptySet(), other.getUnderlyingType());
mostSpecific.addAccumulatedValues(this.accumulatedValues);
mostSpecific.addAccumulatedValues(other.accumulatedValues);
return mostSpecific;
} else if (this.getAnnotations().isEmpty()) {
mostSpecific =
new AccumulationValue(
analysis, AnnotationMirrorSet.emptySet(), other.getUnderlyingType());
mostSpecific.addAccumulatedValues(this.accumulatedValues);
mostSpecific.addAccumulatedValues(other.accumulatedValues);
return mostSpecific;
}
return null;
}
/**
* Merges its argument into the {@link #accumulatedValues} field of this.
*
* @param newAccumulatedValues a new list of accumulated values
*/
private void addAccumulatedValues(Set newAccumulatedValues) {
if (newAccumulatedValues == null || newAccumulatedValues.isEmpty()) {
return;
}
if (accumulatedValues == null) {
accumulatedValues = new HashSet<>(newAccumulatedValues.size());
}
accumulatedValues.addAll(newAccumulatedValues);
}
@Override
public String toString() {
String superToString = super.toString();
// remove last '}'
superToString = superToString.substring(0, superToString.length() - 1);
return superToString
+ ", "
+ "["
+ (accumulatedValues == null
? "null"
: String.join(", ", accumulatedValues.toArray(new String[0])))
+ "] }";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy