package org.checkerframework.framework.util;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
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.ExecutableElement;
import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation;
import org.checkerframework.framework.qual.EnsuresQualifier;
import org.checkerframework.framework.qual.EnsuresQualifierIf;
import org.checkerframework.framework.qual.EnsuresQualifiers;
import org.checkerframework.framework.qual.EnsuresQualifiersIf;
import org.checkerframework.framework.qual.PostconditionAnnotation;
import org.checkerframework.framework.qual.PreconditionAnnotation;
import org.checkerframework.framework.qual.RequiresQualifier;
import org.checkerframework.framework.qual.RequiresQualifiers;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.Pair;
/**
* A utility class to handle pre- and postconditions.
*
* @see PreconditionAnnotation
* @see RequiresQualifier
* @see PostconditionAnnotation
* @see EnsuresQualifier
* @see EnsuresQualifierIf
* @author Stefan Heule
*/
public class ContractsUtils {
protected static ContractsUtils instance;
protected GenericAnnotatedTypeFactory<?, ?, ?, ?> factory;
/** Returns an instance of the {@link ContractsUtils} class. */
public static ContractsUtils getInstance(GenericAnnotatedTypeFactory<?, ?, ?, ?> factory) {
if (instance == null || instance.factory != factory) {
instance = new ContractsUtils(factory);
}
return instance;
}
/**
* A contract represents an annotation on an expression, along with the kind: precondition,
* postcondition, or conditional postcondition.
*/
public abstract static class Contract {
public enum Kind {
PRECONDITION("precondition"),
POSTCONDTION("postcondition"),
CONDITIONALPOSTCONDTION("conditional.postcondition");
public final String errorKey;
Kind(String errorKey) {
this.errorKey = errorKey;
}
}
/**
* The expression for which the condition must hold, such as {@code "foo"} in
* {@code @RequiresNonNull("foo")}.
*/
public final String expression;
/** The annotation that must be on the type of expression as part of this contract. */
public final AnnotationMirror annotation;
public final Kind kind;
public Contract(String expression, AnnotationMirror annotation, Kind kind) {
this.expression = expression;
this.annotation = annotation;
this.kind = kind;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Contract contract = (Contract) o;
if (expression != null
? !expression.equals(contract.expression)
: contract.expression != null) {
return false;
}
if (annotation != null
? !annotation.equals(contract.annotation)
: contract.annotation != null) {
return false;
}
return kind == contract.kind;
}
@Override
public int hashCode() {
int result = expression != null ? expression.hashCode() : 0;
result = 31 * result + (annotation != null ? annotation.hashCode() : 0);
result = 31 * result + (kind != null ? kind.hashCode() : 0);
return result;
}
}
public static class Precondition extends Contract {
public Precondition(String expression, AnnotationMirror annotation) {
super(expression, annotation, Kind.PRECONDITION);
}
}
public static class Postcondition extends Contract {
public Postcondition(String expression, AnnotationMirror annotation) {
super(expression, annotation, Kind.POSTCONDTION);
}
}
/**
* Represents a conditional postcondition that must be verified by {@code BaseTypeVisitor} or
* one of its subclasses. Automatically extracted from annotations with meta-annotation
* {@code @ConditionalPostconditionAnnotation}, such as {@code EnsuresNonNullIf}.
*/
public static class ConditionalPostcondition extends Contract {
/**
* The return value for the annotated method that ensures that the conditional postcondition
* holds. For example, given<br>
* {@code @EnsuresNonNullIf(expression="foo", result=false) boolean method()}<br>
* {@code foo} is guaranteed to be {@code @NonNull} after a call to {@code method()} if that
* call returns {@code false}.
*/
public final boolean annoResult;
public ConditionalPostcondition(
String expression, boolean annoResult, AnnotationMirror annotation) {
super(expression, annotation, Kind.CONDITIONALPOSTCONDTION);
this.annoResult = annoResult;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
ConditionalPostcondition that = (ConditionalPostcondition) o;
return annoResult == that.annoResult;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (annoResult ? 1 : 0);
return result;
}
}
public List<Contract> getContracts(ExecutableElement element) {
List<Contract> contracts = new ArrayList<>();
contracts.addAll(getPreconditions(element));
contracts.addAll(getPostconditions(element));
contracts.addAll(getConditionalPostconditions(element));
return contracts;
}
/** Returns the set of preconditions on the element {@code element}. */
public Set<Precondition> getPreconditions(Element element) {
Set<Precondition> result = new LinkedHashSet<>();
// Check for a single contract.
AnnotationMirror requiresAnnotation =
factory.getDeclAnnotation(element, RequiresQualifier.class);
result.addAll(getPrecondition(requiresAnnotation));
// Check for multiple contracts.
AnnotationMirror requiresAnnotations =
factory.getDeclAnnotation(element, RequiresQualifiers.class);
if (requiresAnnotations != null) {
List<AnnotationMirror> annotations =
AnnotationUtils.getElementValueArray(
requiresAnnotations, "value", AnnotationMirror.class, false);
for (AnnotationMirror a : annotations) {
result.addAll(getPrecondition(a));
}
}
// Check type-system specific annotations.
Class<PreconditionAnnotation> metaAnnotation = PreconditionAnnotation.class;
List<Pair<AnnotationMirror, AnnotationMirror>> declAnnotations =
factory.getDeclAnnotationWithMetaAnnotation(element, metaAnnotation);
for (Pair<AnnotationMirror, AnnotationMirror> r : declAnnotations) {
AnnotationMirror anno = r.first;
AnnotationMirror metaAnno = r.second;
List<String> expressions =
AnnotationUtils.getElementValueArray(anno, "value", String.class, false);
AnnotationMirror precondtionAnno = getAnnotationMirrorOfQualifier(metaAnno);
if (precondtionAnno == null) {
continue;
}
for (String expr : expressions) {
result.add(new Precondition(expr, precondtionAnno));
}
}
return result;
}
/** Returns the annotation mirror as specified by the "qualifier" value in metaAnno. */
private AnnotationMirror getAnnotationMirrorOfQualifier(AnnotationMirror metaAnno) {
@SuppressWarnings("unchecked")
Class<? extends Annotation> c =
(Class<? extends Annotation>)
AnnotationUtils.getElementValueClass(metaAnno, "qualifier", false);
AnnotationMirror anno = AnnotationUtils.fromClass(factory.getElementUtils(), c);
if (factory.isSupportedQualifier(anno)) {
return anno;
} else {
return null;
}
}
/** Returns the set of preconditions according to the given {@link RequiresQualifier}. */
private Set<Precondition> getPrecondition(AnnotationMirror requiresAnnotation) {
if (requiresAnnotation == null) {
return Collections.emptySet();
}
Set<Precondition> result = new LinkedHashSet<Precondition>();
List<String> expressions =
AnnotationUtils.getElementValueArray(
requiresAnnotation, "expression", String.class, false);
AnnotationMirror postcondAnno = getAnnotationMirrorOfQualifier(requiresAnnotation);
if (postcondAnno == null) {
return result;
}
for (String expr : expressions) {
result.add(new Precondition(expr, postcondAnno));
}
return result;
}
/** Returns the set of postconditions on the method {@code methodElement}. */
public Set<Postcondition> getPostconditions(ExecutableElement methodElement) {
Set<Postcondition> result = new LinkedHashSet<>();
// Check for a single contract.
AnnotationMirror ensuresAnnotation =
factory.getDeclAnnotation(methodElement, EnsuresQualifier.class);
result.addAll(getPostcondition(ensuresAnnotation));
// Check for multiple contracts.
AnnotationMirror ensuresAnnotations =
factory.getDeclAnnotation(methodElement, EnsuresQualifiers.class);
if (ensuresAnnotations != null) {
List<AnnotationMirror> annotations =
AnnotationUtils.getElementValueArray(
ensuresAnnotations, "value", AnnotationMirror.class, false);
for (AnnotationMirror a : annotations) {
result.addAll(getPostcondition(a));
}
}
// Check type-system specific annotations.
Class<PostconditionAnnotation> metaAnnotation = PostconditionAnnotation.class;
List<Pair<AnnotationMirror, AnnotationMirror>> declAnnotations =
factory.getDeclAnnotationWithMetaAnnotation(methodElement, metaAnnotation);
for (Pair<AnnotationMirror, AnnotationMirror> r : declAnnotations) {
AnnotationMirror anno = r.first;
AnnotationMirror metaAnno = r.second;
List<String> expressions =
AnnotationUtils.getElementValueArray(anno, "value", String.class, false);
AnnotationMirror postcondAnno = getAnnotationMirrorOfQualifier(metaAnno);
if (postcondAnno == null) {
continue;
}
for (String expr : expressions) {
result.add(new Postcondition(expr, postcondAnno));
}
}
return result;
}
/** Returns the set of postconditions according to the given {@link EnsuresQualifier}. */
private Set<Postcondition> getPostcondition(AnnotationMirror ensuresAnnotation) {
if (ensuresAnnotation == null) {
return Collections.emptySet();
}
Set<Postcondition> result = new LinkedHashSet<>();
List<String> expressions =
AnnotationUtils.getElementValueArray(
ensuresAnnotation, "expression", String.class, false);
AnnotationMirror postcondAnno = getAnnotationMirrorOfQualifier(ensuresAnnotation);
if (postcondAnno == null) {
return result;
}
for (String expr : expressions) {
result.add(new Postcondition(expr, postcondAnno));
}
return result;
}
/**
* Returns a set of triples {@code (expr, (result, annotation))} of conditional postconditions
* on the method {@code methodElement}.
*/
public Set<ConditionalPostcondition> getConditionalPostconditions(
ExecutableElement methodElement) {
Set<ConditionalPostcondition> result = new LinkedHashSet<ConditionalPostcondition>();
// Check for a single contract.
AnnotationMirror ensuresAnnotationIf =
factory.getDeclAnnotation(methodElement, EnsuresQualifierIf.class);
result.addAll(getConditionalPostcondition(ensuresAnnotationIf));
// Check for multiple contracts.
AnnotationMirror ensuresAnnotationsIf =
factory.getDeclAnnotation(methodElement, EnsuresQualifiersIf.class);
if (ensuresAnnotationsIf != null) {
List<AnnotationMirror> annotations =
AnnotationUtils.getElementValueArray(
ensuresAnnotationsIf, "value", AnnotationMirror.class, false);
for (AnnotationMirror a : annotations) {
result.addAll(getConditionalPostcondition(a));
}
}
// Check type-system specific annotations.
Class<ConditionalPostconditionAnnotation> metaAnnotation =
ConditionalPostconditionAnnotation.class;
List<Pair<AnnotationMirror, AnnotationMirror>> declAnnotations =
factory.getDeclAnnotationWithMetaAnnotation(methodElement, metaAnnotation);
for (Pair<AnnotationMirror, AnnotationMirror> r : declAnnotations) {
AnnotationMirror anno = r.first;
AnnotationMirror metaAnno = r.second;
List<String> expressions =
AnnotationUtils.getElementValueArray(anno, "expression", String.class, false);
AnnotationMirror postcondAnno = getAnnotationMirrorOfQualifier(metaAnno);
if (postcondAnno == null) {
continue;
}
boolean annoResult =
AnnotationUtils.getElementValue(anno, "result", Boolean.class, false);
for (String expr : expressions) {
result.add(new ConditionalPostcondition(expr, annoResult, postcondAnno));
}
}
return result;
}
/**
* Returns a set of triples {@code (expr, (result, annotation))} of conditional postconditions
* according to the given {@link EnsuresQualifierIf}.
*/
private Set<ConditionalPostcondition> getConditionalPostcondition(
AnnotationMirror ensuresAnnotationIf) {
if (ensuresAnnotationIf == null) {
return Collections.emptySet();
}
Set<ConditionalPostcondition> result = new LinkedHashSet<>();
List<String> expressions =
AnnotationUtils.getElementValueArray(
ensuresAnnotationIf, "expression", String.class, false);
AnnotationMirror postcondAnno = getAnnotationMirrorOfQualifier(ensuresAnnotationIf);
if (postcondAnno == null) {
return result;
}
boolean annoResult =
AnnotationUtils.getElementValue(
ensuresAnnotationIf, "result", Boolean.class, false);
for (String expr : expressions) {
result.add(new ConditionalPostcondition(expr, annoResult, postcondAnno));
}
return result;
}
// private constructor
private ContractsUtils(GenericAnnotatedTypeFactory<?, ?, ?, ?> factory) {
this.factory = factory;
}
}