blob: be0e307f52b38e00c0ba0a4b5a2b5f576fb1e864 [file] [log] [blame]
package org.checkerframework.framework.util;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.util.ElementFilter;
import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation;
import org.checkerframework.framework.qual.EnsuresQualifier;
import org.checkerframework.framework.qual.EnsuresQualifierIf;
import org.checkerframework.framework.qual.PostconditionAnnotation;
import org.checkerframework.framework.qual.PreconditionAnnotation;
import org.checkerframework.framework.qual.QualifierArgument;
import org.checkerframework.framework.qual.RequiresQualifier;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.framework.util.Contract.Kind;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreeUtils;
/**
* A utility class to retrieve pre- and postconditions from a method.
*
* @see PreconditionAnnotation
* @see RequiresQualifier
* @see PostconditionAnnotation
* @see EnsuresQualifier
* @see ConditionalPostconditionAnnotation
* @see EnsuresQualifierIf
*/
// TODO: This class assumes that most annotations have a field named "expression". If not, issue a
// more helpful error message.
public class ContractsFromMethod {
/** The QualifierArgument.value field/element. */
ExecutableElement qualifierArgumentValueElement;
/** The factory that this ContractsFromMethod is associated with. */
protected GenericAnnotatedTypeFactory<?, ?, ?, ?> factory;
/**
* Creates a ContractsFromMethod for the given factory.
*
* @param factory the type factory associated with the newly-created ContractsFromMethod
*/
public ContractsFromMethod(GenericAnnotatedTypeFactory<?, ?, ?, ?> factory) {
this.factory = factory;
qualifierArgumentValueElement =
TreeUtils.getMethod(QualifierArgument.class, "value", 0, factory.getProcessingEnv());
}
/**
* Returns all the contracts on method or constructor {@code executableElement}.
*
* @param executableElement the method or constructor whose contracts to retrieve
* @return the contracts on {@code executableElement}
*/
public Set<Contract> getContracts(ExecutableElement executableElement) {
Set<Contract> contracts = new LinkedHashSet<>();
contracts.addAll(getPreconditions(executableElement));
contracts.addAll(getPostconditions(executableElement));
contracts.addAll(getConditionalPostconditions(executableElement));
return contracts;
}
/**
* Returns the precondition contracts on method or constructor {@code executableElement}.
*
* @param executableElement the method whose contracts to return
* @return the precondition contracts on {@code executableElement}
*/
public Set<Contract.Precondition> getPreconditions(ExecutableElement executableElement) {
return getContracts(executableElement, Kind.PRECONDITION, Contract.Precondition.class);
}
/**
* Returns the postcondition contracts on {@code executableElement}.
*
* @param executableElement the method whose contracts to return
* @return the postcondition contracts on {@code executableElement}
*/
public Set<Contract.Postcondition> getPostconditions(ExecutableElement executableElement) {
return getContracts(executableElement, Kind.POSTCONDITION, Contract.Postcondition.class);
}
/**
* Returns the conditional postcondition contracts on method {@code methodElement}.
*
* @param methodElement the method whose contracts to return
* @return the conditional postcondition contracts on {@code methodElement}
*/
public Set<Contract.ConditionalPostcondition> getConditionalPostconditions(
ExecutableElement methodElement) {
return getContracts(
methodElement, Kind.CONDITIONALPOSTCONDITION, Contract.ConditionalPostcondition.class);
}
/// Helper methods
/**
* Returns the contracts (of a particular kind) on method or constructor {@code
* executableElement}.
*
* @param <T> the type of {@link Contract} to return
* @param executableElement the method whose contracts to return
* @param kind the kind of contracts to retrieve
* @param clazz the class to determine the return type
* @return the contracts on {@code executableElement}
*/
private <T extends Contract> Set<T> getContracts(
ExecutableElement executableElement, Kind kind, Class<T> clazz) {
Set<T> result = new LinkedHashSet<>();
// Check for a single framework-defined contract annotation.
AnnotationMirror frameworkContractAnno =
factory.getDeclAnnotation(executableElement, kind.frameworkContractClass);
result.addAll(getContract(kind, frameworkContractAnno, clazz));
// Check for a framework-defined wrapper around contract annotations.
// The result is RequiresQualifier.List, EnsuresQualifier.List, or EnsuresQualifierIf.List.
AnnotationMirror frameworkContractListAnno =
factory.getDeclAnnotation(executableElement, kind.frameworkContractListClass);
if (frameworkContractListAnno != null) {
List<AnnotationMirror> frameworkContractAnnoList =
factory.getContractListValues(frameworkContractListAnno);
for (AnnotationMirror a : frameworkContractAnnoList) {
result.addAll(getContract(kind, a, clazz));
}
}
// Check for type-system specific annotations.
List<Pair<AnnotationMirror, AnnotationMirror>> declAnnotations =
factory.getDeclAnnotationWithMetaAnnotation(executableElement, kind.metaAnnotation);
for (Pair<AnnotationMirror, AnnotationMirror> r : declAnnotations) {
AnnotationMirror anno = r.first;
// contractAnno is the meta-annotation on anno.
AnnotationMirror contractAnno = r.second;
AnnotationMirror enforcedQualifier =
getQualifierEnforcedByContractAnnotation(contractAnno, anno);
if (enforcedQualifier == null) {
continue;
}
List<String> expressions = factory.getContractExpressions(kind, anno);
Collections.sort(expressions);
Boolean ensuresQualifierIfResult = factory.getEnsuresQualifierIfResult(kind, anno);
for (String expr : expressions) {
T contract =
clazz.cast(
Contract.create(kind, expr, enforcedQualifier, anno, ensuresQualifierIfResult));
result.add(contract);
}
}
return result;
}
/**
* Returns the contracts expressed by the given framework contract annotation.
*
* @param <T> the type of {@link Contract} to return
* @param kind the kind of {@code contractAnnotation}
* @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, {@link
* EnsuresQualifierIf}, or null
* @param clazz the class to determine the return type
* @return the contracts expressed by the given annotation, or the empty set if the argument is
* null
*/
private <T extends Contract> Set<T> getContract(
Contract.Kind kind, AnnotationMirror contractAnnotation, Class<T> clazz) {
if (contractAnnotation == null) {
return Collections.emptySet();
}
AnnotationMirror enforcedQualifier =
getQualifierEnforcedByContractAnnotation(contractAnnotation);
if (enforcedQualifier == null) {
return Collections.emptySet();
}
List<String> expressions = factory.getContractExpressions(contractAnnotation);
Collections.sort(expressions);
Boolean ensuresQualifierIfResult =
factory.getEnsuresQualifierIfResult(kind, contractAnnotation);
Set<T> result = new LinkedHashSet<>();
for (String expr : expressions) {
T contract =
clazz.cast(
Contract.create(
kind, expr, enforcedQualifier, contractAnnotation, ensuresQualifierIfResult));
result.add(contract);
}
return result;
}
/**
* Returns the annotation mirror as specified by the {@code qualifier} element in {@code
* contractAnno}. May return null.
*
* @param contractAnno a pre- or post-condition annotation, such as {@code @RequiresQualifier}
* @return the type annotation specified in {@code contractAnno.qualifier}
*/
private AnnotationMirror getQualifierEnforcedByContractAnnotation(AnnotationMirror contractAnno) {
return getQualifierEnforcedByContractAnnotation(contractAnno, null, null);
}
/**
* Returns the annotation mirror as specified by the {@code qualifier} element in {@code
* contractAnno}, with elements/arguments taken from {@code argumentAnno}. May return null.
*
* @param contractAnno a pre- or post-condition annotation, such as {@code @RequiresQualifier}
* @param argumentAnno supplies the elements/fields in the return value
* @return the type annotation specified in {@code contractAnno.qualifier}
*/
private AnnotationMirror getQualifierEnforcedByContractAnnotation(
AnnotationMirror contractAnno, AnnotationMirror argumentAnno) {
Map<String, String> argumentRenaming =
makeArgumentRenaming(argumentAnno.getAnnotationType().asElement());
return getQualifierEnforcedByContractAnnotation(contractAnno, argumentAnno, argumentRenaming);
}
/**
* Returns the annotation mirror as specified by the "qualifier" element in {@code contractAnno}.
* If {@code argumentAnno} is specified, then elements/arguments are copied from {@code
* argumentAnno} to the returned annotation, renamed according to {@code argumentRenaming}. If
* {@code argumentAnno} is not specified, the result has no elements/arguments; this may make it
* invalid.
*
* <p>This is a helper method. Use one of its overloads if possible.
*
* @param contractAnno a contract annotation, such as {@code @RequiresQualifier}, which has a
* {@code qualifier} element/field
* @param argumentAnno annotation containing the element {@code values}, or {@code null}
* @param argumentRenaming renaming of argument names, which maps from names in {@code
* argumentAnno} to names used in the returned annotation, or {@code null}
* @return a qualifier whose type is that of {@code contract.qualifier}, or an alias for it, or
* null if it is not a supported qualifier of the type system
*/
private AnnotationMirror getQualifierEnforcedByContractAnnotation(
AnnotationMirror contractAnno,
AnnotationMirror argumentAnno,
Map<String, String> argumentRenaming) {
@SuppressWarnings("deprecation") // permitted for use in the framework
Name c = AnnotationUtils.getElementValueClassName(contractAnno, "qualifier", false);
AnnotationMirror anno;
if (argumentAnno == null || argumentRenaming.isEmpty()) {
// If there are no arguments, use factory method that allows caching
anno = AnnotationBuilder.fromName(factory.getElementUtils(), c);
} else {
AnnotationBuilder builder = new AnnotationBuilder(factory.getProcessingEnv(), c);
builder.copyRenameElementValuesFromAnnotation(argumentAnno, argumentRenaming);
anno = builder.build();
}
if (factory.isSupportedQualifier(anno)) {
return anno;
} else {
AnnotationMirror aliasedAnno = factory.canonicalAnnotation(anno);
if (factory.isSupportedQualifier(aliasedAnno)) {
return aliasedAnno;
} else {
return null;
}
}
}
/**
* Makes a map from element names of a contract annotation to qualifier argument names, as defined
* by {@link QualifierArgument}.
*
* <p>Each element of {@code contractAnnoElement} that is annotated by {@link QualifierArgument}
* is mapped to the name specified by the value of {@link QualifierArgument}. If the value is not
* specified or is an empty string, then the element is mapped to an argument of the same name.
*
* @param contractAnnoElement the declaration of the contract annotation containing the elements
* @return map from the names of elements of {@code sourceArgumentNames} to the corresponding
* qualifier argument names
* @see QualifierArgument
*/
private Map<String, String> makeArgumentRenaming(Element contractAnnoElement) {
HashMap<String, String> argumentRenaming = new HashMap<>();
for (ExecutableElement meth :
ElementFilter.methodsIn(contractAnnoElement.getEnclosedElements())) {
AnnotationMirror argumentAnnotation =
factory.getDeclAnnotation(meth, QualifierArgument.class);
if (argumentAnnotation != null) {
String sourceName = meth.getSimpleName().toString();
String targetName =
AnnotationUtils.getElementValue(
argumentAnnotation, qualifierArgumentValueElement, String.class);
if (targetName == null || targetName.isEmpty()) {
targetName = sourceName;
}
argumentRenaming.put(sourceName, targetName);
}
}
return argumentRenaming;
}
}