org.checkerframework.common.accumulation.AccumulationValue Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of checker Show documentation
Show all versions of checker Show documentation
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.
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])))
+ "] }";
}
}