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

org.checkerframework.dataflow.expression.JavaExpression Maven / Gradle / Ivy

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.Tree;
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.dataflow.qual.Pure;
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; } /** * Returns true if some subexpression is of given class. * *

If you want to debug and determine which subexpression is of the given class, use * {@link #containedOfClass}. * * @param clazz the JavaExpression subclass to search for * @return true if some subexpression's class is the given class */ @Pure public final boolean containsOfClass(Class clazz) { return containedOfClass(clazz) != null; } /** * Returns the first subexpression whose class is the given class, or null. * *

This is intended as a diagnostic aid; most clients will use {@link #containsOfClass}. * * @param the type corresponding to {@code clazz} * @param clazz the JavaExpression subclass to search for * @return true if some subexpression whose class is the given class */ @Pure public abstract @Nullable T containedOfClass(Class clazz); /** * Returns true if some subexpression is {@link Unknown}. * *

If you want to debug and determine which subexpression is of the given class, use * {@link #containedUnknown}. * * @return true if some subexpression is {@link Unknown} */ @Pure public final boolean containsUnknown() { return containsOfClass(Unknown.class); } /** * Returns the first subexpression whose class is {@link Unknown}, or null. * *

This is intended as a diagnostic aid; most clients will use {@link #containsUnknown}. * * @return the first subexpression whose class is {@link Unknown}, or null */ @Pure public final @Nullable Unknown containedUnknown() { return containedOfClass(Unknown.class); } /** * Returns true if the expression is deterministic. * * @param provider an annotation provider (a type factory) * @return true if this expression is deterministic */ @Pure 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 */ @Pure 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}. * * @return true if no subexpression of this can be assigned to from outside the current method * body * @see #isUnmodifiableByOtherCode * @deprecated use {@link #isAssignableByOtherCode} */ @Deprecated // 2024-04-30 @Pure public boolean isUnassignableByOtherCode() { return !isAssignableByOtherCode(); } /** * Returns true if some subexpression of this can be assigned to from outside the current method * body. * *

This is false for local variables, the self reference, final field accesses whose receiver * is {@link #isUnassignableByOtherCode}, and operations whose operands are all not {@link * #isModifiableByOtherCode}. * * @return true if some subexpression of this can be assigned to from outside the current method * body * @see #isModifiableByOtherCode */ // TODO: Make abstract when isUnassignableByOtherCode is removed. @Pure public boolean isAssignableByOtherCode() { return !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. * * @return true if the value of this expression cannot be changed from outside the current method * body * @see #isUnassignableByOtherCode * @deprecated use {@link #isModifiableByOtherCode} */ @Deprecated // 2024-04-30 @Pure public boolean isUnmodifiableByOtherCode() { return !isModifiableByOtherCode(); } /** * Returns true if the value this expression stands for can be changed by a method call; * equivalently, if the value this expression evaluates to can be changed by a side effect from * outside the containing method. * *

Approximately, this returns true if the expression is {@link #isAssignableByOtherCode} or * its type is mutable. ({@code String} is an immutable type.) * * @return true if the value of this expression can be changed from outside the current method * body * @see #isUnassignableByOtherCode */ // TODO: Make abstract when isUnmodifiableByOtherCode is removed. @Pure public boolean isModifiableByOtherCode() { return !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 @Pure 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} */ @SuppressWarnings("RedundantControlFlow") @Pure public 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} */ @Pure 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} */ @Pure 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'. */ @Pure 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 */ @Pure 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 SuperReference(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")) { result = new ThisReference(typeOfId); break; } else if (identifierName.contentEquals("super")) { result = new SuperReference(typeOfId); break; } assert TreeUtils.isUseOfElement(identifierTree) : "@AssumeAssertion(nullness): tree kind"; Element ele = TreeUtils.elementFromUse(identifierTree); if (ele == null) { result = null; } else if (ElementUtils.isTypeElement(ele)) { result = new ClassName(ele.asType()); } else { result = fromVariableElement(typeOfId, (VariableElement) ele, identifierTree); } 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), 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 * @param tree the tree for the variable * @return the Java expression corresponding to the given variable element {@code ele} */ private static JavaExpression fromVariableElement( TypeMirror typeOfEle, @Nullable VariableElement ele, Tree tree) { if (ele == null) { return new Unknown(tree); } 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, 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 a 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. * *

When this returns a ThisReference, its type is the class that declares {@code ele}, which is * not necessarily the type of {@code this} at the invocation site. * * @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 a tree path * @param enclosingType type of the enclosing type * @return a new {@link ClassName} or {@link 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) { @SuppressWarnings("nullness:argument") // elementFromDeclaration is non-null for a parameter 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 - 2025 Weber Informatics LLC | Privacy Policy