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

org.checkerframework.framework.util.JavaExpressionParseUtil 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.44.0
Show newest version
package org.checkerframework.framework.util;

import com.github.javaparser.ParseProblemException;
import com.github.javaparser.ParserConfiguration.LanguageLevel;
import com.github.javaparser.ast.ArrayCreationLevel;
import com.github.javaparser.ast.expr.ArrayAccessExpr;
import com.github.javaparser.ast.expr.ArrayCreationExpr;
import com.github.javaparser.ast.expr.BinaryExpr;
import com.github.javaparser.ast.expr.BooleanLiteralExpr;
import com.github.javaparser.ast.expr.CharLiteralExpr;
import com.github.javaparser.ast.expr.ClassExpr;
import com.github.javaparser.ast.expr.DoubleLiteralExpr;
import com.github.javaparser.ast.expr.EnclosedExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.FieldAccessExpr;
import com.github.javaparser.ast.expr.IntegerLiteralExpr;
import com.github.javaparser.ast.expr.LongLiteralExpr;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.expr.NullLiteralExpr;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import com.github.javaparser.ast.expr.SuperExpr;
import com.github.javaparser.ast.expr.ThisExpr;
import com.github.javaparser.ast.expr.UnaryExpr;
import com.github.javaparser.ast.type.Type;
import com.github.javaparser.ast.visitor.GenericVisitorWithDefaults;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.PackageSymbol;
import com.sun.tools.javac.code.Type.ArrayType;
import com.sun.tools.javac.code.Type.ClassType;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.expression.ArrayAccess;
import org.checkerframework.dataflow.expression.ArrayCreation;
import org.checkerframework.dataflow.expression.BinaryOperation;
import org.checkerframework.dataflow.expression.ClassName;
import org.checkerframework.dataflow.expression.FieldAccess;
import org.checkerframework.dataflow.expression.FormalParameter;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.expression.LocalVariable;
import org.checkerframework.dataflow.expression.MethodCall;
import org.checkerframework.dataflow.expression.ThisReference;
import org.checkerframework.dataflow.expression.UnaryOperation;
import org.checkerframework.dataflow.expression.ValueLiteral;
import org.checkerframework.framework.source.DiagMessage;
import org.checkerframework.framework.util.dependenttypes.DependentTypesError;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.Resolver;
import org.checkerframework.javacutil.TypesUtils;
import org.checkerframework.javacutil.trees.TreeBuilder;
import org.plumelib.util.CollectionsPlume;
import org.plumelib.util.StringsPlume;

/**
 * Helper methods to parse a string that represents a restricted Java expression.
 *
 * @checker_framework.manual #java-expressions-as-arguments Writing Java expressions as annotation
 *     arguments
 * @checker_framework.manual #dependent-types Annotations whose argument is a Java expression
 *     (dependent type annotations)
 */
public class JavaExpressionParseUtil {

  /** Regular expression for a formal parameter use. */
  protected static final String PARAMETER_REGEX = "#([1-9][0-9]*)";

  /**
   * Anchored pattern for a formal parameter use; matches a string that is exactly a formal
   * parameter use.
   */
  protected static final Pattern ANCHORED_PARAMETER_PATTERN =
      Pattern.compile("^" + PARAMETER_REGEX + "$");

  /**
   * Unanchored pattern for a formal parameter use; can be used to find all formal parameter uses.
   */
  protected static final Pattern UNANCHORED_PARAMETER_PATTERN = Pattern.compile(PARAMETER_REGEX);

  /**
   * Parsable replacement for formal parameter references. It is parsable because it is a Java
   * identifier.
   */
  private static final String PARAMETER_PREFIX = "_param_";

  /** The length of {@link #PARAMETER_PREFIX}. */
  private static final int PARAMETER_PREFIX_LENGTH = PARAMETER_PREFIX.length();

  /** A pattern that matches the start of a formal parameter in "#2" syntax. */
  private static Pattern FORMAL_PARAMETER = Pattern.compile("#(\\d)");

  /** The replacement for a formal parameter in "#2" syntax. */
  private static final String PARAMETER_REPLACEMENT = PARAMETER_PREFIX + "$1";

  /**
   * Parses a string to a {@link JavaExpression}.
   *
   * 

For most uses, clients should call one of the static methods in {@link * StringToJavaExpression} rather than calling this method directly. * * @param expression the string expression to parse * @param enclosingType type of the class that encloses the JavaExpression * @param thisReference the JavaExpression to which to parse "this", or null if "this" should not * appear in the expression * @param parameters list of JavaExpressions to which to parse formal parameter references such as * "#2", or null if formal parameter references should not appear in the expression * @param localVarPath if non-null, the expression is parsed as if it were written at this * location; affects only parsing of local variables * @param pathToCompilationUnit required to use the underlying Javac API * @param env the processing environment * @return {@code expression} as a {@code JavaExpression} * @throws JavaExpressionParseException if the string cannot be parsed */ public static JavaExpression parse( String expression, TypeMirror enclosingType, @Nullable ThisReference thisReference, @Nullable List parameters, @Nullable TreePath localVarPath, TreePath pathToCompilationUnit, ProcessingEnvironment env) throws JavaExpressionParseException { // Use the current source version to parse with because a JavaExpression could refer to a // variable named "var", which is a keyword in Java 10 and later. LanguageLevel currentSourceVersion = JavaParserUtil.getCurrentSourceVersion(env); String expressionWithParameterNames = StringsPlume.replaceAll(expression, FORMAL_PARAMETER, PARAMETER_REPLACEMENT); Expression expr; try { expr = JavaParserUtil.parseExpression(expressionWithParameterNames, currentSourceVersion); } catch (ParseProblemException e) { String extra = "."; if (!e.getProblems().isEmpty()) { String message = e.getProblems().get(0).getMessage(); int newLine = message.indexOf(System.lineSeparator()); if (newLine != -1) { message = message.substring(0, newLine); } extra = ". Error message: " + message; } throw constructJavaExpressionParseError(expression, "the expression did not parse" + extra); } JavaExpression result = ExpressionToJavaExpressionVisitor.convert( expr, enclosingType, thisReference, parameters, localVarPath, pathToCompilationUnit, env); if (result instanceof ClassName && !expression.endsWith(".class")) { throw constructJavaExpressionParseError( expression, String.format( "a class name cannot terminate a Java expression string, where result=%s [%s]", result, result.getClass())); } return result; } /** * A visitor class that converts a JavaParser {@link Expression} to a {@link JavaExpression}. This * class does not viewpoint-adapt the expression. */ private static class ExpressionToJavaExpressionVisitor extends GenericVisitorWithDefaults { /** * The underlying javac API used to convert from Strings to Elements requires a tree path even * when the information could be deduced from elements alone. So use the path to the current * CompilationUnit. */ private final TreePath pathToCompilationUnit; /** If non-null, the expression is parsed as if it were written at this location. */ private final @Nullable TreePath localVarPath; /** The processing environment. */ private final ProcessingEnvironment env; /** The resolver. Computed from the environment, but lazily initialized. */ private @MonotonicNonNull Resolver resolver = null; /** The type utilities. */ private final Types types; /** The java.lang.String type. */ private final TypeMirror stringTypeMirror; /** The enclosing type. Used to look up unqualified method, field, and class names. */ private final TypeMirror enclosingType; /** * The expression to use for "this". If {@code null}, a parse error will be thrown if "this" * appears in the expression. */ private final @Nullable ThisReference thisReference; /** * For each formal parameter, the expression to which to parse it. For example, the second * (index 1) element of the list is what "#2" parses to. If this field is {@code null}, a parse * error will be thrown if "#2" appears in the expression. */ private final @Nullable List parameters; /** * Create a new ExpressionToJavaExpressionVisitor. * * @param enclosingType type of the class that encloses the JavaExpression * @param thisReference JavaExpression to which to parse "this", or null if "this" should not * appear in the expression * @param parameters list of JavaExpressions to which to parse a formal parameter reference such * as "#2", or null if parameters should not appear in the expression * @param localVarPath if non-null, the expression is parsed as if it were written at this * location * @param pathToCompilationUnit required to use the underlying Javac API * @param env the processing environment */ private ExpressionToJavaExpressionVisitor( TypeMirror enclosingType, @Nullable ThisReference thisReference, @Nullable List parameters, @Nullable TreePath localVarPath, TreePath pathToCompilationUnit, ProcessingEnvironment env) { this.pathToCompilationUnit = pathToCompilationUnit; this.localVarPath = localVarPath; this.env = env; this.types = env.getTypeUtils(); this.stringTypeMirror = env.getElementUtils().getTypeElement("java.lang.String").asType(); this.enclosingType = enclosingType; this.thisReference = thisReference; this.parameters = parameters; } /** * Converts a JavaParser {@link Expression} to a {@link JavaExpression}. * * @param expr the JavaParser {@link Expression} to convert * @param enclosingType type of the class that encloses the JavaExpression * @param thisReference JavaExpression to which to parse "this", or null if "this" should not * appear in the expression * @param parameters list of JavaExpressions to which to parse parameters, or null if parameters * should not appear in the expression * @param localVarPath if non-null, the expression is parsed as if it were written at this * location * @param pathToCompilationUnit required to use the underlying Javac API * @param env the processing environment * @return {@code expr} as a {@code JavaExpression} * @throws JavaExpressionParseException if {@code expr} cannot be converted to a {@code * JavaExpression} */ public static JavaExpression convert( Expression expr, TypeMirror enclosingType, @Nullable ThisReference thisReference, @Nullable List parameters, @Nullable TreePath localVarPath, TreePath pathToCompilationUnit, ProcessingEnvironment env) throws JavaExpressionParseException { try { return expr.accept( new ExpressionToJavaExpressionVisitor( enclosingType, thisReference, parameters, localVarPath, pathToCompilationUnit, env), null); } catch (ParseRuntimeException e) { // Convert unchecked to checked exception. Visitor methods can't throw checked // exceptions. They override the methods in the superclass, and a checked exception // would change the method signature. throw e.getCheckedException(); } } /** * Initializes the {@code resolver} field if necessary. Does nothing on invocations after the * first. */ private void setResolverField() { if (resolver == null) { resolver = new Resolver(env); } } /** If the expression is not supported, throw a {@link ParseRuntimeException} by default. */ @Override public JavaExpression defaultAction(com.github.javaparser.ast.Node n, Void aVoid) { throw new ParseRuntimeException( constructJavaExpressionParseError( n.toString(), n.getClass() + " is not a supported expression")); } @Override public JavaExpression visit(NullLiteralExpr expr, Void aVoid) { return new ValueLiteral(types.getNullType(), (Object) null); } @Override public JavaExpression visit(IntegerLiteralExpr expr, Void aVoid) { return new ValueLiteral(types.getPrimitiveType(TypeKind.INT), expr.asNumber()); } @Override public JavaExpression visit(LongLiteralExpr expr, Void aVoid) { return new ValueLiteral(types.getPrimitiveType(TypeKind.LONG), expr.asNumber()); } @Override public JavaExpression visit(CharLiteralExpr expr, Void aVoid) { return new ValueLiteral(types.getPrimitiveType(TypeKind.CHAR), expr.asChar()); } @Override public JavaExpression visit(DoubleLiteralExpr expr, Void aVoid) { return new ValueLiteral(types.getPrimitiveType(TypeKind.DOUBLE), expr.asDouble()); } @Override public JavaExpression visit(StringLiteralExpr expr, Void aVoid) { return new ValueLiteral(stringTypeMirror, expr.asString()); } @Override public JavaExpression visit(BooleanLiteralExpr expr, Void aVoid) { return new ValueLiteral(types.getPrimitiveType(TypeKind.BOOLEAN), expr.getValue()); } @Override public JavaExpression visit(ThisExpr n, Void aVoid) { if (thisReference == null) { throw new ParseRuntimeException( constructJavaExpressionParseError("this", "\"this\" isn't allowed here")); } return thisReference; } @Override public JavaExpression visit(SuperExpr n, Void aVoid) { // super literal TypeMirror superclass = TypesUtils.getSuperclass(enclosingType, types); if (superclass == null) { throw new ParseRuntimeException( constructJavaExpressionParseError("super", enclosingType + " has no superclass")); } return new ThisReference(superclass); } // expr is an expression in parentheses. @Override public JavaExpression visit(EnclosedExpr expr, Void aVoid) { return expr.getInner().accept(this, null); } @Override public JavaExpression visit(ArrayAccessExpr expr, Void aVoid) { JavaExpression array = expr.getName().accept(this, null); TypeMirror arrayType = array.getType(); if (arrayType.getKind() != TypeKind.ARRAY) { throw new ParseRuntimeException( constructJavaExpressionParseError( expr.toString(), String.format( "expected an array, found %s of type %s [%s]", array, arrayType, arrayType.getKind()))); } TypeMirror componentType = ((ArrayType) arrayType).getComponentType(); JavaExpression index = expr.getIndex().accept(this, null); return new ArrayAccess(componentType, array, index); } // expr is an identifier with no dots in its name. @Override public JavaExpression visit(NameExpr expr, Void aVoid) { String s = expr.getNameAsString(); setResolverField(); // Formal parameter, using "#2" syntax. JavaExpression parameter = getParameterJavaExpression(s); if (parameter != null) { // A parameter is a local variable, but it can be referenced outside of local scope // (at the method scope) using the special #NN syntax. return parameter; } // Local variable or parameter. if (localVarPath != null) { // Attempt to match a local variable within the scope of the // given path before attempting to match a field. VariableElement varElem = resolver.findLocalVariableOrParameter(s, localVarPath); if (varElem != null) { return new LocalVariable(varElem); } } // Field access JavaExpression fieldAccessReceiver; if (thisReference != null) { fieldAccessReceiver = thisReference; } else { fieldAccessReceiver = new ClassName(enclosingType); } FieldAccess fieldAccess = getIdentifierAsFieldAccess(fieldAccessReceiver, s); if (fieldAccess != null) { return fieldAccess; } if (localVarPath != null) { Element classElem = resolver.findClass(s, localVarPath); TypeMirror classType = ElementUtils.getType(classElem); if (classType != null) { return new ClassName(classType); } } ClassName classType = getIdentifierAsUnqualifiedClassName(s); if (classType != null) { return classType; } // Err if a formal parameter name is used, instead of the "#2" syntax. if (parameters != null) { for (int i = 0; i < parameters.size(); i++) { Element varElt = parameters.get(i).getElement(); if (varElt.getSimpleName().contentEquals(s)) { throw new ParseRuntimeException( constructJavaExpressionParseError( s, String.format(DependentTypesError.FORMAL_PARAM_NAME_STRING, i + 1, s))); } } } throw new ParseRuntimeException(constructJavaExpressionParseError(s, "identifier not found")); } /** * If {@code s} a parameter expressed using the {@code #NN} syntax, then returns a * JavaExpression for the given parameter; that is, returns an element of {@code parameters}. * Otherwise, returns {@code null}. * * @param s a String that starts with PARAMETER_PREFIX * @return the JavaExpression for the given parameter or {@code null} if {@code s} is not a * parameter */ private @Nullable JavaExpression getParameterJavaExpression(String s) { if (!s.startsWith(PARAMETER_PREFIX)) { return null; } if (parameters == null) { throw new ParseRuntimeException( constructJavaExpressionParseError(s, "no parameters found")); } int idx = Integer.parseInt(s.substring(PARAMETER_PREFIX_LENGTH)); if (idx == 0) { throw new ParseRuntimeException( constructJavaExpressionParseError( "#0", "Use \"this\" for the receiver or \"#1\" for the first formal parameter")); } if (idx > parameters.size()) { throw new ParseRuntimeException( new JavaExpressionParseException( "flowexpr.parse.index.too.big", Integer.toString(idx))); } return parameters.get(idx - 1); } /** * If {@code identifier} is the simple class name of any inner class of {@code type}, return the * {@link ClassName} for the inner class. If not, return null. * * @param type type to search for {@code identifier} * @param identifier possible simple class name * @return the {@code ClassName} for {@code identifier}, or null if it is not a simple class * name */ protected @Nullable ClassName getIdentifierAsInnerClassName( TypeMirror type, String identifier) { if (type.getKind() != TypeKind.DECLARED) { return null; } Element outerClass = ((DeclaredType) type).asElement(); for (Element memberElement : outerClass.getEnclosedElements()) { if (!(memberElement.getKind().isClass() || memberElement.getKind().isInterface())) { continue; } if (memberElement.getSimpleName().contentEquals(identifier)) { return new ClassName(ElementUtils.getType(memberElement)); } } return null; } /** * If {@code identifier} is a class name with that can be referenced using only its simple name * within {@code enclosingType}, return the {@link ClassName} for the class. If not, return * null. * *

{@code identifier} may be * *

    *
  1. the simple name of {@code type}. *
  2. the simple name of a class declared in {@code type} or in an enclosing type of {@code * type}. *
  3. the simple name of a class in the java.lang package. *
  4. the simple name of a class in the unnamed package. *
* * @param identifier possible class name * @return the {@code ClassName} for {@code identifier}, or null if it is not a class name */ protected @Nullable ClassName getIdentifierAsUnqualifiedClassName(String identifier) { // Is identifier an inner class of enclosingType or of any enclosing class of enclosingType? TypeMirror searchType = enclosingType; while (searchType.getKind() == TypeKind.DECLARED) { DeclaredType searchDeclaredType = (DeclaredType) searchType; if (searchDeclaredType.asElement().getSimpleName().contentEquals(identifier)) { return new ClassName(searchType); } ClassName className = getIdentifierAsInnerClassName(searchType, identifier); if (className != null) { return className; } searchType = getTypeOfEnclosingClass(searchDeclaredType); } if (enclosingType.getKind() == TypeKind.DECLARED) { // Is identifier in the same package as this? PackageSymbol packageSymbol = (PackageSymbol) ElementUtils.enclosingPackage(((DeclaredType) enclosingType).asElement()); ClassSymbol classSymbol = resolver.findClassInPackage(identifier, packageSymbol, pathToCompilationUnit); if (classSymbol != null) { return new ClassName(classSymbol.asType()); } } // Is identifier a simple name for a class in java.lang? Symbol.PackageSymbol packageSymbol = resolver.findPackage("java.lang", pathToCompilationUnit); if (packageSymbol == null) { throw new BugInCF("Can't find java.lang package."); } ClassSymbol classSymbol = resolver.findClassInPackage(identifier, packageSymbol, pathToCompilationUnit); if (classSymbol != null) { return new ClassName(classSymbol.asType()); } // Is identifier a class in the unnamed package? Element classElem = resolver.findClass(identifier, pathToCompilationUnit); if (classElem != null) { PackageElement pkg = ElementUtils.enclosingPackage(classElem); if (pkg != null && pkg.isUnnamed()) { TypeMirror classType = ElementUtils.getType(classElem); if (classType != null) { return new ClassName(classType); } } } return null; } /** * Return the {@link FieldAccess} expression for the field with name {@code identifier} accessed * via {@code receiverExpr}. If no such field exists, then {@code null} is returned. * * @param receiverExpr the receiver of the field access; the expression used to access the field * @param identifier possibly a field name * @return a field access, or null if {@code identifier} is not a field that can be accessed via * {@code receiverExpr} */ protected @Nullable FieldAccess getIdentifierAsFieldAccess( JavaExpression receiverExpr, String identifier) { // Find the field element. TypeMirror enclosingTypeOfField = receiverExpr.getType(); VariableElement fieldElem; if (identifier.equals("length") && enclosingTypeOfField.getKind() == TypeKind.ARRAY) { fieldElem = resolver.findField(identifier, enclosingTypeOfField, pathToCompilationUnit); if (fieldElem == null) { throw new BugInCF("length field not found for type %s", enclosingTypeOfField); } } else { fieldElem = null; // Search for field in each enclosing class. while (enclosingTypeOfField.getKind() == TypeKind.DECLARED) { fieldElem = resolver.findField(identifier, enclosingTypeOfField, pathToCompilationUnit); if (fieldElem != null) { break; } enclosingTypeOfField = getTypeOfEnclosingClass((DeclaredType) enclosingTypeOfField); } if (fieldElem == null) { // field not found. return null; } } // Construct a FieldAccess expression. if (ElementUtils.isStatic(fieldElem)) { Element classElem = fieldElem.getEnclosingElement(); JavaExpression staticClassReceiver = new ClassName(ElementUtils.getType(classElem)); return new FieldAccess(staticClassReceiver, fieldElem); } // fieldElem is an instance field. if (receiverExpr instanceof ClassName) { throw new ParseRuntimeException( constructJavaExpressionParseError( fieldElem.getSimpleName().toString(), "a non-static field cannot have a class name as a receiver.")); } // There are two possibilities, captured by local variable fieldDeclaredInReceiverType: // * true: it's an instance field declared in the type (or supertype) of receiverExpr. // * false: it's an instance field declared in an enclosing type of receiverExpr. @SuppressWarnings("interning:not.interned") // Checking for exact object boolean fieldDeclaredInReceiverType = enclosingTypeOfField == receiverExpr.getType(); if (fieldDeclaredInReceiverType) { TypeMirror fieldType = ElementUtils.getType(fieldElem); return new FieldAccess(receiverExpr, fieldType, fieldElem); } else { if (!(receiverExpr instanceof ThisReference)) { String msg = String.format( "%s is declared in an outer type of the type of the receiver expression, %s.", identifier, receiverExpr); throw new ParseRuntimeException(constructJavaExpressionParseError(identifier, msg)); } TypeElement receiverTypeElement = TypesUtils.getTypeElement(receiverExpr.getType()); if (receiverTypeElement == null || ElementUtils.isStatic(receiverTypeElement)) { String msg = String.format("%s is a non-static field declared in an outer type this.", identifier); throw new ParseRuntimeException(constructJavaExpressionParseError(identifier, msg)); } JavaExpression locationOfField = new ThisReference(enclosingTypeOfField); return new FieldAccess(locationOfField, fieldElem); } } @Override public JavaExpression visit(MethodCallExpr expr, Void aVoid) { setResolverField(); JavaExpression receiverExpr; if (expr.getScope().isPresent()) { receiverExpr = expr.getScope().get().accept(this, null); expr = expr.removeScope(); } else if (thisReference != null) { receiverExpr = thisReference; } else { receiverExpr = new ClassName(enclosingType); } String methodName = expr.getNameAsString(); // parse argument list List arguments = CollectionsPlume.mapList(argument -> argument.accept(this, null), expr.getArguments()); ExecutableElement methodElement; try { methodElement = getMethodElement( methodName, receiverExpr.getType(), pathToCompilationUnit, arguments, resolver); } catch (JavaExpressionParseException e) { throw new ParseRuntimeException(e); } // Box any arguments that require it. for (int i = 0; i < arguments.size(); i++) { VariableElement parameter = methodElement.getParameters().get(i); TypeMirror parameterType = parameter.asType(); JavaExpression argument = arguments.get(i); TypeMirror argumentType = argument.getType(); // is boxing necessary? if (TypesUtils.isBoxedPrimitive(parameterType) && TypesUtils.isPrimitive(argumentType)) { // boxing is necessary MethodSymbol valueOfMethod = TreeBuilder.getValueOfMethod(env, parameterType); JavaExpression boxedParam = new MethodCall( parameterType, valueOfMethod, new ClassName(parameterType), Collections.singletonList(argument)); arguments.set(i, boxedParam); } } // Build the MethodCall expression object. if (ElementUtils.isStatic(methodElement)) { Element classElem = methodElement.getEnclosingElement(); JavaExpression staticClassReceiver = new ClassName(ElementUtils.getType(classElem)); return new MethodCall( ElementUtils.getType(methodElement), methodElement, staticClassReceiver, arguments); } else { if (receiverExpr instanceof ClassName) { throw new ParseRuntimeException( constructJavaExpressionParseError( expr.toString(), "a non-static method call cannot have a class name as a receiver")); } TypeMirror methodType = TypesUtils.substituteMethodReturnType(methodElement, receiverExpr.getType(), env); return new MethodCall(methodType, methodElement, receiverExpr, arguments); } } /** * Returns the ExecutableElement for a method, or throws an exception. * *

(This method takes into account autoboxing.) * * @param methodName the method name * @param receiverType the receiver type * @param pathToCompilationUnit the path to the compilation unit * @param arguments the arguments * @param resolver the resolver * @return the ExecutableElement for a method, or throws an exception * @throws JavaExpressionParseException if the string cannot be parsed as a method name */ private ExecutableElement getMethodElement( String methodName, TypeMirror receiverType, TreePath pathToCompilationUnit, List arguments, Resolver resolver) throws JavaExpressionParseException { List argumentTypes = CollectionsPlume.mapList(JavaExpression::getType, arguments); if (receiverType.getKind() == TypeKind.ARRAY) { ExecutableElement element = resolver.findMethod(methodName, receiverType, pathToCompilationUnit, argumentTypes); if (element == null) { throw constructJavaExpressionParseError(methodName, "no such method"); } return element; } // Search for method in each enclosing class. while (receiverType.getKind() == TypeKind.DECLARED) { ExecutableElement element = resolver.findMethod(methodName, receiverType, pathToCompilationUnit, argumentTypes); if (element != null) { return element; } receiverType = getTypeOfEnclosingClass((DeclaredType) receiverType); } // Method not found. throw constructJavaExpressionParseError(methodName, "no such method"); } // expr is a field access, a fully qualified class name, or a class name qualified with // another class name (e.g. {@code OuterClass.InnerClass}) @Override public JavaExpression visit(FieldAccessExpr expr, Void aVoid) { setResolverField(); Expression scope = expr.getScope(); String name = expr.getNameAsString(); // Check for fully qualified class name. Symbol.PackageSymbol packageSymbol = resolver.findPackage(scope.toString(), pathToCompilationUnit); if (packageSymbol != null) { ClassSymbol classSymbol = resolver.findClassInPackage(name, packageSymbol, pathToCompilationUnit); if (classSymbol != null) { return new ClassName(classSymbol.asType()); } throw new ParseRuntimeException( constructJavaExpressionParseError( expr.toString(), "could not find class " + expr.getNameAsString() + " in package " + scope.toString())); } JavaExpression receiver = scope.accept(this, null); // Check for field access expression. FieldAccess fieldAccess = getIdentifierAsFieldAccess(receiver, name); if (fieldAccess != null) { return fieldAccess; } // Check for inner class. ClassName classType = getIdentifierAsInnerClassName(receiver.getType(), name); if (classType != null) { return classType; } throw new ParseRuntimeException( constructJavaExpressionParseError( name, String.format("field or class %s not found in %s", name, receiver))); } // expr is a Class literal @Override public JavaExpression visit(ClassExpr expr, Void aVoid) { TypeMirror result = convertTypeToTypeMirror(expr.getType()); if (result == null) { throw new ParseRuntimeException( constructJavaExpressionParseError( expr.toString(), "it is an unparsable class literal")); } return new ClassName(result); } @Override public JavaExpression visit(ArrayCreationExpr expr, Void aVoid) { List dimensions = CollectionsPlume.mapList( (ArrayCreationLevel dimension) -> dimension.getDimension().isPresent() ? dimension.getDimension().get().accept(this, aVoid) : null, expr.getLevels()); List initializers; if (expr.getInitializer().isPresent()) { initializers = CollectionsPlume.mapList( (Expression initializer) -> initializer.accept(this, null), expr.getInitializer().get().getValues()); } else { initializers = Collections.emptyList(); } TypeMirror arrayType = convertTypeToTypeMirror(expr.getElementType()); if (arrayType == null) { throw new ParseRuntimeException( constructJavaExpressionParseError( expr.getElementType().asString(), "type not parsable")); } for (int i = 0; i < dimensions.size(); i++) { arrayType = TypesUtils.createArrayType(arrayType, env.getTypeUtils()); } return new ArrayCreation(arrayType, dimensions, initializers); } @Override public JavaExpression visit(UnaryExpr expr, Void aVoid) { Tree.Kind treeKind = javaParserUnaryOperatorToTreeKind(expr.getOperator()); JavaExpression operand = expr.getExpression().accept(this, null); // This eliminates + and performs constant-folding for -; it could also do so for other // operations. switch (treeKind) { case UNARY_PLUS: return operand; case UNARY_MINUS: if (operand instanceof ValueLiteral) { return ((ValueLiteral) operand).negate(); } break; default: // Not optimization for this operand break; } return new UnaryOperation(operand.getType(), treeKind, operand); } /** * Convert a JavaParser unary operator to a TreeKind. * * @param op a JavaParser unary operator * @return a TreeKind for the unary operator */ private Tree.Kind javaParserUnaryOperatorToTreeKind(UnaryExpr.Operator op) { switch (op) { case BITWISE_COMPLEMENT: return Tree.Kind.BITWISE_COMPLEMENT; case LOGICAL_COMPLEMENT: return Tree.Kind.LOGICAL_COMPLEMENT; case MINUS: return Tree.Kind.UNARY_MINUS; case PLUS: return Tree.Kind.UNARY_PLUS; case POSTFIX_DECREMENT: return Tree.Kind.POSTFIX_DECREMENT; case POSTFIX_INCREMENT: return Tree.Kind.POSTFIX_INCREMENT; case PREFIX_DECREMENT: return Tree.Kind.PREFIX_DECREMENT; case PREFIX_INCREMENT: return Tree.Kind.PREFIX_INCREMENT; default: throw new BugInCF("unhandled " + op); } } @Override public JavaExpression visit(BinaryExpr expr, Void aVoid) { JavaExpression leftJe = expr.getLeft().accept(this, null); JavaExpression rightJe = expr.getRight().accept(this, null); TypeMirror leftType = leftJe.getType(); TypeMirror rightType = rightJe.getType(); TypeMirror type; // isSubtype() first does the cheaper test isSameType(), so no need to do it here. if (types.isSubtype(leftType, rightType)) { type = rightType; } else if (types.isSubtype(rightType, leftType)) { type = leftType; } else if (expr.getOperator() == BinaryExpr.Operator.PLUS && (TypesUtils.isString(leftType) || TypesUtils.isString(rightType))) { type = stringTypeMirror; } else { throw new ParseRuntimeException( constructJavaExpressionParseError( expr.toString(), String.format("inconsistent types %s %s for %s", leftType, rightType, expr))); } return new BinaryOperation( type, javaParserBinaryOperatorToTreeKind(expr.getOperator()), leftJe, rightJe); } /** * Convert a JavaParser binary operator to a TreeKind. * * @param op a JavaParser binary operator * @return a TreeKind for the binary operator */ private Tree.Kind javaParserBinaryOperatorToTreeKind(BinaryExpr.Operator op) { switch (op) { case AND: return Tree.Kind.CONDITIONAL_AND; case BINARY_AND: return Tree.Kind.AND; case BINARY_OR: return Tree.Kind.OR; case DIVIDE: return Tree.Kind.DIVIDE; case EQUALS: return Tree.Kind.EQUAL_TO; case GREATER: return Tree.Kind.GREATER_THAN; case GREATER_EQUALS: return Tree.Kind.GREATER_THAN_EQUAL; case LEFT_SHIFT: return Tree.Kind.LEFT_SHIFT; case LESS: return Tree.Kind.LESS_THAN; case LESS_EQUALS: return Tree.Kind.LESS_THAN_EQUAL; case MINUS: return Tree.Kind.MINUS; case MULTIPLY: return Tree.Kind.MULTIPLY; case NOT_EQUALS: return Tree.Kind.NOT_EQUAL_TO; case OR: return Tree.Kind.CONDITIONAL_OR; case PLUS: return Tree.Kind.PLUS; case REMAINDER: return Tree.Kind.REMAINDER; case SIGNED_RIGHT_SHIFT: return Tree.Kind.RIGHT_SHIFT; case UNSIGNED_RIGHT_SHIFT: return Tree.Kind.UNSIGNED_RIGHT_SHIFT; case XOR: return Tree.Kind.XOR; default: throw new BugInCF("unhandled " + op); } } /** * Converts the JavaParser type to a TypeMirror. Returns null if {@code type} is not handled; * this method does not handle type variables, union types, or intersection types. * * @param type a JavaParser type * @return a TypeMirror corresponding to {@code type}, or null if {@code type} isn't handled */ private @Nullable TypeMirror convertTypeToTypeMirror(Type type) { if (type.isClassOrInterfaceType()) { LanguageLevel currentSourceVersion = JavaParserUtil.getCurrentSourceVersion(env); try { return JavaParserUtil.parseExpression(type.asString(), currentSourceVersion) .accept(this, null) .getType(); } catch (ParseProblemException e) { return null; } } else if (type.isPrimitiveType()) { switch (type.asPrimitiveType().getType()) { case BOOLEAN: return types.getPrimitiveType(TypeKind.BOOLEAN); case BYTE: return types.getPrimitiveType(TypeKind.BYTE); case SHORT: return types.getPrimitiveType(TypeKind.SHORT); case INT: return types.getPrimitiveType(TypeKind.INT); case CHAR: return types.getPrimitiveType(TypeKind.CHAR); case FLOAT: return types.getPrimitiveType(TypeKind.FLOAT); case LONG: return types.getPrimitiveType(TypeKind.LONG); case DOUBLE: return types.getPrimitiveType(TypeKind.DOUBLE); } } else if (type.isVoidType()) { return types.getNoType(TypeKind.VOID); } else if (type.isArrayType()) { TypeMirror componentType = convertTypeToTypeMirror(type.asArrayType().getComponentType()); if (componentType == null) { return null; } return types.getArrayType(componentType); } return null; } } /** * If {@code s} is exactly a formal parameter, return its 1-based index. Returns -1 otherwise. * * @param s a Java expression * @return the 1-based index of the formal parameter that {@code s} represents, or -1 */ public static int parameterIndex(String s) { Matcher matcher = ANCHORED_PARAMETER_PATTERN.matcher(s); if (matcher.find()) { return Integer.parseInt(matcher.group(1)); } return -1; } /////////////////////////////////////////////////////////////////////////// /// Contexts /// /** * Returns the type of the innermost enclosing class. Returns Type.noType if the type is a * top-level class. * *

If the innermost enclosing class is static, this method returns the type of that class. By * contrast, {@link DeclaredType#getEnclosingType()} returns the type of the innermost enclosing * class that is not static. * * @param type a DeclaredType * @return the type of the innermost enclosing class or Type.noType */ private static TypeMirror getTypeOfEnclosingClass(DeclaredType type) { if (type instanceof ClassType) { // enclClass() needs to be called on tsym.owner, because tsym.enclClass() == tsym. Symbol sym = ((ClassType) type).tsym.owner; if (sym == null) { return com.sun.tools.javac.code.Type.noType; } ClassSymbol cs = sym.enclClass(); if (cs == null) { return com.sun.tools.javac.code.Type.noType; } return cs.asType(); } else { return type.getEnclosingType(); } } /////////////////////////////////////////////////////////////////////////// /// Exceptions /// /** * An exception that indicates a parse error. Call {@link #getDiagMessage} to obtain a {@link * DiagMessage} that can be used for error reporting. */ public static class JavaExpressionParseException extends Exception { private static final long serialVersionUID = 2L; /** The error message key. */ private @CompilerMessageKey String errorKey; /** The arguments to the error message key. */ public final Object[] args; /** * Create a new JavaExpressionParseException. * * @param errorKey the error message key * @param args the arguments to the error message key */ public JavaExpressionParseException(@CompilerMessageKey String errorKey, Object... args) { this(null, errorKey, args); } /** * Create a new JavaExpressionParseException. * * @param cause cause * @param errorKey the error message key * @param args the arguments to the error message key */ public JavaExpressionParseException( @Nullable Throwable cause, @CompilerMessageKey String errorKey, Object... args) { super(cause); this.errorKey = errorKey; this.args = args; } @Override public String getMessage() { return errorKey + " " + Arrays.toString(args); } /** * Return a DiagMessage that can be used for error reporting. * * @return a DiagMessage that can be used for error reporting */ public DiagMessage getDiagMessage() { return new DiagMessage(Kind.ERROR, errorKey, args); } public boolean isFlowParseError() { return errorKey.endsWith("flowexpr.parse.error"); } } /** * Returns a {@link JavaExpressionParseException} with error key "flowexpr.parse.error" for the * expression {@code expr} with explanation {@code explanation}. * * @param expr the string that could not be parsed * @param explanation an explanation of the parse failure * @return a {@link JavaExpressionParseException} for the expression {@code expr} with explanation * {@code explanation}. */ private static JavaExpressionParseException constructJavaExpressionParseError( String expr, String explanation) { if (expr == null) { throw new BugInCF("Must have an expression."); } if (explanation == null) { throw new BugInCF("Must have an explanation."); } return new JavaExpressionParseException( (Throwable) null, "flowexpr.parse.error", "Invalid '" + expr + "' because " + explanation); } /** * The unchecked exception equivalent of checked exception {@link JavaExpressionParseException}. */ private static class ParseRuntimeException extends RuntimeException { private static final long serialVersionUID = 2L; private final JavaExpressionParseException exception; private ParseRuntimeException(JavaExpressionParseException exception) { this.exception = exception; } private JavaExpressionParseException getCheckedException() { return exception; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy