// 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.LiteralTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.ParenthesizedTree;
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.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.TreeScanner;
import com.sun.tools.javac.util.List;
/**
* Prunes AST nodes that are not required for header compilation.
*
*
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.
*
*
Specifically:
*
*
* - method bodies
*
- class and instance initializer blocks
*
- initializers of definitely non-constant fields
*
*/
static void prune(JCTree tree) {
tree.accept(PRUNING_VISITOR);
}
/** A {@link TreeScanner} that deletes method bodies and blocks from the AST. */
private static final TreeScanner PRUNING_VISITOR =
new TreeScanner() {
@Override
public void visitMethodDef(JCMethodDecl tree) {
if (tree.body == null) {
return;
}
tree.body.stats = com.sun.tools.javac.util.List.nil();
}
@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
return;
}
// drop field initializers unless the field looks like a JLS §4.12.4 constant variable
if (isConstantVariable(tree)) {
return;
}
tree.init = null;
}
};
private static boolean isConstantVariable(JCVariableDecl tree) {
if ((tree.mods.flags & Flags.FINAL) != Flags.FINAL) {
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.
*
* 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 CONSTANT_VISITOR =
new SimpleTreeVisitor(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;
}
};
}