blob: 314a59d9b08d8143ca39bbd2ebb3273002c0b79a [file] [log] [blame]
package org.checkerframework.checker.mustcall;
import java.util.ArrayList;
import java.util.List;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import org.checkerframework.checker.mustcall.qual.CreatesObligation;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.expression.Unknown;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
import org.checkerframework.framework.util.StringToJavaExpression;
import org.checkerframework.javacutil.AnnotationUtils;
/**
* This interface should be implemented by all type factories that can provide the ExecutableElement
* needed to call {@link AnnotationUtils#getElementValueArray} when {@link
* #getCreatesObligationExpressions(MethodInvocationNode, GenericAnnotatedTypeFactory,
* CreatesObligationElementSupplier)} is called. This interface is needed so any type factory with
* these elements can be used to call that method, not just the MustCallAnnotatedTypeFactory (in
* particular, the consistency checker needs to be able to call that method with both the
* ObjectConstruction/CalledMethods type factory and the MustCall type factory).
*/
public interface CreatesObligationElementSupplier {
/**
* Returns the CreatesObligation.value field/element.
*
* @return the CreatesObligation.value field/element
*/
ExecutableElement getCreatesObligationValueElement();
/**
* Returns the CreatesObligation.List.value field/element.
*
* @return the CreatesObligation.List.value field/element
*/
ExecutableElement getCreatesObligationListValueElement();
/**
* Returns the arguments of the @CreatesObligation annotation on the invoked method, as
* JavaExpressions. Returns the empty set if the given method has no @CreatesObligation
* annotation.
*
* <p>If any expression is unparseable, this method reports an error and returns the empty set.
*
* @param n a method invocation
* @param atypeFactory the type factory to report errors and parse the expression string
* @param supplier a type factory that can supply the executable elements for CreatesObligation
* and CreatesObligation.List's value elements. Usually, you should just pass atypeFactory
* again. The arguments are different so that the given type factory's adherence to both
* protocols are checked by the type system.
* @return the arguments of the method's @CreatesObligation annotation, or an empty list
*/
static List<JavaExpression> getCreatesObligationExpressions(
MethodInvocationNode n,
GenericAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory,
CreatesObligationElementSupplier supplier) {
AnnotationMirror createsObligationList =
atypeFactory.getDeclAnnotation(n.getTarget().getMethod(), CreatesObligation.List.class);
List<JavaExpression> results = new ArrayList<>(1);
if (createsObligationList != null) {
// Handle a set of CreatesObligation annotations.
List<AnnotationMirror> createsObligations =
AnnotationUtils.getElementValueArray(
createsObligationList,
supplier.getCreatesObligationListValueElement(),
AnnotationMirror.class);
for (AnnotationMirror co : createsObligations) {
JavaExpression expr = getCreatesObligationExpression(co, n, atypeFactory, supplier);
if (expr != null && !results.contains(expr)) {
results.add(expr);
}
}
}
AnnotationMirror createsObligation =
atypeFactory.getDeclAnnotation(n.getTarget().getMethod(), CreatesObligation.class);
if (createsObligation != null) {
JavaExpression expr =
getCreatesObligationExpression(createsObligation, n, atypeFactory, supplier);
if (expr != null && !results.contains(expr)) {
results.add(expr);
}
}
return results;
}
/**
* Parses a single CreatesObligation annotation. Clients should use {@link
* #getCreatesObligationExpressions(MethodInvocationNode, GenericAnnotatedTypeFactory,
* CreatesObligationElementSupplier)}, which handles the possibility of multiple such annotations,
* instead.
*
* @param createsObligation a @CreatesObligation annotation
* @param n the invocation of a reset method
* @param atypeFactory the type factory
* @param supplier a type factory that can supply the executable elements for CreatesObligation
* and CreatesObligation.List's value elements. Usually, you should just pass atypeFactory
* again. The arguments are different so that the given type factory's adherence to both
* protocols are checked by the type system.
* @return the Java expression representing the target, or null if the target is unparseable
*/
static @Nullable JavaExpression getCreatesObligationExpression(
AnnotationMirror createsObligation,
MethodInvocationNode n,
GenericAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory,
CreatesObligationElementSupplier supplier) {
// Unfortunately, there is no way to avoid passing the default string "this" here. The default
// must be hard-coded into the client, such as here. That is the price for the efficiency of not
// having to query the annotation definition (such queries are expensive).
String targetStrWithoutAdaptation =
AnnotationUtils.getElementValue(
createsObligation, supplier.getCreatesObligationValueElement(), String.class, "this");
// TODO: find a way to also check if the target is a known tempvar, and if so return that. That
// should improve the quality of the error messages we give.
JavaExpression targetExpr;
try {
targetExpr =
StringToJavaExpression.atMethodInvocation(
targetStrWithoutAdaptation, n, atypeFactory.getChecker());
if (targetExpr instanceof Unknown) {
issueUnparseableError(n, atypeFactory, targetStrWithoutAdaptation);
return null;
}
} catch (JavaExpressionParseException e) {
issueUnparseableError(n, atypeFactory, targetStrWithoutAdaptation);
return null;
}
return targetExpr;
}
/**
* Issues a createsobligation.target.unparseable error.
*
* @param n the node
* @param atypeFactory the type factory to use to issue the error
* @param unparseable the unparseable string
*/
static void issueUnparseableError(
MethodInvocationNode n,
GenericAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory,
String unparseable) {
atypeFactory
.getChecker()
.reportError(
n.getTree(),
"createsobligation.target.unparseable",
n.getTarget().getMethod().getSimpleName(),
unparseable);
}
}