package org.checkerframework.javacutil.trees; 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.Name; 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.InternalUtils; import org.checkerframework.javacutil.TypesUtils; 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.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.TreeInfo; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Names; /** * 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(InternalUtils.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))) { Name methodName = method.getSimpleName(); if (method.getParameters().size() == 0) { if (methodName.contentEquals("iterator")) { iteratorMethod = (Symbol.MethodSymbol)method; } } } assert iteratorMethod != null : "no iterator method declared for expression type"; Type.MethodType methodType = (Type.MethodType)iteratorMethod.asType(); Symbol.TypeSymbol methodClass = methodType.asElement(); DeclaredType iteratorType = (DeclaredType)methodType.getReturnType(); TypeMirror elementType; if (iteratorType.getTypeArguments().size() > 0) { 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.<Type>nil(), (Type)iteratorType, com.sun.tools.javac.util.List.<Type>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)InternalUtils.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))) { Name methodName = method.getSimpleName(); if (method.getParameters().size() == 0) { if (methodName.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)InternalUtils.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))) { Name methodName = method.getSimpleName(); if (method.getParameters().size() == 0) { if (methodName.contentEquals("next")) { nextMethod = (Symbol.MethodSymbol)method; } } } assert nextMethod != null : "no next method declared for expression type"; Type.MethodType methodType = (Type.MethodType)nextMethod.asType(); Symbol.TypeSymbol methodClass = methodType.asElement(); Type elementType; if (exprType.getTypeArguments().size() > 0) { elementType = (Type)exprType.getTypeArguments().get(0); } else { elementType = symtab.objectType; } // 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.<Type>nil(), elementType, com.sun.tools.javac.util.List.<Type>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)InternalUtils.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)InternalUtils.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 < 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)InternalUtils.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 = InternalUtils.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))) { Name methodName = method.getSimpleName(); if (methodName.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 : "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 = InternalUtils.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))) { Name methodName = method.getSimpleName(); if (methodName.contentEquals(primValueName) && method.getParameters().size() == 0) { primValueMethod = (Symbol.MethodSymbol)method; } } assert primValueMethod != null : "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 < 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; } }