package checkers.util;
import checkers.nullness.quals.*;
import checkers.types.AnnotatedTypeMirror;
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 com.sun.source.tree.*;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.tree.JCTree.JCNewClass;
import com.sun.tools.javac.tree.JCTree.JCTypeApply;
/**
* A utility class made for helping to analyze a given {@code Tree}.
*/
// TODO: This class needs significant restructuring
public final class TreeUtils {
// Cannot be instantiated
private TreeUtils() { throw new AssertionError("un-initializable class"); }
/**
* Checks if the provided method is a constructor method or no.
*
* @param tree
* a tree defining the method
* @return true iff tree describes a constructor
*/
public static boolean isConstructor(final MethodTree tree) {
return (tree.getName().contentEquals("<init>"));
}
/**
* Returns true if the tree is a tree that 'looks like' either an access
* of a field or an invokation of a method that are owned by the same
* accessing instance.
*
* It would only return true if the access tree is of the form:
* <pre>
* field
* this.field
*
* method()
* this.method()
* </pre>
*
* It does not perform any semantical check to differentiate between
* fields and local variables; local methods or imported static methods.
*
* @param tree expression tree representing an access to object member
* @return {@code true} iff the member is a member of {@code this} instance
*/
public static boolean isSelfAccess(final ExpressionTree tree) {
ExpressionTree tr = TreeUtils.skipParens(tree);
// If method invocation check the method select
if (tr.getKind() == Tree.Kind.ARRAY_ACCESS)
return false;
if (tree.getKind() == Tree.Kind.METHOD_INVOCATION) {
tr = ((MethodInvocationTree)tree).getMethodSelect();
}
tr = TreeUtils.skipParens(tr);
if (tr.getKind() == Tree.Kind.TYPE_CAST)
tr = ((TypeCastTree)tr).getExpression();
tr = TreeUtils.skipParens(tr);
if (tr.getKind() == Tree.Kind.IDENTIFIER)
return true;
if (tr.getKind() == Tree.Kind.MEMBER_SELECT) {
tr = ((MemberSelectTree)tr).getExpression();
if (tr.getKind() == Tree.Kind.IDENTIFIER) {
Name ident = ((IdentifierTree)tr).getName();
return ident.contentEquals("this") ||
ident.contentEquals("super");
}
}
return false;
}
/**
* Checks if the method invocation is a call to super
*
* @param tree
* a tree defining a method invocation
*
* @return true iff tree describes a call to super
*/
public static boolean isSuperCall(MethodInvocationTree tree) {
/*@Nullable*/ ExpressionTree mst = tree.getMethodSelect();
assert mst != null; /*nninvariant*/
if (mst.getKind() != Tree.Kind.MEMBER_SELECT)
return false;
MemberSelectTree selectTree = (MemberSelectTree)mst;
if (selectTree.getExpression().getKind() != Tree.Kind.IDENTIFIER)
return false;
return ((IdentifierTree) selectTree.getExpression()).getName()
.contentEquals("super");
}
/**
* Gets the first enclosing tree in path, of the specified kind.
*
* @param path the path defining the tree node
* @param kind the kind of the desired tree
* @return the enclosing tree of the given type as given by the path
*/
public static Tree enclosingOfKind(final TreePath path, final Tree.Kind kind) {
TreePath p = path;
while (p != null) {
Tree leaf = p.getLeaf();
assert leaf != null; /*nninvariant*/
if (leaf.getKind() == kind)
return leaf;
p = p.getParentPath();
}
return null;
}
/**
* Gets the first enclosing tree in path, of the specified class
*
* @param path the path defining the tree node
* @param treeClass the class of the desired tree
* @return the enclosing tree of the given type as given by the path
*/
public static <T extends Tree> T enclosingOfClass(final TreePath path, final Class<T> treeClass) {
TreePath p = path;
while (p != null) {
Tree leaf = p.getLeaf();
if (treeClass.isInstance(leaf))
return treeClass.cast(leaf);
p = p.getParentPath();
}
return null;
}
/**
* Gets the enclosing method of the tree node defined by the given
* {@code {@link TreePath}}. It returns a {@link Tree}, from which
* {@link AnnotatedTypeMirror} or {@link Element} can be
* obtained.
*
* @param path
* the path defining the tree node
* @return the enclosing class (or interface) as given by the path, or null
* if one does not exist.
*/
public static /*@Nullable*/ ClassTree enclosingClass(final /*@Nullable*/ TreePath path) {
return (ClassTree) enclosingOfKind(path, Tree.Kind.CLASS);
}
/**
* Gets the enclosing variable of a tree node defined by the given
* {@link TreePath}.
*
* @param path the path defining the tree node
* @return the enclosing variable as given by the path, or null if one does not exist
*/
public static VariableTree enclosingVariable(final TreePath path) {
return (VariableTree) enclosingOfKind(path, Tree.Kind.VARIABLE);
}
/**
* Gets the enclosing method of the tree node defined by the given
* {@code {@link TreePath}}. It returns a {@link Tree}, from which an
* {@link AnnotatedTypeMirror} or {@link Element} can be
* obtained.
*
* @param path the path defining the tree node
* @return the enclosing method as given by the path, or null if one does
* not exist
*/
public static /*@Nullable*/ MethodTree enclosingMethod(final /*@Nullable*/ TreePath path) {
return (MethodTree) enclosingOfKind(path, Tree.Kind.METHOD);
}
/**
* If the given tree is a parenthesized tree, it returns the enclosed
* non-parenthesized tree. Otherwise, it returns the same tree.
*
* @param tree a tree
* @return the outermost non-parenthesized tree enclosed by the given tree
*/
public static Tree skipParens(final Tree tree) {
Tree t = tree;
while (t.getKind() == Tree.Kind.PARENTHESIZED)
t = ((ParenthesizedTree)t).getExpression();
return t;
}
/**
* If the given tree is a parenthesized tree, it returns the enclosed
* non-parenthesized tree. Otherwise, it returns the same tree.
*
* @param tree an expression tree
* @return the outermost non-parenthesized tree enclosed by the given tree
*/
public static ExpressionTree skipParens(final ExpressionTree tree) {
ExpressionTree t = tree;
while (t.getKind() == Tree.Kind.PARENTHESIZED)
t = ((ParenthesizedTree)t).getExpression();
return t;
}
/**
* Returns the tree with the assignment context for the treePath
* leaf node.
*
* The assignment context for the treepath is the most enclosing
* tree of type:
* <ul>
* <li>AssignmentTree </li>
* <li>CompoundAssignmentTree </li>
* <li>MethodInvocationTree</li>
* <li>NewArrayTree</li>
* <li>NewClassTree</li>
* <li>ReturnTree</li>
* <li>VariableTree</li>
* </ul>
*
* @param treePath
* @return the assignment context as described.
*/
public static Tree getAssignmentContext(final TreePath treePath) {
TreePath path = treePath.getParentPath();
if (path == null)
return null;
Tree node = path.getLeaf();
if ((node instanceof AssignmentTree) ||
(node instanceof CompoundAssignmentTree) ||
(node instanceof MethodInvocationTree) ||
(node instanceof NewArrayTree) ||
(node instanceof NewClassTree) ||
(node instanceof ReturnTree) ||
(node instanceof VariableTree))
return node;
return null;
}
/**
* Gets the element for a class corresponding to a declaration.
*
* @param node
* @return the element for the given class
*/
public static final TypeElement elementFromDeclaration(ClassTree node) {
assert node != null : "null node";
TypeElement elt = (TypeElement) TreeInfo.symbolFor((JCTree) node);
return elt;
// if (elt != null)
// return elt;
// TreePath path = trees.getPath(root, node);
// return (TypeElement)trees.getElement(path);
}
/**
* Gets the element for a method corresponding to a declaration.
*
* @param node
* @return the element for the given method
*/
public static final ExecutableElement elementFromDeclaration(MethodTree node) {
assert node != null : "null node";
ExecutableElement elt = (ExecutableElement)TreeInfo.symbolFor((JCTree)node);
return elt;
// if (elt != null)
// return elt;
// TreePath path = trees.getPath(root, node);
// return (ExecutableElement)trees.getElement(path);
}
/**
* Gets the element for a variable corresponding to its declaration.
*
* @param node
* @return the element for the given variable
*/
public static final VariableElement elementFromDeclaration(VariableTree node) {
assert node != null : "null node";
VariableElement elt = (VariableElement)TreeInfo.symbolFor((JCTree)node);
return elt;
// if (elt != null)
// return elt;
// TreePath path = trees.getPath(root, node);
// return (VariableElement)trees.getElement(path);
}
/**
* Gets the element for the method corresponding to this invocation. To get
* the element for a method declaration, use {@link
* Trees#getElement(TreePath)} instead.
*
* @param node the method invocation
* @return the element for the method that corresponds to this invocation
*/
public static final ExecutableElement elementFromUse(MethodInvocationTree node) {
return (ExecutableElement)TreeInfo.symbol((JCTree)node.getMethodSelect());
}
/**
* Gets the element for the declaration corresponding to this identifier.
* To get the element for a declaration, use {@link
* Trees#getElement(TreePath)} instead.
*
* @param node the identifier
* @return the element for the declaration that corresponds to this
* identifier
*/
public static final Element elementFromUse(IdentifierTree node) {
return TreeInfo.symbol((JCTree)node);
}
public static final boolean isUseOfElement(Tree node) {
switch (node.getKind()) {
case IDENTIFIER:
case MEMBER_SELECT:
case METHOD_INVOCATION:
return true;
default:
return false;
}
}
public static final Element elementFromUse(ExpressionTree node) {
switch (node.getKind()) {
case IDENTIFIER:
case MEMBER_SELECT:
case METHOD_INVOCATION:
return TreeInfo.symbol((JCTree)node);
default:
throw new IllegalArgumentException("Tree not use: " + node.getKind());
}
}
public static final ExecutableElement elementFromUse(NewClassTree node) {
return (ExecutableElement)((JCNewClass)node).constructor;
}
/**
* Gets the element for the declaration corresponding to this member
* access. To get the element for a declaration, use {@link
* Trees#getElement(TreePath)} instead.
*
* @param node the member access
* @return the element for the declaration that corresponds to this member
* access
*/
public static final Element elementFromUse(MemberSelectTree node) {
return TreeInfo.symbol((JCTree)node);
}
/**
* @return the name of the invoked method
*/
public static final Name methodName(MethodInvocationTree node) {
ExpressionTree expr = node.getMethodSelect();
if (expr.getKind() == Tree.Kind.IDENTIFIER)
return ((IdentifierTree)expr).getName();
else if (expr.getKind() == Tree.Kind.MEMBER_SELECT)
return ((MemberSelectTree)expr).getIdentifier();
throw new AssertionError("cannot be here: " + node);
}
/**
* @return true if the first statement in the body is a self constructor
* invocation within a constructor
*/
public static final boolean containsThisConstructorInvocation(MethodTree node) {
if (!TreeUtils.isConstructor(node)
|| node.getBody().getStatements().isEmpty())
return false;
StatementTree st = node.getBody().getStatements().get(0);
if (!(st instanceof ExpressionStatementTree)
|| !(((ExpressionStatementTree)st).getExpression() instanceof MethodInvocationTree))
return false;
MethodInvocationTree invocation = (MethodInvocationTree)
((ExpressionStatementTree)st).getExpression();
return "this".contentEquals(TreeUtils.methodName(invocation));
}
public static final Tree firstStatement(Tree tree) {
Tree first;
if (tree.getKind() == Tree.Kind.BLOCK) {
BlockTree block = (BlockTree)tree;
if (block.getStatements().isEmpty())
first = block;
else
first = block.getStatements().iterator().next();
} else {
first = tree;
}
return first;
}
/**
* Returns true if the tree is of a diamond type
*/
public static final boolean isDiamondTree(Tree tree) {
switch (tree.getKind()) {
case ANNOTATED_TYPE: return isDiamondTree(((AnnotatedTypeTree)tree).getUnderlyingType());
case PARAMETERIZED_TYPE: return ((ParameterizedTypeTree)tree).getTypeArguments().isEmpty();
case NEW_CLASS: return isDiamondTree(((NewClassTree)tree).getIdentifier());
default: return false;
}
}
/**
* Returns true if the tree represents a {@code String} concatenation
* operation
*/
public static final boolean isStringConcatenation(Tree tree) {
return (tree.getKind() == Tree.Kind.PLUS
&& TypesUtils.isDeclaredOfName(InternalUtils.typeOf(tree),
String.class.getCanonicalName()));
}
/**
* Returns true if the compound assignment tree is a string concatenation
*/
public static final boolean isStringCompoundConcatenation(CompoundAssignmentTree tree) {
return (tree.getKind() == Tree.Kind.PLUS_ASSIGNMENT
&& TypesUtils.isDeclaredOfName(InternalUtils.typeOf(tree),
String.class.getCanonicalName()));
}
/**
* Returns true if the node is a constant-time expression.
*
* A tree is a constant-time expression if it is:
* <ol>
* <li>a literal tree
* <li>a reference to a final variable initialized with a compile time
* constant
* <li>a String concatination of two compile time constants
* </ol>
*/
public static boolean isCompileTimeString(ExpressionTree node) {
ExpressionTree tree = TreeUtils.skipParens(node);
if (tree instanceof LiteralTree)
return true;
if (TreeUtils.isUseOfElement(tree)) {
Element elt = TreeUtils.elementFromUse(tree);
return ElementUtils.isCompileTimeConstant(elt);
} else if (TreeUtils.isStringConcatenation(tree)) {
BinaryTree binOp = (BinaryTree)node;
return isCompileTimeString(binOp.getLeftOperand())
&& isCompileTimeString(binOp.getRightOperand());
} else {
return false;
}
}
/**
* Returns the receiver tree of a field access or a method invocation
*/
public static ExpressionTree getReceiverTree(ExpressionTree expression) {
if (!(expression.getKind() == Tree.Kind.METHOD_INVOCATION
|| expression.getKind() == Tree.Kind.MEMBER_SELECT
|| expression.getKind() == Tree.Kind.IDENTIFIER
|| expression.getKind() == Tree.Kind.ARRAY_ACCESS))
// No receiver type for those
return null;
if (expression.getKind() == Tree.Kind.IDENTIFIER
&& "this".equals(expression.toString()))
return null;
ExpressionTree receiver = TreeUtils.skipParens(expression);
if (receiver.getKind() == Tree.Kind.ARRAY_ACCESS)
return ((ArrayAccessTree)receiver).getExpression();
// Avoid int.class
if (expression.getKind() == Tree.Kind.MEMBER_SELECT &&
((MemberSelectTree)expression).getExpression() instanceof PrimitiveTypeTree)
return null;
if (isSelfAccess(expression)) {
return null;
}
//
// Trying to handle receiver calls to trees of the form
// ((m).getArray())
// returns the type of 'm' in this case
if (receiver.getKind() == Tree.Kind.METHOD_INVOCATION)
receiver = ((MethodInvocationTree)receiver).getMethodSelect();
receiver = TreeUtils.skipParens(receiver);
assert receiver.getKind() == Tree.Kind.MEMBER_SELECT;
if (receiver.getKind() == Tree.Kind.MEMBER_SELECT)
receiver = ((MemberSelectTree)receiver).getExpression();
return receiver;
}
}