blob: 2c91a2592385d324702362e6e25d5f5dc45cae95 [file] [log] [blame]
package org.checkerframework.framework.util;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import java.util.ArrayList;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.expression.FormalParameter;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.expression.LocalVariable;
import org.checkerframework.dataflow.expression.ThisReference;
import org.checkerframework.dataflow.expression.ViewpointAdaptJavaExpression;
import org.checkerframework.framework.source.SourceChecker;
import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
/**
* This interface is both a functional interface, see {@link #toJavaExpression(String)}, and also a
* collection of static methods that convert a string to a JavaExpression at common locations.
*
* <p>Some conversion routines merely do parsing. Other conversion routines parse and then transform
* the result of parsing into another {@code JavaExpression}; for all the static methods, this
* transformation is viewpoint-adaptation.
*
* <p>To parse a string "at a location" means to parse it as if it were written in an annotation
* that is written on that location.
*/
@FunctionalInterface
public interface StringToJavaExpression {
/**
* Convert a string to a {@link JavaExpression}. Returns {@code null} if no conversion exists.
*
* <p>Conversion includes parsing {@code stringExpr} to a {@code JavaExpression} and optionally
* transforming the result of parsing into another {@code JavaExpression}. An example of
* transformation is viewpoint adaptation.
*
* @param stringExpr a Java expression
* @return a {@code JavaExpression} or {@code null} if no conversion from {@code stringExpr}
* exists
* @throws JavaExpressionParseException if {@code stringExpr} cannot be parsed to a {@code
* JavaExpression}
*/
@Nullable JavaExpression toJavaExpression(String stringExpr) throws JavaExpressionParseException;
/**
* Parses a string to a {@link JavaExpression} as if it were written at {@code typeElement}.
*
* @param expression a Java expression to parse
* @param typeElement type element at which {@code expression} is parsed
* @param checker checker used to get the {@link
* javax.annotation.processing.ProcessingEnvironment} and current {@link
* com.sun.source.tree.CompilationUnitTree}
* @return a {@code JavaExpression} for {@code expression}
* @throws JavaExpressionParseException if {@code expression} cannot be parsed
*/
static JavaExpression atTypeDecl(
String expression, TypeElement typeElement, SourceChecker checker)
throws JavaExpressionParseException {
ThisReference thisReference = new ThisReference(typeElement.asType());
List<FormalParameter> parameters = null;
return JavaExpressionParseUtil.parse(
expression,
typeElement.asType(),
thisReference,
parameters,
null,
checker.getPathToCompilationUnit(),
checker.getProcessingEnvironment());
}
/**
* Parses a string to a {@link JavaExpression} as if it were written at {@code fieldElement}.
*
* @param expression a Java expression to parse
* @param fieldElement variable element at which {@code expression} is parsed
* @param checker checker used to get the {@link
* javax.annotation.processing.ProcessingEnvironment} and current {@link
* com.sun.source.tree.CompilationUnitTree}
* @return a {@code JavaExpression} for {@code expression}
* @throws JavaExpressionParseException if {@code expression} cannot be parsed
*/
static JavaExpression atFieldDecl(
String expression, VariableElement fieldElement, SourceChecker checker)
throws JavaExpressionParseException {
TypeMirror enclosingType = ElementUtils.enclosingTypeElement(fieldElement).asType();
ThisReference thisReference;
if (ElementUtils.isStatic(fieldElement)) {
// Can't use "this" on a static fieldElement
thisReference = null;
} else {
thisReference = new ThisReference(enclosingType);
}
List<FormalParameter> parameters = null;
return JavaExpressionParseUtil.parse(
expression,
enclosingType,
thisReference,
parameters,
null,
checker.getPathToCompilationUnit(),
checker.getProcessingEnvironment());
}
/**
* Parses a string to a {@link JavaExpression} as if it were written at {@code method}. The
* returned {@code JavaExpression} uses {@link FormalParameter}s to represent parameters. Use
* {@link #atMethodBody(String, MethodTree, SourceChecker)} if parameters should be {@link
* LocalVariable}s instead.
*
* @param expression a Java expression to parse
* @param method method element at which {@code expression} is parsed
* @param checker checker used to get the {@link
* javax.annotation.processing.ProcessingEnvironment} and current {@link
* com.sun.source.tree.CompilationUnitTree}
* @return a {@code JavaExpression} for {@code expression}
* @throws JavaExpressionParseException if {@code expression} cannot be parsed
*/
static JavaExpression atMethodDecl(
String expression, ExecutableElement method, SourceChecker checker)
throws JavaExpressionParseException {
TypeMirror enclosingType = ElementUtils.enclosingTypeElement(method).asType();
ThisReference thisReference;
if (ElementUtils.isStatic(method)) {
// Can't use "this" on a static method
thisReference = null;
} else {
thisReference = new ThisReference(enclosingType);
}
List<FormalParameter> parameters = JavaExpression.getFormalParameters(method);
return JavaExpressionParseUtil.parse(
expression,
enclosingType,
thisReference,
parameters,
null,
checker.getPathToCompilationUnit(),
checker.getProcessingEnvironment());
}
/**
* Parses a string to a {@link JavaExpression} as if it were written at {@code methodTree}. The
* returned {@code JavaExpression} uses {@link LocalVariable}s to represent parameters. Use {@link
* #atMethodDecl(String, ExecutableElement, SourceChecker)} if parameters should be {@link
* FormalParameter}s instead.
*
* @param expression a Java expression to parse
* @param methodTree method declaration tree at which {@code expression} is parsed
* @param checker checker used to get the {@link
* javax.annotation.processing.ProcessingEnvironment} and current {@link
* com.sun.source.tree.CompilationUnitTree}
* @return a {@code JavaExpression} for {@code expression}
* @throws JavaExpressionParseException if {@code expression} cannot be parsed
*/
static JavaExpression atMethodBody(
String expression, MethodTree methodTree, SourceChecker checker)
throws JavaExpressionParseException {
ExecutableElement ee = TreeUtils.elementFromDeclaration(methodTree);
JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker);
return javaExpr.atMethodBody(methodTree);
}
/**
* Parses a string as if it were written at the declaration of the invoked method and then
* viewpoint-adapts the result to the call site.
*
* @param expression a Java expression to parse
* @param methodInvocationTree method invocation tree
* @param checker checker used to get the {@link
* javax.annotation.processing.ProcessingEnvironment} and current {@link
* com.sun.source.tree.CompilationUnitTree}
* @return a {@code JavaExpression} for {@code expression}
* @throws JavaExpressionParseException if {@code expression} cannot be parsed
*/
static JavaExpression atMethodInvocation(
String expression, MethodInvocationTree methodInvocationTree, SourceChecker checker)
throws JavaExpressionParseException {
ExecutableElement ee = TreeUtils.elementFromUse(methodInvocationTree);
JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker);
return javaExpr.atMethodInvocation(methodInvocationTree);
}
/**
* Parses a string as if it were written at the declaration of the invoked method and then
* viewpoint-adapts the result to the call site.
*
* @param expression a Java expression to parse
* @param methodInvocationNode method invocation node
* @param checker checker used to get the {@link
* javax.annotation.processing.ProcessingEnvironment} and current {@link
* com.sun.source.tree.CompilationUnitTree}
* @return a {@code JavaExpression} for {@code expression}
* @throws JavaExpressionParseException if {@code expression} cannot be parsed
*/
static JavaExpression atMethodInvocation(
String expression, MethodInvocationNode methodInvocationNode, SourceChecker checker)
throws JavaExpressionParseException {
ExecutableElement ee = TreeUtils.elementFromUse(methodInvocationNode.getTree());
JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker);
return javaExpr.atMethodInvocation(methodInvocationNode);
}
/**
* Parses a string as if it were written at the declaration of the invoked constructor and then
* viewpoint-adapts the result to the call site.
*
* @param expression a Java expression to parse
* @param newClassTree constructor invocation
* @param checker checker used to get the {@link
* javax.annotation.processing.ProcessingEnvironment} and current {@link
* com.sun.source.tree.CompilationUnitTree}
* @return a {@code JavaExpression} for {@code expression}
* @throws JavaExpressionParseException if {@code expression} cannot be parsed
*/
static JavaExpression atConstructorInvocation(
String expression, NewClassTree newClassTree, SourceChecker checker)
throws JavaExpressionParseException {
ExecutableElement ee = TreeUtils.elementFromUse(newClassTree);
JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker);
return javaExpr.atConstructorInvocation(newClassTree);
}
/**
* uf found Parses a string as if it were written at the declaration of the field and then
* viewpoint-adapts the result to the use.
*
* @param expression a Java expression to parse
* @param fieldAccess the field access tree
* @param checker checker used to get the {@link
* javax.annotation.processing.ProcessingEnvironment} and current {@link
* com.sun.source.tree.CompilationUnitTree}
* @return a {@code JavaExpression} for {@code expression}
* @throws JavaExpressionParseException if {@code expression} cannot be parsed
*/
static JavaExpression atFieldAccess(
String expression, MemberSelectTree fieldAccess, SourceChecker checker)
throws JavaExpressionParseException {
Element ele = TreeUtils.elementFromUse(fieldAccess);
if (ele.getKind() != ElementKind.FIELD && ele.getKind() != ElementKind.ENUM_CONSTANT) {
throw new BugInCF("Expected a field, but found %s for %s", ele.getKind(), fieldAccess);
}
VariableElement fieldEle = (VariableElement) ele;
JavaExpression receiver = JavaExpression.fromTree(fieldAccess.getExpression());
JavaExpression javaExpr = StringToJavaExpression.atFieldDecl(expression, fieldEle, checker);
return javaExpr.atFieldAccess(receiver);
}
/**
* Parses a string as if it were written at one of the parameters of {@code lambdaTree}.
* Parameters of the lambda are expressed as {@link LocalVariable}s.
*
* @param expression a Java expression to parse
* @param lambdaTree the lambda tree
* @param parentPath path to the parent of {@code lambdaTree}; required because the expression can
* reference final local variables of the enclosing method
* @param checker checker used to get the {@link
* javax.annotation.processing.ProcessingEnvironment} and current {@link
* com.sun.source.tree.CompilationUnitTree}
* @return a {@code JavaExpression} for {@code expression}
* @throws JavaExpressionParseException if {@code expression} cannot be parsed
*/
static JavaExpression atLambdaParameter(
String expression,
LambdaExpressionTree lambdaTree,
TreePath parentPath,
SourceChecker checker)
throws JavaExpressionParseException {
TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(parentPath));
JavaExpression receiver = JavaExpression.getPseudoReceiver(parentPath, enclosingType);
// If receiver isn't a ThisReference, then the lambda is in a static context and "this"
// cannot be referenced in the expression.
ThisReference thisReference =
receiver instanceof ThisReference ? (ThisReference) receiver : null;
List<JavaExpression> paramsAsLocals = new ArrayList<>(lambdaTree.getParameters().size());
List<FormalParameter> parameters = new ArrayList<>(lambdaTree.getParameters().size());
int oneBasedIndex = 1;
for (VariableTree arg : lambdaTree.getParameters()) {
LocalVariable param = (LocalVariable) JavaExpression.fromVariableTree(arg);
paramsAsLocals.add(param);
parameters.add(new FormalParameter(oneBasedIndex, (VariableElement) param.getElement()));
oneBasedIndex++;
}
JavaExpression javaExpr =
JavaExpressionParseUtil.parse(
expression,
enclosingType,
thisReference,
parameters,
parentPath,
checker.getPathToCompilationUnit(),
checker.getProcessingEnvironment());
return ViewpointAdaptJavaExpression.viewpointAdapt(javaExpr, paramsAsLocals);
}
/**
* Parses a string as if it were written at {@code localVarPath}.
*
* @param expression a Java expression to parse
* @param localVarPath location at which {@code expression} is parsed
* @param checker checker used to get the {@link
* javax.annotation.processing.ProcessingEnvironment} and current {@link
* com.sun.source.tree.CompilationUnitTree}
* @return a {@code JavaExpression} for {@code expression}
* @throws JavaExpressionParseException if {@code expression} cannot be parsed
*/
static JavaExpression atPath(String expression, TreePath localVarPath, SourceChecker checker)
throws JavaExpressionParseException {
TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(localVarPath));
ThisReference thisReference =
TreePathUtil.isTreeInStaticScope(localVarPath) ? null : new ThisReference(enclosingType);
MethodTree methodTree = TreePathUtil.enclosingMethod(localVarPath);
if (methodTree == null) {
return JavaExpressionParseUtil.parse(
expression,
enclosingType,
thisReference,
null,
localVarPath,
checker.getPathToCompilationUnit(),
checker.getProcessingEnvironment());
}
ExecutableElement methodEle = TreeUtils.elementFromDeclaration(methodTree);
List<FormalParameter> parameters = JavaExpression.getFormalParameters(methodEle);
JavaExpression javaExpr =
JavaExpressionParseUtil.parse(
expression,
enclosingType,
thisReference,
parameters,
localVarPath,
checker.getPathToCompilationUnit(),
checker.getProcessingEnvironment());
List<JavaExpression> paramsAsLocals = JavaExpression.getParametersAsLocalVariables(methodEle);
return ViewpointAdaptJavaExpression.viewpointAdapt(javaExpr, paramsAsLocals);
}
}