/* * 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.treeshaker; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Table; import com.google.devtools.j2objc.util.CodeReferenceMap; import com.google.devtools.j2objc.util.CodeReferenceMap.Builder; import com.google.devtools.j2objc.util.ElementUtil; import com.google.devtools.j2objc.util.ErrorUtil; import com.google.devtools.j2objc.util.TranslationEnvironment; import com.google.devtools.treeshaker.ElementReferenceMapper.ClassReferenceNode; import com.google.devtools.treeshaker.ElementReferenceMapper.MethodReferenceNode; import com.google.devtools.treeshaker.ElementReferenceMapper.ReferenceNode; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; /** * UnusedCodeTracker traverses all elements of its elementReferenceMap and determines unused code. * * @author Priyank Malvania */ public class UnusedCodeTracker { private final TranslationEnvironment env; private final HashMap<String, ReferenceNode> elementReferenceMap; private final HashMap<String, Set<String>> overrideMap; private final Set<String> staticSet; private final Set<String> rootSet = new HashSet<String>(); private final Set<MethodReferenceNode> declaredSet = new HashSet<MethodReferenceNode>(); public UnusedCodeTracker(TranslationEnvironment env, HashMap<String, ReferenceNode> elementReferenceMap, Set<String> staticSet, HashMap<String, Set<String>> overrideMap) { Preconditions.checkNotNull(env); Preconditions.checkNotNull(elementReferenceMap); Preconditions.checkNotNull(staticSet); this.env = env; this.elementReferenceMap = elementReferenceMap; this.staticSet = staticSet; this.overrideMap = overrideMap; } /** * Since the MethodInvocation node cannot currently detect invocations of overriding methods, * (it only detects the top-level method being invoked), this method allows treeshaker to track * which methods are being overridden. For all relevant methods (that are declared but not * invoked), checks all other methods with the same overrideID in the overrideMap, and compares * each pair with the ElementUtil.overrides method. */ public void mapOverridingMethods() { for (String key : elementReferenceMap.keySet()) { ReferenceNode node = elementReferenceMap.get(key); if (node instanceof MethodReferenceNode) { MethodReferenceNode methodNode = (MethodReferenceNode) node; if (methodNode.declared && !methodNode.invoked) { declaredSet.add(methodNode); } } } for (MethodReferenceNode derivedNode : declaredSet) { String overrideID = ElementReferenceMapper.stitchOverrideMethodIdentifier( derivedNode.methodElement, env.typeUtil()); assert(overrideMap.get(overrideID) != null); for (String otherID : overrideMap.get(overrideID)) { MethodReferenceNode baseNode = ((MethodReferenceNode) elementReferenceMap.get(otherID)); if (env.elementUtil().overrides(derivedNode.methodElement, baseNode.methodElement, ElementUtil.getDeclaringClass(derivedNode.methodElement))) { baseNode.overridingMethods.add(derivedNode.getUniqueID()); } } } } /** * Do tree shaker traversal with staticSet cached in class (because no input root elements). */ public void markUsedElements() { markUsedElements(staticSet); } /** * Add to root set, methods from CodeReferenceMap and also all public methods in input classes. * Then, do tree shaker traversal starting from this root set. * @param publicRootSet: CodeReferenceMap with public root methods and classes. */ //TODO(malvania): Current paradigm: All methods in input CodeReferenceMap are assumed to be // public roots to traverse from. //Classes in input CodeReferenceMap here allow user to add Dynamically Loaded Classes and keep // their public methods in the public root set. //In the future, when we add support for libraries, we will want to include protected methods // of those library classes as well, so we should add "|| ElementUtil.isProtected(method)" after // the isPublic check. public void markUsedElements(CodeReferenceMap publicRootSet) { if (publicRootSet == null) { markUsedElements(); return; } //Add all public methods in publicRootClasses to root set for (String clazz : publicRootSet.getReferencedClasses()) { ClassReferenceNode classNode = (ClassReferenceNode) elementReferenceMap .get(ElementReferenceMapper.stitchClassIdentifier(clazz)); assert(classNode != null); Iterable<ExecutableElement> methods = ElementUtil.getMethods(classNode.classElement); for (ExecutableElement method : methods) { if (ElementUtil.isPublic(method)) { rootSet.add(ElementReferenceMapper.stitchMethodIdentifier(method, env.typeUtil(), env.elementUtil())); } } } //Add input root methods to static set for (Table.Cell<String, String, ImmutableSet<String>> cell : publicRootSet .getReferencedMethods().cellSet()) { String clazzName = cell.getRowKey(); String methodName = cell.getColumnKey(); for (String signature : cell.getValue()) { rootSet.add(ElementReferenceMapper .stitchMethodIdentifier(clazzName, methodName, signature)); } } markUsedElements(staticSet); markUsedElements(rootSet); } /** * Do tree shaker traversal starting from input root set of node. * @param publicRootSet: Set of String identifiers for the root methods to start traversal from. */ public void markUsedElements(Set<String> publicRootSet) { for (String publicRoot : publicRootSet) { traverseMethod(publicRoot); } } /** * Traverses the method invocation graph created by ElementReferenceMapper, and marks all methods * that are reachable from the inputRootSet. Also covers all methods that possibly override these * called methods. * @param methodID */ public void traverseMethod(String methodID) { MethodReferenceNode node = (MethodReferenceNode) elementReferenceMap.get(methodID); if (node == null) { //TODO(malvania): This might never be reached, because we create a node for every method, // both invoked and declared. ErrorUtil.warning("Encountered .class method while accessing: " + methodID); return; } if (node.reachable) { return; } node.reachable = true; markParentClasses(ElementUtil.getDeclaringClass(node.methodElement)); for (String invokedMethodID : node.invokedMethods) { traverseMethod(invokedMethodID); } for (String overrideMethodID : node.overridingMethods) { traverseMethod(overrideMethodID); } } /** * Mark all ancestor classes of (sub)class as used */ public void markParentClasses(TypeElement type) { while (type != null) { String typeID = ElementReferenceMapper.stitchClassIdentifier(type, env.elementUtil()); ReferenceNode node = elementReferenceMap.get(typeID); if (node == null) { ErrorUtil.warning("Encountered .class parent class while accessing: " + typeID); return; } if (node.reachable) { return; } node.reachable = true; if (ElementUtil.isStatic(type)) { return; } type = ElementUtil.getDeclaringClass(type); } } public CodeReferenceMap buildTreeShakerMap() { Builder treeShakerMap = CodeReferenceMap.builder(); for (ReferenceNode node : elementReferenceMap.values()) { if (node.isDead()) { node.addToBuilder(treeShakerMap); } } return treeShakerMap.build(); } }