// Copyright 2016 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.java.turbine.javac; import static com.google.common.base.MoreObjects.firstNonNull; import com.sun.source.tree.BinaryTree; import com.sun.source.tree.ConditionalExpressionTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.LambdaExpressionTree.BodyKind; import com.sun.source.tree.LiteralTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.ParenthesizedTree; import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.TypeCastTree; import com.sun.source.tree.UnaryTree; import com.sun.source.util.SimpleTreeVisitor; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCLambda; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCThrow; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.TreeScanner; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Name; /** * Prunes AST nodes that are not required for header compilation. * * <p>Used by Turbine after parsing and before all subsequent phases to avoid * doing unnecessary work. */ public class TreePruner { /** * Prunes AST nodes that are not required for header compilation. * * <p>Specifically: * * <ul> * <li>method bodies * <li>class and instance initializer blocks * <li>initializers of definitely non-constant fields * </ul> */ static void prune(Context context, JCTree tree) { tree.accept(new PruningVisitor(context)); } /** A {@link TreeScanner} that deletes method bodies and blocks from the AST. */ private static class PruningVisitor extends TreeScanner { private final TreeMaker make; private final Symtab symtab; PruningVisitor(Context context) { this.make = TreeMaker.instance(context); this.symtab = Symtab.instance(context); } JCClassDecl enclClass = null; @Override public void visitClassDef(JCClassDecl tree) { JCClassDecl prev = enclClass; enclClass = tree; try { super.visitClassDef(tree); } finally { enclClass = prev; } } @Override public void visitMethodDef(JCMethodDecl tree) { if (tree.body == null) { return; } if (tree.getReturnType() == null && delegatingConstructor(tree.body.stats)) { // if the first statement of a constructor declaration delegates to another // constructor, it needs to be preserved to satisfy checks in Resolve tree.body.stats = com.sun.tools.javac.util.List.of(tree.body.stats.get(0)); return; } tree.body.stats = com.sun.tools.javac.util.List.nil(); } @Override public void visitLambda(JCLambda tree) { if (tree.getBodyKind() == BodyKind.STATEMENT) { JCExpression ident = make.at(tree).QualIdent(symtab.assertionErrorType.tsym); JCThrow throwTree = make.Throw(make.NewClass(null, List.nil(), ident, List.nil(), null)); tree.body = make.Block(0, List.of(throwTree)); } } @Override public void visitBlock(JCBlock tree) { tree.stats = List.nil(); } @Override public void visitVarDef(JCVariableDecl tree) { if ((tree.mods.flags & Flags.ENUM) == Flags.ENUM) { // javac desugars enum constants into fields during parsing super.visitVarDef(tree); return; } // drop field initializers unless the field looks like a JLS §4.12.4 constant variable if (isConstantVariable(enclClass, tree)) { return; } tree.init = null; } } private static boolean delegatingConstructor(List<JCStatement> stats) { if (stats.isEmpty()) { return false; } JCStatement stat = stats.get(0); if (stat.getKind() != Kind.EXPRESSION_STATEMENT) { return false; } JCExpression expr = ((JCExpressionStatement) stat).getExpression(); if (expr.getKind() != Kind.METHOD_INVOCATION) { return false; } JCExpression method = ((JCMethodInvocation) expr).getMethodSelect(); Name name; switch (method.getKind()) { case IDENTIFIER: name = ((JCIdent) method).getName(); break; case MEMBER_SELECT: name = ((JCFieldAccess) method).getIdentifier(); break; default: return false; } return name.contentEquals("this") || name.contentEquals("super"); } private static boolean isFinal(JCClassDecl enclClass, JCVariableDecl tree) { if ((tree.mods.flags & Flags.FINAL) == Flags.FINAL) { return true; } if (enclClass != null && (enclClass.mods.flags & (Flags.ANNOTATION | Flags.INTERFACE)) != 0) { // Fields in annotation declarations and interfaces are implicitly final return true; } return false; } private static boolean isConstantVariable(JCClassDecl enclClass, JCVariableDecl tree) { if (!isFinal(enclClass, tree)) { return false; } if (!constantType(tree.getType())) { return false; } if (tree.getInitializer() != null) { Boolean result = tree.getInitializer().accept(CONSTANT_VISITOR, null); if (result == null || !result) { return false; } } return true; } /** * Returns true iff the given tree could be the type name of a constant type. * * <p>This is a conservative over-approximation: an identifier named {@code String} * isn't necessarily a type name, but this is used at parse-time before types have * been attributed. */ private static boolean constantType(JCTree tree) { switch (tree.getKind()) { case PRIMITIVE_TYPE: return true; case IDENTIFIER: return tree.toString().contentEquals("String"); case MEMBER_SELECT: return tree.toString().contentEquals("java.lang.String"); default: return false; } } /** A visitor that identifies JLS §15.28 constant expressions. */ private static final SimpleTreeVisitor<Boolean, Void> CONSTANT_VISITOR = new SimpleTreeVisitor<Boolean, Void>(false) { @Override public Boolean visitConditionalExpression(ConditionalExpressionTree node, Void p) { return reduce( node.getCondition().accept(this, null), node.getTrueExpression().accept(this, null), node.getFalseExpression().accept(this, null)); } @Override public Boolean visitParenthesized(ParenthesizedTree node, Void p) { return node.getExpression().accept(this, null); } @Override public Boolean visitUnary(UnaryTree node, Void p) { switch (node.getKind()) { case UNARY_PLUS: case UNARY_MINUS: case BITWISE_COMPLEMENT: case LOGICAL_COMPLEMENT: break; default: // non-constant unary expression return false; } return node.getExpression().accept(this, null); } @Override public Boolean visitBinary(BinaryTree node, Void p) { switch (node.getKind()) { case MULTIPLY: case DIVIDE: case REMAINDER: case PLUS: case MINUS: case LEFT_SHIFT: case RIGHT_SHIFT: case UNSIGNED_RIGHT_SHIFT: case LESS_THAN: case LESS_THAN_EQUAL: case GREATER_THAN: case GREATER_THAN_EQUAL: case AND: case XOR: case OR: case CONDITIONAL_AND: case CONDITIONAL_OR: case EQUAL_TO: case NOT_EQUAL_TO: break; default: // non-constant binary expression return false; } return reduce( node.getLeftOperand().accept(this, null), node.getRightOperand().accept(this, null)); } @Override public Boolean visitTypeCast(TypeCastTree node, Void p) { return reduce( constantType((JCTree) node.getType()), node.getExpression().accept(this, null)); } @Override public Boolean visitMemberSelect(MemberSelectTree node, Void p) { return node.getExpression().accept(this, null); } @Override public Boolean visitIdentifier(IdentifierTree node, Void p) { // Assume all variables are constant variables. This is a conservative assumption, but // it's the best we can do with only syntactic information. return true; } @Override public Boolean visitLiteral(LiteralTree node, Void unused) { switch (node.getKind()) { case STRING_LITERAL: case INT_LITERAL: case LONG_LITERAL: case FLOAT_LITERAL: case DOUBLE_LITERAL: case BOOLEAN_LITERAL: case CHAR_LITERAL: return true; default: return false; } } public boolean reduce(Boolean... bx) { boolean r = true; for (Boolean b : bx) { r &= firstNonNull(b, false); } return r; } }; }