blob: 2c48f4a66d3590f9361f8a5eddc12f12561599f9 [file] [log] [blame]
package org.checkerframework.javacutil.trees;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.AssignmentTree;
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.NewArrayTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.VariableTree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Names;
import java.util.List;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.plumelib.util.CollectionsPlume;
/**
* The TreeBuilder permits the creation of new AST Trees using the non-public Java compiler API
* TreeMaker.
*/
public class TreeBuilder {
protected final Elements elements;
protected final Types modelTypes;
protected final com.sun.tools.javac.code.Types javacTypes;
protected final TreeMaker maker;
protected final Names names;
protected final Symtab symtab;
protected final ProcessingEnvironment env;
public TreeBuilder(ProcessingEnvironment env) {
this.env = env;
Context context = ((JavacProcessingEnvironment) env).getContext();
elements = env.getElementUtils();
modelTypes = env.getTypeUtils();
javacTypes = com.sun.tools.javac.code.Types.instance(context);
maker = TreeMaker.instance(context);
names = Names.instance(context);
symtab = Symtab.instance(context);
}
/**
* Builds an AST Tree to access the iterator() method of some iterable expression.
*
* @param iterableExpr an expression whose type is a subtype of Iterable
* @return a MemberSelectTree that accesses the iterator() method of the expression
*/
public MemberSelectTree buildIteratorMethodAccess(ExpressionTree iterableExpr) {
DeclaredType exprType = (DeclaredType) TypesUtils.upperBound(TreeUtils.typeOf(iterableExpr));
assert exprType != null : "expression must be of declared type Iterable<>";
TypeElement exprElement = (TypeElement) exprType.asElement();
// Find the iterator() method of the iterable type
Symbol.MethodSymbol iteratorMethod = null;
for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(exprElement))) {
if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("iterator")) {
iteratorMethod = (Symbol.MethodSymbol) method;
}
}
assert iteratorMethod != null
: "@AssumeAssertion(nullness): no iterator method declared for expression type";
Type.MethodType methodType = (Type.MethodType) iteratorMethod.asType();
Symbol.TypeSymbol methodClass = methodType.asElement();
DeclaredType iteratorType = (DeclaredType) methodType.getReturnType();
iteratorType =
(DeclaredType) javacTypes.asSuper((Type) iteratorType, symtab.iteratorType.asElement());
int numIterTypeArgs = iteratorType.getTypeArguments().size();
assert numIterTypeArgs <= 1 : "expected at most one type argument for Iterator";
if (numIterTypeArgs == 1) {
TypeMirror elementType = iteratorType.getTypeArguments().get(0);
// Remove captured type from a wildcard.
if (elementType instanceof Type.CapturedType) {
elementType = ((Type.CapturedType) elementType).wildcard;
iteratorType =
modelTypes.getDeclaredType(
(TypeElement) modelTypes.asElement(iteratorType), elementType);
}
}
// Replace the iterator method's generic return type with
// the actual element type of the expression.
Type.MethodType updatedMethodType =
new Type.MethodType(
com.sun.tools.javac.util.List.nil(),
(Type) iteratorType,
com.sun.tools.javac.util.List.nil(),
methodClass);
JCTree.JCFieldAccess iteratorAccess =
(JCTree.JCFieldAccess) maker.Select((JCTree.JCExpression) iterableExpr, iteratorMethod);
iteratorAccess.setType(updatedMethodType);
return iteratorAccess;
}
/**
* Builds an AST Tree to access the hasNext() method of an iterator.
*
* @param iteratorExpr an expression whose type is a subtype of Iterator
* @return a MemberSelectTree that accesses the hasNext() method of the expression
*/
public MemberSelectTree buildHasNextMethodAccess(ExpressionTree iteratorExpr) {
DeclaredType exprType = (DeclaredType) TreeUtils.typeOf(iteratorExpr);
assert exprType != null : "expression must be of declared type Iterator<>";
TypeElement exprElement = (TypeElement) exprType.asElement();
// Find the hasNext() method of the iterator type
Symbol.MethodSymbol hasNextMethod = null;
for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(exprElement))) {
if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("hasNext")) {
hasNextMethod = (Symbol.MethodSymbol) method;
}
}
assert hasNextMethod != null : "no hasNext method declared for expression type";
JCTree.JCFieldAccess hasNextAccess =
(JCTree.JCFieldAccess) maker.Select((JCTree.JCExpression) iteratorExpr, hasNextMethod);
hasNextAccess.setType(hasNextMethod.asType());
return hasNextAccess;
}
/**
* Builds an AST Tree to access the next() method of an iterator.
*
* @param iteratorExpr an expression whose type is a subtype of Iterator
* @return a MemberSelectTree that accesses the next() method of the expression
*/
public MemberSelectTree buildNextMethodAccess(ExpressionTree iteratorExpr) {
DeclaredType exprType = (DeclaredType) TreeUtils.typeOf(iteratorExpr);
assert exprType != null : "expression must be of declared type Iterator<>";
TypeElement exprElement = (TypeElement) exprType.asElement();
// Find the next() method of the iterator type
Symbol.MethodSymbol nextMethod = null;
for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(exprElement))) {
if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("next")) {
nextMethod = (Symbol.MethodSymbol) method;
}
}
assert nextMethod != null
: "@AssumeAssertion(nullness): no next method declared for expression type";
Type.MethodType methodType = (Type.MethodType) nextMethod.asType();
Symbol.TypeSymbol methodClass = methodType.asElement();
Type elementType;
if (exprType.getTypeArguments().isEmpty()) {
elementType = symtab.objectType;
} else {
elementType = (Type) exprType.getTypeArguments().get(0);
}
// Replace the next method's generic return type with
// the actual element type of the expression.
Type.MethodType updatedMethodType =
new Type.MethodType(
com.sun.tools.javac.util.List.nil(),
elementType,
com.sun.tools.javac.util.List.nil(),
methodClass);
JCTree.JCFieldAccess nextAccess =
(JCTree.JCFieldAccess) maker.Select((JCTree.JCExpression) iteratorExpr, nextMethod);
nextAccess.setType(updatedMethodType);
return nextAccess;
}
/**
* Builds an AST Tree to dereference the length field of an array.
*
* @param expression the array expression whose length is being accessed
* @return a MemberSelectTree to dereference the length of the array
*/
public MemberSelectTree buildArrayLengthAccess(ExpressionTree expression) {
return (JCTree.JCFieldAccess) maker.Select((JCTree.JCExpression) expression, symtab.lengthVar);
}
/**
* Builds an AST Tree to call a method designated by the argument expression.
*
* @param methodExpr an expression denoting a method with no arguments
* @return a MethodInvocationTree to call the argument method
*/
public MethodInvocationTree buildMethodInvocation(ExpressionTree methodExpr) {
return maker.App((JCTree.JCExpression) methodExpr);
}
/**
* Builds an AST Tree to call a method designated by methodExpr, with one argument designated by
* argExpr.
*
* @param methodExpr an expression denoting a method with one argument
* @param argExpr an expression denoting an argument to the method
* @return a MethodInvocationTree to call the argument method
*/
public MethodInvocationTree buildMethodInvocation(
ExpressionTree methodExpr, ExpressionTree argExpr) {
return maker.App(
(JCTree.JCExpression) methodExpr,
com.sun.tools.javac.util.List.of((JCTree.JCExpression) argExpr));
}
/**
* Builds an AST Tree to declare and initialize a variable, with no modifiers.
*
* @param type the type of the variable
* @param name the name of the variable
* @param owner the element containing the new symbol
* @param initializer the initializer expression
* @return a VariableDeclTree declaring the new variable
*/
public VariableTree buildVariableDecl(
TypeMirror type, String name, Element owner, ExpressionTree initializer) {
DetachedVarSymbol sym =
new DetachedVarSymbol(0, names.fromString(name), (Type) type, (Symbol) owner);
VariableTree tree = maker.VarDef(sym, (JCTree.JCExpression) initializer);
sym.setDeclaration(tree);
return tree;
}
/**
* Builds an AST Tree to declare and initialize a variable. The type of the variable is specified
* by a Tree.
*
* @param type the type of the variable, as a Tree
* @param name the name of the variable
* @param owner the element containing the new symbol
* @param initializer the initializer expression
* @return a VariableDeclTree declaring the new variable
*/
public VariableTree buildVariableDecl(
Tree type, String name, Element owner, ExpressionTree initializer) {
Type typeMirror = (Type) TreeUtils.typeOf(type);
DetachedVarSymbol sym =
new DetachedVarSymbol(0, names.fromString(name), typeMirror, (Symbol) owner);
JCTree.JCModifiers mods = maker.Modifiers(0);
JCTree.JCVariableDecl decl =
maker.VarDef(mods, sym.name, (JCTree.JCExpression) type, (JCTree.JCExpression) initializer);
decl.setType(typeMirror);
decl.sym = sym;
sym.setDeclaration(decl);
return decl;
}
/**
* Builds an AST Tree to refer to a variable.
*
* @param decl the declaration of the variable
* @return an IdentifierTree to refer to the variable
*/
public IdentifierTree buildVariableUse(VariableTree decl) {
return (IdentifierTree) maker.Ident((JCTree.JCVariableDecl) decl);
}
/**
* Builds an AST Tree to cast the type of an expression.
*
* @param type the type to cast to
* @param expr the expression to be cast
* @return a cast of the expression to the type
*/
public TypeCastTree buildTypeCast(TypeMirror type, ExpressionTree expr) {
return maker.TypeCast((Type) type, (JCTree.JCExpression) expr);
}
/**
* Builds an AST Tree to assign an expression to a variable.
*
* @param variable the declaration of the variable to assign to
* @param expr the expression to be assigned
* @return a statement assigning the expression to the variable
*/
public StatementTree buildAssignment(VariableTree variable, ExpressionTree expr) {
return maker.Assignment(TreeInfo.symbolFor((JCTree) variable), (JCTree.JCExpression) expr);
}
/**
* Builds an AST Tree to assign an RHS expression to an LHS expression.
*
* @param lhs the expression to be assigned to
* @param rhs the expression to be assigned
* @return a statement assigning the expression to the variable
*/
public AssignmentTree buildAssignment(ExpressionTree lhs, ExpressionTree rhs) {
JCTree.JCAssign assign = maker.Assign((JCTree.JCExpression) lhs, (JCTree.JCExpression) rhs);
assign.setType((Type) TreeUtils.typeOf(lhs));
return assign;
}
/** Builds an AST Tree representing a literal value of primitive or String type. */
public LiteralTree buildLiteral(Object value) {
return maker.Literal(value);
}
/**
* Builds an AST Tree to compare two operands with less than.
*
* @param left the left operand tree
* @param right the right operand tree
* @return a Tree representing "left &lt; right"
*/
public BinaryTree buildLessThan(ExpressionTree left, ExpressionTree right) {
JCTree.JCBinary binary =
maker.Binary(JCTree.Tag.LT, (JCTree.JCExpression) left, (JCTree.JCExpression) right);
binary.setType((Type) modelTypes.getPrimitiveType(TypeKind.BOOLEAN));
return binary;
}
/**
* Builds an AST Tree to dereference an array.
*
* @param array the array to dereference
* @param index the index at which to dereference
* @return a Tree representing the dereference
*/
public ArrayAccessTree buildArrayAccess(ExpressionTree array, ExpressionTree index) {
ArrayType arrayType = (ArrayType) TreeUtils.typeOf(array);
JCTree.JCArrayAccess access =
maker.Indexed((JCTree.JCExpression) array, (JCTree.JCExpression) index);
access.setType((Type) arrayType.getComponentType());
return access;
}
/**
* Builds an AST Tree to refer to a class name.
*
* @param elt an element representing the class
* @return an IdentifierTree referring to the class
*/
public IdentifierTree buildClassUse(Element elt) {
return maker.Ident((Symbol) elt);
}
/**
* Builds an AST Tree to access the valueOf() method of boxed type such as Short or Float.
*
* @param expr an expression whose type is a boxed type
* @return a MemberSelectTree that accesses the valueOf() method of the expression
*/
public MemberSelectTree buildValueOfMethodAccess(Tree expr) {
TypeMirror boxedType = TreeUtils.typeOf(expr);
assert TypesUtils.isBoxedPrimitive(boxedType);
// Find the valueOf(unboxedType) method of the boxed type
Symbol.MethodSymbol valueOfMethod = getValueOfMethod(env, boxedType);
Type.MethodType methodType = (Type.MethodType) valueOfMethod.asType();
JCTree.JCFieldAccess valueOfAccess =
(JCTree.JCFieldAccess) maker.Select((JCTree.JCExpression) expr, valueOfMethod);
valueOfAccess.setType(methodType);
return valueOfAccess;
}
/** Returns the valueOf method of a boxed type such as Short or Float. */
public static Symbol.MethodSymbol getValueOfMethod(
ProcessingEnvironment env, TypeMirror boxedType) {
Symbol.MethodSymbol valueOfMethod = null;
TypeMirror unboxedType = env.getTypeUtils().unboxedType(boxedType);
TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement();
for (ExecutableElement method :
ElementFilter.methodsIn(env.getElementUtils().getAllMembers(boxedElement))) {
if (method.getSimpleName().contentEquals("valueOf")) {
List<? extends VariableElement> params = method.getParameters();
if (params.size() == 1
&& env.getTypeUtils().isSameType(params.get(0).asType(), unboxedType)) {
valueOfMethod = (Symbol.MethodSymbol) method;
}
}
}
assert valueOfMethod != null
: "@AssumeAssertion(nullness): no valueOf method declared for boxed type";
return valueOfMethod;
}
/**
* Builds an AST Tree to access the *Value() method of a boxed type such as Short or Float, where
* * is the corresponding primitive type (i.e. shortValue or floatValue).
*
* @param expr an expression whose type is a boxed type
* @return a MemberSelectTree that accesses the *Value() method of the expression
*/
public MemberSelectTree buildPrimValueMethodAccess(Tree expr) {
TypeMirror boxedType = TreeUtils.typeOf(expr);
TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement();
assert TypesUtils.isBoxedPrimitive(boxedType);
TypeMirror unboxedType = modelTypes.unboxedType(boxedType);
// Find the *Value() method of the boxed type
String primValueName = unboxedType.toString() + "Value";
Symbol.MethodSymbol primValueMethod = null;
for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(boxedElement))) {
if (method.getSimpleName().contentEquals(primValueName) && method.getParameters().isEmpty()) {
primValueMethod = (Symbol.MethodSymbol) method;
}
}
assert primValueMethod != null
: "@AssumeAssertion(nullness): no *Value method declared for boxed type";
Type.MethodType methodType = (Type.MethodType) primValueMethod.asType();
JCTree.JCFieldAccess primValueAccess =
(JCTree.JCFieldAccess) maker.Select((JCTree.JCExpression) expr, primValueMethod);
primValueAccess.setType(methodType);
return primValueAccess;
}
/** Map public AST Tree.Kinds to internal javac JCTree.Tags. */
public JCTree.Tag kindToTag(Tree.Kind kind) {
switch (kind) {
case AND:
return JCTree.Tag.BITAND;
case AND_ASSIGNMENT:
return JCTree.Tag.BITAND_ASG;
case ANNOTATION:
return JCTree.Tag.ANNOTATION;
case ANNOTATION_TYPE:
return JCTree.Tag.TYPE_ANNOTATION;
case ARRAY_ACCESS:
return JCTree.Tag.INDEXED;
case ARRAY_TYPE:
return JCTree.Tag.TYPEARRAY;
case ASSERT:
return JCTree.Tag.ASSERT;
case ASSIGNMENT:
return JCTree.Tag.ASSIGN;
case BITWISE_COMPLEMENT:
return JCTree.Tag.COMPL;
case BLOCK:
return JCTree.Tag.BLOCK;
case BREAK:
return JCTree.Tag.BREAK;
case CASE:
return JCTree.Tag.CASE;
case CATCH:
return JCTree.Tag.CATCH;
case CLASS:
return JCTree.Tag.CLASSDEF;
case CONDITIONAL_AND:
return JCTree.Tag.AND;
case CONDITIONAL_EXPRESSION:
return JCTree.Tag.CONDEXPR;
case CONDITIONAL_OR:
return JCTree.Tag.OR;
case CONTINUE:
return JCTree.Tag.CONTINUE;
case DIVIDE:
return JCTree.Tag.DIV;
case DIVIDE_ASSIGNMENT:
return JCTree.Tag.DIV_ASG;
case DO_WHILE_LOOP:
return JCTree.Tag.DOLOOP;
case ENHANCED_FOR_LOOP:
return JCTree.Tag.FOREACHLOOP;
case EQUAL_TO:
return JCTree.Tag.EQ;
case EXPRESSION_STATEMENT:
return JCTree.Tag.EXEC;
case FOR_LOOP:
return JCTree.Tag.FORLOOP;
case GREATER_THAN:
return JCTree.Tag.GT;
case GREATER_THAN_EQUAL:
return JCTree.Tag.GE;
case IDENTIFIER:
return JCTree.Tag.IDENT;
case IF:
return JCTree.Tag.IF;
case IMPORT:
return JCTree.Tag.IMPORT;
case INSTANCE_OF:
return JCTree.Tag.TYPETEST;
case LABELED_STATEMENT:
return JCTree.Tag.LABELLED;
case LEFT_SHIFT:
return JCTree.Tag.SL;
case LEFT_SHIFT_ASSIGNMENT:
return JCTree.Tag.SL_ASG;
case LESS_THAN:
return JCTree.Tag.LT;
case LESS_THAN_EQUAL:
return JCTree.Tag.LE;
case LOGICAL_COMPLEMENT:
return JCTree.Tag.NOT;
case MEMBER_SELECT:
return JCTree.Tag.SELECT;
case METHOD:
return JCTree.Tag.METHODDEF;
case METHOD_INVOCATION:
return JCTree.Tag.APPLY;
case MINUS:
return JCTree.Tag.MINUS;
case MINUS_ASSIGNMENT:
return JCTree.Tag.MINUS_ASG;
case MODIFIERS:
return JCTree.Tag.MODIFIERS;
case MULTIPLY:
return JCTree.Tag.MUL;
case MULTIPLY_ASSIGNMENT:
return JCTree.Tag.MUL_ASG;
case NEW_ARRAY:
return JCTree.Tag.NEWARRAY;
case NEW_CLASS:
return JCTree.Tag.NEWCLASS;
case NOT_EQUAL_TO:
return JCTree.Tag.NE;
case OR:
return JCTree.Tag.BITOR;
case OR_ASSIGNMENT:
return JCTree.Tag.BITOR_ASG;
case PARENTHESIZED:
return JCTree.Tag.PARENS;
case PLUS:
return JCTree.Tag.PLUS;
case PLUS_ASSIGNMENT:
return JCTree.Tag.PLUS_ASG;
case POSTFIX_DECREMENT:
return JCTree.Tag.POSTDEC;
case POSTFIX_INCREMENT:
return JCTree.Tag.POSTINC;
case PREFIX_DECREMENT:
return JCTree.Tag.PREDEC;
case PREFIX_INCREMENT:
return JCTree.Tag.PREINC;
case REMAINDER:
return JCTree.Tag.MOD;
case REMAINDER_ASSIGNMENT:
return JCTree.Tag.MOD_ASG;
case RETURN:
return JCTree.Tag.RETURN;
case RIGHT_SHIFT:
return JCTree.Tag.SR;
case RIGHT_SHIFT_ASSIGNMENT:
return JCTree.Tag.SR_ASG;
case SWITCH:
return JCTree.Tag.SWITCH;
case SYNCHRONIZED:
return JCTree.Tag.SYNCHRONIZED;
case THROW:
return JCTree.Tag.THROW;
case TRY:
return JCTree.Tag.TRY;
case TYPE_CAST:
return JCTree.Tag.TYPECAST;
case TYPE_PARAMETER:
return JCTree.Tag.TYPEPARAMETER;
case UNARY_MINUS:
return JCTree.Tag.NEG;
case UNARY_PLUS:
return JCTree.Tag.POS;
case UNION_TYPE:
return JCTree.Tag.TYPEUNION;
case UNSIGNED_RIGHT_SHIFT:
return JCTree.Tag.USR;
case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
return JCTree.Tag.USR_ASG;
case VARIABLE:
return JCTree.Tag.VARDEF;
case WHILE_LOOP:
return JCTree.Tag.WHILELOOP;
case XOR:
return JCTree.Tag.BITXOR;
case XOR_ASSIGNMENT:
return JCTree.Tag.BITXOR_ASG;
default:
return JCTree.Tag.NO_TAG;
}
}
/**
* Builds an AST Tree to perform a binary operation.
*
* @param type result type of the operation
* @param op AST Tree operator
* @param left the left operand tree
* @param right the right operand tree
* @return a Tree representing "left &lt; right"
*/
public BinaryTree buildBinary(
TypeMirror type, Tree.Kind op, ExpressionTree left, ExpressionTree right) {
JCTree.Tag jcOp = kindToTag(op);
JCTree.JCBinary binary =
maker.Binary(jcOp, (JCTree.JCExpression) left, (JCTree.JCExpression) right);
binary.setType((Type) type);
return binary;
}
/**
* Builds an AST Tree to create a new array with initializers.
*
* @param componentType component type of the new array
* @param elems expression trees of initializers
* @return a NewArrayTree to create a new array with initializers
*/
public NewArrayTree buildNewArray(TypeMirror componentType, List<ExpressionTree> elems) {
List<JCExpression> exprs = CollectionsPlume.mapList(JCExpression.class::cast, elems);
JCTree.JCNewArray newArray =
maker.NewArray(
(JCTree.JCExpression) buildClassUse(((Type) componentType).tsym),
com.sun.tools.javac.util.List.nil(),
com.sun.tools.javac.util.List.from(exprs));
newArray.setType(javacTypes.makeArrayType((Type) componentType));
return newArray;
}
}