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

org.checkerframework.dataflow.expression.JavaExpression 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.43.0
Show newest version
package org.checkerframework.dataflow.expression;

import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.interning.qual.EqualsMethod;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.analysis.Store;
import org.checkerframework.dataflow.cfg.node.ArrayAccessNode;
import org.checkerframework.dataflow.cfg.node.ArrayCreationNode;
import org.checkerframework.dataflow.cfg.node.BinaryOperationNode;
import org.checkerframework.dataflow.cfg.node.ClassNameNode;
import org.checkerframework.dataflow.cfg.node.ExplicitThisNode;
import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.NarrowingConversionNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.StringConversionNode;
import org.checkerframework.dataflow.cfg.node.SuperNode;
import org.checkerframework.dataflow.cfg.node.ThisNode;
import org.checkerframework.dataflow.cfg.node.UnaryOperationNode;
import org.checkerframework.dataflow.cfg.node.ValueLiteralNode;
import org.checkerframework.dataflow.cfg.node.WideningConversionNode;
import org.checkerframework.javacutil.AnnotationProvider;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.plumelib.util.CollectionsPlume;

// The Lock Checker also supports "" as a JavaExpression, but that is implemented in the Lock
// Checker.
// There are no special subclasses (AST nodes) for "".
/**
 * This class represents a Java expression and its type. It does not represent all possible Java
 * expressions (for example, it does not represent a ternary conditional expression {@code ?:}; use
 * {@link org.checkerframework.dataflow.expression.Unknown} for unrepresentable expressions).
 *
 * 

This class's representation is like an AST: subparts are also expressions. For declared names * (fields, local variables, and methods), it also contains an Element. * *

Each subclass represents a different type of expression, such as {@link * org.checkerframework.dataflow.expression.MethodCall}, {@link * org.checkerframework.dataflow.expression.ArrayAccess}, {@link * org.checkerframework.dataflow.expression.LocalVariable}, etc. * * @see the syntax of * Java expressions supported by the Checker Framework */ public abstract class JavaExpression { /** The type of this expression. */ protected final TypeMirror type; /** * Create a JavaExpression. * * @param type the type of the expression */ protected JavaExpression(TypeMirror type) { assert type != null; this.type = type; } public TypeMirror getType() { return type; } public abstract boolean containsOfClass(Class clazz); public boolean containsUnknown() { return containsOfClass(Unknown.class); } /** * Returns true if the expression is deterministic. * * @param provider an annotation provider (a type factory) * @return true if this expression is deterministic */ public abstract boolean isDeterministic(AnnotationProvider provider); /** * Returns true if all the expressions in the list are deterministic. * * @param list the list whose elements to test * @param provider an annotation provider (a type factory) * @return true if all the expressions in the list are deterministic */ public static boolean listIsDeterministic( List list, AnnotationProvider provider) { return list.stream().allMatch(je -> je == null || je.isDeterministic(provider)); } /** * Returns true if and only if the value this expression stands for cannot be changed (with * respect to ==) by a method call. This is the case for local variables, the self reference, * final field accesses whose receiver is {@link #isUnassignableByOtherCode}, and operations whose * operands are all {@link #isUnmodifiableByOtherCode}. * * @see #isUnmodifiableByOtherCode */ public abstract boolean isUnassignableByOtherCode(); /** * Returns true if and only if the value this expression stands for cannot be changed by a method * call, including changes to any of its fields. * *

Approximately, this returns true if the expression is {@link #isUnassignableByOtherCode} and * its type is immutable. * * @see #isUnassignableByOtherCode */ public abstract boolean isUnmodifiableByOtherCode(); /** * Returns true if and only if the two Java expressions are syntactically identical. * *

This exists for use by {@link #containsSyntacticEqualJavaExpression}. * * @param je the other Java expression to compare to this one * @return true if and only if the two Java expressions are syntactically identical */ @EqualsMethod public abstract boolean syntacticEquals(JavaExpression je); /** * Returns true if the corresponding list elements satisfy {@link #syntacticEquals}. * * @param lst1 the first list to compare * @param lst2 the second list to compare * @return true if the corresponding list elements satisfy {@link #syntacticEquals} */ static boolean syntacticEqualsList( List lst1, List lst2) { if (lst1.size() != lst2.size()) { return false; } for (int i = 0; i < lst1.size(); i++) { JavaExpression dim1 = lst1.get(i); JavaExpression dim2 = lst2.get(i); if (dim1 == null && dim2 == null) { continue; } else if (dim1 == null || dim2 == null) { return false; } else { if (!dim1.syntacticEquals(dim2)) { return false; } } } return true; } /** * Returns true if and only if this contains a JavaExpression that is syntactically equal to * {@code other}. * * @param other the JavaExpression to search for * @return true if and only if this contains a JavaExpression that is syntactically equal to * {@code other} */ public abstract boolean containsSyntacticEqualJavaExpression(JavaExpression other); /** * Returns true if the given list contains a JavaExpression that is syntactically equal to {@code * other}. * * @param list the list in which to search for a match * @param other the JavaExpression to search for * @return true if and only if the list contains a JavaExpression that is syntactically equal to * {@code other} */ public static boolean listContainsSyntacticEqualJavaExpression( List list, JavaExpression other) { return list.stream() .anyMatch(je -> je != null && je.containsSyntacticEqualJavaExpression(other)); } /** * Returns true if and only if {@code other} appears anywhere in this or an expression appears in * this such that {@code other} might alias this expression, and that expression is modifiable. * *

This is always true, except for cases where the Java type information prevents aliasing and * none of the subexpressions can alias 'other'. */ public boolean containsModifiableAliasOf(Store store, JavaExpression other) { return this.equals(other) || store.canAlias(this, other); } /** * Format this verbosely, for debugging. * * @return a verbose string representation of this */ public String toStringDebug() { return String.format("%s(%s): %s", getClass().getSimpleName(), type, toString()); } /// /// Static methods /// /** * Returns the Java expression for a {@link FieldAccessNode}. The result may contain {@link * Unknown} as receiver. * * @param node the FieldAccessNode to convert to a JavaExpression * @return the {@link FieldAccess} or {@link ClassName} that corresponds to {@code node} */ public static JavaExpression fromNodeFieldAccess(FieldAccessNode node) { Node receiverNode = node.getReceiver(); String fieldName = node.getFieldName(); if (fieldName.equals("this")) { // The CFG represents "className.this" as a FieldAccessNode, but it isn't a field access. return new ThisReference(receiverNode.getType()); } else if (fieldName.equals("class")) { // The CFG represents "className.class" as a FieldAccessNode; bit it is a class literal. return new ClassName(receiverNode.getType()); } JavaExpression receiver; if (node.isStatic()) { receiver = new ClassName(receiverNode.getType()); } else { receiver = fromNode(receiverNode); } return new FieldAccess(receiver, node); } /** * Returns the internal representation (as {@link FieldAccess}) of a {@link FieldAccessNode}. The * result may contain {@link Unknown} as receiver. * * @param node the ArrayAccessNode to convert to a JavaExpression * @return the internal representation (as {@link FieldAccess}) of a {@link FieldAccessNode}. Can * contain {@link Unknown} as receiver. */ public static ArrayAccess fromArrayAccess(ArrayAccessNode node) { JavaExpression array = fromNode(node.getArray()); JavaExpression index = fromNode(node.getIndex()); return new ArrayAccess(node.getType(), array, index); } /** * We ignore operations such as widening and narrowing when computing the internal representation. * * @param receiverNode a node to convert to a JavaExpression * @return the internal representation of the given node. Might contain {@link Unknown}. */ public static JavaExpression fromNode(Node receiverNode) { JavaExpression result = null; if (receiverNode instanceof FieldAccessNode) { result = fromNodeFieldAccess((FieldAccessNode) receiverNode); } else if (receiverNode instanceof ExplicitThisNode) { result = new ThisReference(receiverNode.getType()); } else if (receiverNode instanceof ThisNode) { result = new ThisReference(receiverNode.getType()); } else if (receiverNode instanceof SuperNode) { result = new ThisReference(receiverNode.getType()); } else if (receiverNode instanceof LocalVariableNode) { LocalVariableNode lv = (LocalVariableNode) receiverNode; result = new LocalVariable(lv); } else if (receiverNode instanceof ArrayAccessNode) { ArrayAccessNode a = (ArrayAccessNode) receiverNode; result = fromArrayAccess(a); } else if (receiverNode instanceof StringConversionNode) { // ignore string conversion return fromNode(((StringConversionNode) receiverNode).getOperand()); } else if (receiverNode instanceof WideningConversionNode) { // ignore widening return fromNode(((WideningConversionNode) receiverNode).getOperand()); } else if (receiverNode instanceof NarrowingConversionNode) { // ignore narrowing return fromNode(((NarrowingConversionNode) receiverNode).getOperand()); } else if (receiverNode instanceof UnaryOperationNode) { UnaryOperationNode uopn = (UnaryOperationNode) receiverNode; return new UnaryOperation(uopn, fromNode(uopn.getOperand())); } else if (receiverNode instanceof BinaryOperationNode) { BinaryOperationNode bopn = (BinaryOperationNode) receiverNode; return new BinaryOperation( bopn, fromNode(bopn.getLeftOperand()), fromNode(bopn.getRightOperand())); } else if (receiverNode instanceof ClassNameNode) { ClassNameNode cn = (ClassNameNode) receiverNode; result = new ClassName(cn.getType()); } else if (receiverNode instanceof ValueLiteralNode) { ValueLiteralNode vn = (ValueLiteralNode) receiverNode; result = new ValueLiteral(vn.getType(), vn); } else if (receiverNode instanceof ArrayCreationNode) { ArrayCreationNode an = (ArrayCreationNode) receiverNode; List<@Nullable JavaExpression> dimensions = CollectionsPlume.mapList(JavaExpression::fromNode, an.getDimensions()); List initializers = CollectionsPlume.mapList(JavaExpression::fromNode, an.getInitializers()); result = new ArrayCreation(an.getType(), dimensions, initializers); } else if (receiverNode instanceof MethodInvocationNode) { MethodInvocationNode mn = (MethodInvocationNode) receiverNode; MethodInvocationTree t = mn.getTree(); if (t == null) { throw new BugInCF("Unexpected null tree for node: " + mn); } assert TreeUtils.isUseOfElement(t) : "@AssumeAssertion(nullness): tree kind"; ExecutableElement invokedMethod = TreeUtils.elementFromUse(t); // Note that the method might be nondeterministic. List parameters = CollectionsPlume.mapList(JavaExpression::fromNode, mn.getArguments()); JavaExpression methodReceiver; if (ElementUtils.isStatic(invokedMethod)) { methodReceiver = new ClassName(mn.getTarget().getReceiver().getType()); } else { methodReceiver = fromNode(mn.getTarget().getReceiver()); } result = new MethodCall(mn.getType(), invokedMethod, methodReceiver, parameters); } if (result == null) { result = new Unknown(receiverNode); } return result; } /** * Converts a javac {@link ExpressionTree} to a CF JavaExpression. The result might contain {@link * Unknown}. * *

We ignore operations such as widening and narrowing when computing the JavaExpression. * * @param tree a javac tree * @return a JavaExpression for the given javac tree */ public static JavaExpression fromTree(ExpressionTree tree) { JavaExpression result; switch (tree.getKind()) { case ARRAY_ACCESS: ArrayAccessTree a = (ArrayAccessTree) tree; JavaExpression arrayAccessExpression = fromTree(a.getExpression()); JavaExpression index = fromTree(a.getIndex()); result = new ArrayAccess(TreeUtils.typeOf(a), arrayAccessExpression, index); break; case BOOLEAN_LITERAL: case CHAR_LITERAL: case DOUBLE_LITERAL: case FLOAT_LITERAL: case INT_LITERAL: case LONG_LITERAL: case NULL_LITERAL: case STRING_LITERAL: LiteralTree vn = (LiteralTree) tree; result = new ValueLiteral(TreeUtils.typeOf(tree), vn.getValue()); break; case NEW_ARRAY: NewArrayTree newArrayTree = (NewArrayTree) tree; List<@Nullable JavaExpression> dimensions; if (newArrayTree.getDimensions() == null) { dimensions = Collections.emptyList(); } else { dimensions = new ArrayList<>(newArrayTree.getDimensions().size()); for (ExpressionTree dimension : newArrayTree.getDimensions()) { dimensions.add(fromTree(dimension)); } } List initializers; if (newArrayTree.getInitializers() == null) { initializers = Collections.emptyList(); } else { initializers = new ArrayList<>(newArrayTree.getInitializers().size()); for (ExpressionTree initializer : newArrayTree.getInitializers()) { initializers.add(fromTree(initializer)); } } result = new ArrayCreation(TreeUtils.typeOf(tree), dimensions, initializers); break; case METHOD_INVOCATION: MethodInvocationTree mn = (MethodInvocationTree) tree; assert TreeUtils.isUseOfElement(mn) : "@AssumeAssertion(nullness): tree kind"; ExecutableElement invokedMethod = TreeUtils.elementFromUse(mn); // Note that the method might be nondeterministic. List parameters = CollectionsPlume.mapList(JavaExpression::fromTree, mn.getArguments()); JavaExpression methodReceiver; if (ElementUtils.isStatic(invokedMethod)) { @SuppressWarnings( "nullness:assignment" // enclosingTypeElement(ExecutableElement): @NonNull ) @NonNull TypeElement methodType = ElementUtils.enclosingTypeElement(invokedMethod); methodReceiver = new ClassName(methodType.asType()); } else { methodReceiver = getReceiver(mn); } TypeMirror resultType = TreeUtils.typeOf(mn); result = new MethodCall(resultType, invokedMethod, methodReceiver, parameters); break; case MEMBER_SELECT: result = fromMemberSelect((MemberSelectTree) tree); break; case IDENTIFIER: IdentifierTree identifierTree = (IdentifierTree) tree; TypeMirror typeOfId = TreeUtils.typeOf(identifierTree); Name identifierName = identifierTree.getName(); if (identifierName.contentEquals("this") || identifierName.contentEquals("super")) { result = new ThisReference(typeOfId); break; } assert TreeUtils.isUseOfElement(identifierTree) : "@AssumeAssertion(nullness): tree kind"; Element ele = TreeUtils.elementFromUse(identifierTree); if (ElementUtils.isTypeElement(ele)) { result = new ClassName(ele.asType()); break; } result = fromVariableElement(typeOfId, ele); break; case UNARY_PLUS: return fromTree(((UnaryTree) tree).getExpression()); case BITWISE_COMPLEMENT: case LOGICAL_COMPLEMENT: case POSTFIX_DECREMENT: case POSTFIX_INCREMENT: case PREFIX_DECREMENT: case PREFIX_INCREMENT: case UNARY_MINUS: JavaExpression operand = fromTree(((UnaryTree) tree).getExpression()); return new UnaryOperation(TreeUtils.typeOf(tree), tree.getKind(), operand); case CONDITIONAL_AND: case CONDITIONAL_OR: case DIVIDE: case EQUAL_TO: case GREATER_THAN: case GREATER_THAN_EQUAL: case LEFT_SHIFT: case LESS_THAN: case LESS_THAN_EQUAL: case MINUS: case MULTIPLY: case NOT_EQUAL_TO: case OR: case PLUS: case REMAINDER: case RIGHT_SHIFT: case UNSIGNED_RIGHT_SHIFT: case XOR: BinaryTree binaryTree = (BinaryTree) tree; JavaExpression left = fromTree(binaryTree.getLeftOperand()); JavaExpression right = fromTree(binaryTree.getRightOperand()); return new BinaryOperation(TreeUtils.typeOf(tree), tree.getKind(), left, right); default: result = null; } if (result == null) { result = new Unknown(tree); } return result; } /** * Returns the Java expression corresponding to the given variable tree {@code tree}. * * @param tree a variable tree * @return a JavaExpression for {@code tree} */ public static JavaExpression fromVariableTree(VariableTree tree) { return fromVariableElement(TreeUtils.typeOf(tree), TreeUtils.elementFromDeclaration(tree)); } /** * Returns the Java expression corresponding to the given variable element {@code ele}. * * @param typeOfEle the type of {@code ele} * @param ele element whose JavaExpression is returned * @return the Java expression corresponding to the given variable element {@code ele} */ private static JavaExpression fromVariableElement(TypeMirror typeOfEle, Element ele) { switch (ele.getKind()) { case LOCAL_VARIABLE: case RESOURCE_VARIABLE: case EXCEPTION_PARAMETER: case PARAMETER: return new LocalVariable(ele); case FIELD: case ENUM_CONSTANT: // Implicit access expression, such as "this" or a class name JavaExpression fieldAccessExpression; @SuppressWarnings("nullness:dereference.of.nullable") // a field has enclosing class TypeMirror enclosingTypeElement = ElementUtils.enclosingTypeElement(ele).asType(); if (ElementUtils.isStatic(ele)) { fieldAccessExpression = new ClassName(enclosingTypeElement); } else { fieldAccessExpression = new ThisReference(enclosingTypeElement); } return new FieldAccess(fieldAccessExpression, typeOfEle, (VariableElement) ele); default: if (ElementUtils.isBindingVariable(ele)) { return new LocalVariable(ele); } throw new BugInCF( "Unexpected kind of VariableTree: kind: %s element: %s", ele.getKind(), ele); } } /** * Creates a JavaExpression from the {@code memberSelectTree}. * * @param memberSelectTree tree * @return a JavaExpression for {@code memberSelectTree} */ private static JavaExpression fromMemberSelect(MemberSelectTree memberSelectTree) { TypeMirror expressionType = TreeUtils.typeOf(memberSelectTree.getExpression()); if (TreeUtils.isClassLiteral(memberSelectTree)) { // the identifier is "class" return new ClassName(expressionType); } if (TreeUtils.isExplicitThisDereference(memberSelectTree)) { // the identifier is "class" return new ThisReference(expressionType); } assert TreeUtils.isUseOfElement(memberSelectTree) : "@AssumeAssertion(nullness): tree kind"; Element ele = TreeUtils.elementFromUse(memberSelectTree); if (ElementUtils.isTypeElement(ele)) { // o instanceof MyClass.InnerClass // o instanceof MyClass.InnerInterface TypeMirror selectType = TreeUtils.typeOf(memberSelectTree); return new ClassName(selectType); } switch (ele.getKind()) { case METHOD: case CONSTRUCTOR: return fromTree(memberSelectTree.getExpression()); case ENUM_CONSTANT: case FIELD: TypeMirror fieldType = TreeUtils.typeOf(memberSelectTree); JavaExpression je = fromTree(memberSelectTree.getExpression()); return new FieldAccess(je, fieldType, (VariableElement) ele); default: throw new BugInCF("Unexpected element kind: %s element: %s", ele.getKind(), ele); } } /** * Returns the parameters of {@code methodEle} as {@link LocalVariable}s. * * @param methodEle the method element * @return list of parameters as {@link LocalVariable}s */ public static List getParametersAsLocalVariables(ExecutableElement methodEle) { return CollectionsPlume.mapList(LocalVariable::new, methodEle.getParameters()); } /** * Returns the parameters of {@code methodEle} as {@link FormalParameter}s. * * @param methodEle the method element * @return list of parameters as {@link FormalParameter}s */ public static List getFormalParameters(ExecutableElement methodEle) { List parameters = new ArrayList<>(methodEle.getParameters().size()); int oneBasedIndex = 1; for (VariableElement variableElement : methodEle.getParameters()) { parameters.add(new FormalParameter(oneBasedIndex, variableElement)); oneBasedIndex++; } return parameters; } /// /// Obtaining the receiver /// /** * Returns the receiver of the given invocation * * @param accessTree method or constructor invocation * @return the receiver of the given invocation */ public static JavaExpression getReceiver(ExpressionTree accessTree) { // TODO: Handle field accesses too? assert accessTree instanceof MethodInvocationTree || accessTree instanceof NewClassTree; ExpressionTree receiverTree = TreeUtils.getReceiverTree(accessTree); if (receiverTree != null) { return fromTree(receiverTree); } else { Element ele = TreeUtils.elementFromUse(accessTree); if (ele == null) { throw new BugInCF("TreeUtils.elementFromUse(" + accessTree + ") => null"); } return getImplicitReceiver(ele); } } /** * Returns the implicit receiver of ele. * *

Returns either a new ClassName or a new ThisReference depending on whether ele is static or * not. The passed element must be a field, method, or class. * * @param ele a field, method, or class * @return either a new ClassName or a new ThisReference depending on whether ele is static or not */ public static JavaExpression getImplicitReceiver(Element ele) { TypeElement enclosingTypeElement = ElementUtils.enclosingTypeElement(ele); if (enclosingTypeElement == null) { throw new BugInCF("getImplicitReceiver's arg has no enclosing type: " + ele); } TypeMirror enclosingType = enclosingTypeElement.asType(); if (ElementUtils.isStatic(ele)) { return new ClassName(enclosingType); } else { return new ThisReference(enclosingType); } } /** * Returns either a new ClassName or ThisReference JavaExpression object for the enclosingType. * *

The Tree should be an expression or a statement that does not have a receiver or an implicit * receiver. For example, a local variable declaration. * * @param path TreePath to tree * @param enclosingType type of the enclosing type * @return a new ClassName or ThisReference that is a JavaExpression object for the enclosingType */ public static JavaExpression getPseudoReceiver(TreePath path, TypeMirror enclosingType) { if (TreePathUtil.isTreeInStaticScope(path)) { return new ClassName(enclosingType); } else { return new ThisReference(enclosingType); } } /** * Accept method of the visitor pattern. * * @param visitor the visitor to be applied to this JavaExpression * @param p the parameter for this operation * @param result type of the operation * @param

parameter type * @return the result of visiting this */ public abstract R accept(JavaExpressionVisitor visitor, P p); /** * Viewpoint-adapts {@code this} to a field access with receiver {@code receiver}. * * @param receiver receiver of the field access * @return viewpoint-adapted version of this */ public JavaExpression atFieldAccess(JavaExpression receiver) { return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiver); } /** * Viewpoint-adapts {@code this} to the {@code methodTree} by converting any {@code * FormalParameter} into {@code LocalVariable}s. * * @param methodTree method declaration tree * @return viewpoint-adapted version of this */ public final JavaExpression atMethodBody(MethodTree methodTree) { List parametersJe = CollectionsPlume.mapList( (VariableTree param) -> new LocalVariable(TreeUtils.elementFromDeclaration(param)), methodTree.getParameters()); return ViewpointAdaptJavaExpression.viewpointAdapt(this, parametersJe); } /** * Viewpoint-adapts {@code this} to the {@code methodInvocationTree}. * * @param methodInvocationTree method invocation * @return viewpoint-adapted version of this */ public final JavaExpression atMethodInvocation(MethodInvocationTree methodInvocationTree) { JavaExpression receiverJe = JavaExpression.getReceiver(methodInvocationTree); List argumentsJe = argumentTreesToJavaExpressions( TreeUtils.elementFromUse(methodInvocationTree), methodInvocationTree.getArguments()); return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe); } /** * Viewpoint-adapts {@code this} to the {@code invocationNode}. * * @param invocationNode method invocation * @return viewpoint-adapted version of this */ public final JavaExpression atMethodInvocation(MethodInvocationNode invocationNode) { JavaExpression receiverJe = JavaExpression.fromNode(invocationNode.getTarget().getReceiver()); List argumentsJe = CollectionsPlume.mapList(JavaExpression::fromNode, invocationNode.getArguments()); return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe); } /** * Viewpoint-adapts {@code this} to the {@code newClassTree}. * * @param newClassTree constructor invocation * @return viewpoint-adapted version of this */ public JavaExpression atConstructorInvocation(NewClassTree newClassTree) { JavaExpression receiverJe = JavaExpression.getReceiver(newClassTree); List argumentsJe = argumentTreesToJavaExpressions( TreeUtils.elementFromUse(newClassTree), newClassTree.getArguments()); return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe); } /** * Converts method or constructor arguments from Trees to JavaExpressions, accounting for varargs. * * @param method the method or constructor being invoked * @param argTrees the arguments to the method or constructor * @return the arguments, as JavaExpressions */ private static List argumentTreesToJavaExpressions( ExecutableElement method, List argTrees) { if (isVarArgsInvocation(method, argTrees)) { List result = new ArrayList<>(method.getParameters().size()); for (int i = 0; i < method.getParameters().size() - 1; i++) { result.add(JavaExpression.fromTree(argTrees.get(i))); } List varargArgs = new ArrayList<>(argTrees.size() - method.getParameters().size() + 1); for (int i = method.getParameters().size() - 1; i < argTrees.size(); i++) { varargArgs.add(JavaExpression.fromTree(argTrees.get(i))); } Element varargsElement = method.getParameters().get(method.getParameters().size() - 1); TypeMirror tm = ElementUtils.getType(varargsElement); result.add(new ArrayCreation(tm, Collections.emptyList(), varargArgs)); return result; } return CollectionsPlume.mapList(JavaExpression::fromTree, argTrees); } /** * Returns true if method is a varargs method or constructor and its varargs arguments are not * passed in an array. * * @param method the method or constructor * @param args the arguments at the call site * @return true if method is a varargs method and its varargs arguments are not passed in an array */ private static boolean isVarArgsInvocation( ExecutableElement method, List args) { if (!method.isVarArgs()) { return false; } if (method.getParameters().size() != args.size()) { return true; } TypeMirror lastArgType = TreeUtils.typeOf(args.get(args.size() - 1)); if (lastArgType.getKind() != TypeKind.ARRAY) { return true; } List paramElts = method.getParameters(); VariableElement lastParamElt = paramElts.get(paramElts.size() - 1); return TypesUtils.getArrayDepth(ElementUtils.getType(lastParamElt)) != TypesUtils.getArrayDepth(lastArgType); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy