/*
* 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.j2objc.translate;
import com.google.devtools.j2objc.ast.AnnotationTypeDeclaration;
import com.google.devtools.j2objc.ast.BodyDeclaration;
import com.google.devtools.j2objc.ast.CompilationUnit;
import com.google.devtools.j2objc.ast.EnumDeclaration;
import com.google.devtools.j2objc.ast.FieldDeclaration;
import com.google.devtools.j2objc.ast.MethodDeclaration;
import com.google.devtools.j2objc.ast.TypeDeclaration;
import com.google.devtools.j2objc.ast.UnitTreeVisitor;
import com.google.devtools.j2objc.ast.VariableDeclarationFragment;
import com.google.devtools.j2objc.util.CodeReferenceMap;
import com.google.devtools.j2objc.util.ElementUtil;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.List;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
/**
* Updates the Java AST to remove methods and classes reported as dead
* by a ProGuard usage report.
*
* @author Daniel Connelly
*/
public class DeadCodeEliminator extends UnitTreeVisitor {
private final CodeReferenceMap deadCodeMap;
public DeadCodeEliminator(CompilationUnit unit, CodeReferenceMap deadCodeMap) {
super(unit);
this.deadCodeMap = deadCodeMap;
}
@Override
public void endVisit(TypeDeclaration node) {
TypeElement type = node.getTypeElement();
eliminateDeadCode(type, node.getBodyDeclarations());
// Also strip supertypes.
if (deadCodeMap.containsClass(elementUtil.getBinaryName(type))) {
node.stripSupertypes();
}
}
@Override
public void endVisit(EnumDeclaration node) {
TypeElement type = node.getTypeElement();
eliminateDeadCode(type, node.getBodyDeclarations());
if (deadCodeMap.containsClass(elementUtil.getBinaryName(type))) {
// Dead enum means none of the constants are ever used, so they can all be deleted.
node.getEnumConstants().clear();
node.stripSuperInterfaces();
}
}
@Override
public void endVisit(AnnotationTypeDeclaration node) {
TypeElement type = node.getTypeElement();
if (!ElementUtil.isRuntimeAnnotation(type)) {
eliminateDeadCode(type, node.getBodyDeclarations());
}
}
/**
* Remove dead members from a type.
*/
private void eliminateDeadCode(TypeElement type, List<BodyDeclaration> decls) {
String clazz = elementUtil.getBinaryName(type);
if (deadCodeMap.containsClass(clazz)) {
stripClass(decls);
} else {
removeDeadMethods(clazz, decls);
removeDeadFields(clazz, decls);
}
}
private void stripClass(List<BodyDeclaration> decls) {
for (Iterator<BodyDeclaration> iter = decls.iterator(); iter.hasNext(); ) {
BodyDeclaration decl = iter.next();
// Do not strip interfaces or static nested classes. They are independent of the dead class,
// and even if they are dead, they may still be referenced by other classes.
if (decl instanceof TypeDeclaration) {
TypeElement type = ((TypeDeclaration) decl).getTypeElement();
if (type.getKind().isInterface() || ElementUtil.isStatic(type)) {
endVisit((TypeDeclaration) decl);
continue;
}
}
if (!isInlinableConstant(decl)) {
if (decl instanceof MethodDeclaration) {
unit.setHasIncompleteProtocol();
}
iter.remove();
}
}
}
private boolean isInlinableConstant(BodyDeclaration decl) {
if (!(decl instanceof FieldDeclaration)) {
return false;
}
int modifiers = decl.getModifiers();
if (!Modifier.isStatic(modifiers) || !Modifier.isFinal(modifiers)
|| Modifier.isPrivate(modifiers)) {
return false;
}
TypeMirror type = ((FieldDeclaration) decl).getTypeMirror();
if (!(type.getKind().isPrimitive() || typeUtil.isString(type))) {
return false;
}
// Only when every fragment has constant value do we say this is inlinable.
for (VariableDeclarationFragment fragment : ((FieldDeclaration) decl).getFragments()) {
if (fragment.getVariableElement().getConstantValue() == null) {
return false;
}
}
return true;
}
/**
* Remove dead methods from a type's body declarations.
*/
private void removeDeadMethods(String clazz, List<BodyDeclaration> declarations) {
Iterator<BodyDeclaration> declarationsIter = declarations.iterator();
while (declarationsIter.hasNext()) {
BodyDeclaration declaration = declarationsIter.next();
if (declaration instanceof MethodDeclaration) {
MethodDeclaration method = (MethodDeclaration) declaration;
// Need to keep native methods because otherwise the OCNI content will
// be emitted without a surrounding method.
// TODO(kstanger): Remove the method and its OCNI comment.
if (Modifier.isNative(method.getModifiers())) {
continue;
}
ExecutableElement elem = method.getExecutableElement();
String name = typeUtil.getReferenceName(elem);
String signature = typeUtil.getReferenceSignature(elem);
if (deadCodeMap.containsMethod(clazz, name, signature)) {
if (method.isConstructor()) {
deadCodeMap.addConstructorRemovedClass(clazz);
}
declarationsIter.remove();
}
}
}
}
/**
* Deletes non-constant dead fields from a type's body declarations list.
*/
private void removeDeadFields(String clazz, List<BodyDeclaration> declarations) {
Iterator<BodyDeclaration> declarationsIter = declarations.iterator();
while (declarationsIter.hasNext()) {
BodyDeclaration declaration = declarationsIter.next();
if (declaration instanceof FieldDeclaration) {
FieldDeclaration field = (FieldDeclaration) declaration;
Iterator<VariableDeclarationFragment> fragmentsIter = field.getFragments().iterator();
while (fragmentsIter.hasNext()) {
VariableDeclarationFragment fragment = fragmentsIter.next();
// Don't delete any constants because we can't detect their use.
VariableElement var = fragment.getVariableElement();
if (var.getConstantValue() == null
&& deadCodeMap.containsField(clazz, ElementUtil.getName(var))) {
fragmentsIter.remove();
}
}
if (field.getFragments().isEmpty()) {
declarationsIter.remove();
}
}
}
}
}