All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.checkerframework.framework.util.DefaultContractsFromMethod Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 3.49.1-eisop1
Show newest version
package org.checkerframework.framework.util;

import org.checkerframework.checker.nullness.qual.Nullable;
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.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.TreeUtils;
import org.plumelib.util.IPair;

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;

/**
 * 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 DefaultContractsFromMethod implements ContractsFromMethod {

    /** The QualifierArgument.value field/element. */
    protected final ExecutableElement qualifierArgumentValueElement;

    /** The factory that this ContractsFromMethod is associated with. */
    protected final GenericAnnotatedTypeFactory atypeFactory;

    /**
     * Creates a ContractsFromMethod for the given factory.
     *
     * @param atypeFactory the type factory associated with the newly-created ContractsFromMethod
     */
    public DefaultContractsFromMethod(GenericAnnotatedTypeFactory atypeFactory) {
        this.atypeFactory = atypeFactory;
        qualifierArgumentValueElement =
                TreeUtils.getMethod(
                        QualifierArgument.class, "value", 0, atypeFactory.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}
     */
    @Override
    public Set getContracts(ExecutableElement executableElement) {
        Set 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}
     */
    @Override
    public Set getPreconditions(ExecutableElement executableElement) {
        return getContracts(
                executableElement, Contract.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}
     */
    @Override
    public Set getPostconditions(ExecutableElement executableElement) {
        return getContracts(
                executableElement, Contract.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}
     */
    @Override
    public Set getConditionalPostconditions(
            ExecutableElement methodElement) {
        return getContracts(
                methodElement,
                Contract.Kind.CONDITIONALPOSTCONDITION,
                Contract.ConditionalPostcondition.class);
    }

    /// Helper methods

    /**
     * Returns the contracts (of a particular kind) on method or constructor {@code
     * executableElement}.
     *
     * @param  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  Set getContracts(
            ExecutableElement executableElement, Contract.Kind kind, Class clazz) {
        Set result = new LinkedHashSet<>();
        // Check for a single framework-defined contract annotation.
        AnnotationMirror frameworkContractAnno =
                atypeFactory.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 =
                atypeFactory.getDeclAnnotation(executableElement, kind.frameworkContractListClass);
        if (frameworkContractListAnno != null) {
            List frameworkContractAnnoList =
                    atypeFactory.getContractListValues(frameworkContractListAnno);
            for (AnnotationMirror a : frameworkContractAnnoList) {
                result.addAll(getContract(kind, a, clazz));
            }
        }

        // Check for type-system specific annotations.
        List> declAnnotations =
                atypeFactory.getDeclAnnotationWithMetaAnnotation(
                        executableElement, kind.metaAnnotation);
        for (IPair 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 expressions = atypeFactory.getContractExpressions(kind, anno);
            Collections.sort(expressions);
            Boolean ensuresQualifierIfResult = atypeFactory.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  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  Set getContract(
            Contract.Kind kind, @Nullable AnnotationMirror contractAnnotation, Class clazz) {
        if (contractAnnotation == null) {
            return Collections.emptySet();
        }

        AnnotationMirror enforcedQualifier =
                getQualifierEnforcedByContractAnnotation(contractAnnotation);
        if (enforcedQualifier == null) {
            return Collections.emptySet();
        }

        List expressions = atypeFactory.getContractExpressions(contractAnnotation);
        Collections.sort(expressions);

        Boolean ensuresQualifierIfResult =
                atypeFactory.getEnsuresQualifierIfResult(kind, contractAnnotation);

        Set 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 @Nullable 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 @Nullable AnnotationMirror getQualifierEnforcedByContractAnnotation(
            AnnotationMirror contractAnno, AnnotationMirror argumentAnno) {

        Map 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.
     *
     * 

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 @Nullable AnnotationMirror getQualifierEnforcedByContractAnnotation( AnnotationMirror contractAnno, @Nullable AnnotationMirror argumentAnno, @Nullable Map 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(atypeFactory.getElementUtils(), c); } else { AnnotationBuilder builder = new AnnotationBuilder(atypeFactory.getProcessingEnv(), c); builder.copyRenameElementValuesFromAnnotation(argumentAnno, argumentRenaming); anno = builder.build(); } if (atypeFactory.isSupportedQualifier(anno)) { return anno; } else { AnnotationMirror aliasedAnno = atypeFactory.canonicalAnnotation(anno); if (atypeFactory.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}. * *

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 makeArgumentRenaming(Element contractAnnoElement) { HashMap argumentRenaming = new HashMap<>(); for (ExecutableElement meth : ElementFilter.methodsIn(contractAnnoElement.getEnclosedElements())) { AnnotationMirror argumentAnnotation = atypeFactory.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; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy