/* * Copyright 2008 Google Inc. * * 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.gwt.dev.jjs.impl; import com.google.gwt.dev.jjs.ast.Context; import com.google.gwt.dev.jjs.ast.JAbsentArrayDimension; import com.google.gwt.dev.jjs.ast.JArrayType; import com.google.gwt.dev.jjs.ast.JBinaryOperation; import com.google.gwt.dev.jjs.ast.JBinaryOperator; import com.google.gwt.dev.jjs.ast.JCastOperation; import com.google.gwt.dev.jjs.ast.JClassLiteral; import com.google.gwt.dev.jjs.ast.JClassType; import com.google.gwt.dev.jjs.ast.JConstructor; import com.google.gwt.dev.jjs.ast.JDeclarationStatement; import com.google.gwt.dev.jjs.ast.JDeclaredType; import com.google.gwt.dev.jjs.ast.JExpression; import com.google.gwt.dev.jjs.ast.JField; import com.google.gwt.dev.jjs.ast.JFieldRef; import com.google.gwt.dev.jjs.ast.JInterfaceType; import com.google.gwt.dev.jjs.ast.JLocal; import com.google.gwt.dev.jjs.ast.JLocalRef; import com.google.gwt.dev.jjs.ast.JMethod; import com.google.gwt.dev.jjs.ast.JMethodCall; import com.google.gwt.dev.jjs.ast.JNewArray; import com.google.gwt.dev.jjs.ast.JNewInstance; import com.google.gwt.dev.jjs.ast.JNode; import com.google.gwt.dev.jjs.ast.JParameter; import com.google.gwt.dev.jjs.ast.JParameterRef; import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JRunAsync; import com.google.gwt.dev.jjs.ast.JStringLiteral; import com.google.gwt.dev.jjs.ast.JType; import com.google.gwt.dev.jjs.ast.JVariable; import com.google.gwt.dev.jjs.ast.JVariableRef; import com.google.gwt.dev.jjs.ast.JVisitor; import com.google.gwt.dev.jjs.ast.js.JsniFieldRef; import com.google.gwt.dev.jjs.ast.js.JsniMethodBody; import com.google.gwt.dev.jjs.ast.js.JsniMethodRef; import com.google.gwt.dev.js.ast.JsContext; import com.google.gwt.dev.js.ast.JsFunction; import com.google.gwt.dev.js.ast.JsName; import com.google.gwt.dev.js.ast.JsNameRef; import com.google.gwt.dev.js.ast.JsVisitor; import com.google.gwt.dev.util.collect.Lists; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * This class finds out what code in a program is live based on starting * execution at a specified location. */ public class ControlFlowAnalyzer { /** * A callback for recording control-flow dependencies as they are discovered. * See {@link ControlFlowAnalyzer#setDependencyRecorder(DependencyRecorder)}. */ public interface DependencyRecorder { /** * Used to record the dependencies of a specific method. */ void methodIsLiveBecause(JMethod liveMethod, ArrayList<JMethod> dependencyChain); } /** * Marks as "referenced" any types, methods, and fields that are reachable. * Also marks as "instantiable" any classes and interfaces that can possibly * be instantiated. * * TODO(later): make RescueVisitor use less stack? */ private class RescueVisitor extends JVisitor { private final ArrayList<JMethod> curMethodStack = new ArrayList<JMethod>(); @Override public boolean visit(JArrayType type, Context ctx) { assert (referencedTypes.contains(type)); boolean isInstantiated = instantiatedTypes.contains(type); JType leafType = type.getLeafType(); int dims = type.getDims(); // Rescue my super array type boolean didSuperType = false; if (leafType instanceof JClassType) { JClassType superClass = ((JClassType) leafType).getSuperClass(); if (superClass != null) { // FooSub[] -> Foo[] rescue(program.getTypeArray(superClass, dims), true, isInstantiated); didSuperType = true; } } else if (leafType instanceof JInterfaceType) { // Intf[] -> Object[] rescue(program.getTypeArray(program.getTypeJavaLangObject(), dims), true, isInstantiated); didSuperType = true; } if (!didSuperType) { if (dims > 1) { // anything[][] -> Object[] rescue(program.getTypeArray(program.getTypeJavaLangObject(), dims - 1), true, isInstantiated); } else { // anything[] -> Object // But instead of Object, rescue the base Array implementation type. rescue(baseArrayType, true, isInstantiated); } } // Rescue super interface array types. if (leafType instanceof JDeclaredType) { JDeclaredType dLeafType = (JDeclaredType) leafType; for (JInterfaceType intfType : dLeafType.getImplements()) { JArrayType intfArray = program.getTypeArray(intfType, dims); rescue(intfArray, true, isInstantiated); } } return false; } @Override public boolean visit(JBinaryOperation x, Context ctx) { if (x.isAssignment() && x.getLhs() instanceof JFieldRef) { fieldsWritten.add(((JFieldRef) x.getLhs()).getField()); } // special string concat handling if ((x.getOp() == JBinaryOperator.CONCAT || x.getOp() == JBinaryOperator.ASG_CONCAT)) { rescueByConcat(x.getLhs().getType()); rescueByConcat(x.getRhs().getType()); } else if (x.getOp() == JBinaryOperator.ASG) { // Don't rescue variables that are merely assigned to and never read boolean doSkip = false; JExpression lhs = x.getLhs(); if (lhs.hasSideEffects() || isVolatileField(lhs)) { /* * If the lhs has side effects, skipping it would lose the side * effect. If the lhs is volatile, also keep it. This behavior * provides a useful idiom for test cases to prevent code from being * pruned. */ } else if (lhs instanceof JLocalRef) { // locals are ok to skip doSkip = true; } else if (lhs instanceof JParameterRef) { // parameters are ok to skip doSkip = true; } else if (lhs instanceof JFieldRef) { // fields must rescue the qualifier doSkip = true; JFieldRef fieldRef = (JFieldRef) lhs; JExpression instance = fieldRef.getInstance(); if (instance != null) { accept(instance); } } if (doSkip) { accept(x.getRhs()); return false; } } return true; } @Override public boolean visit(JCastOperation x, Context ctx) { // Rescue any JavaScriptObject type that is the target of a cast. JType targetType = x.getCastType(); if (program.typeOracle.canBeJavaScriptObject(targetType)) { rescue((JReferenceType) targetType, true, true); JType exprType = x.getExpr().getType(); if (program.typeOracle.isSingleJsoImpl(targetType)) { /* * It's a JSO interface, check if the source expr can be a live JSO 1) * source is java.lang.Object (JSO could have been assigned to it) 2) * source is JSO 3) source is SingleJSO interface whose implementor is * live */ if (program.getTypeJavaLangObject() == exprType || program.typeOracle.canBeJavaScriptObject(exprType)) { // source is JSO or SingleJso interface whose implementor is live JClassType jsoImplementor = program.typeOracle.getSingleJsoImpl((JReferenceType) targetType); rescue(jsoImplementor, true, true); } } } return true; } @Override public boolean visit(JClassLiteral x, Context ctx) { JField field = x.getField(); assert field != null; rescue(field); return true; } @Override public boolean visit(JClassType type, Context ctx) { assert (referencedTypes.contains(type)); boolean isInstantiated = instantiatedTypes.contains(type); // Rescue my super type rescue(type.getSuperClass(), true, isInstantiated); // Rescue my clinit (it won't ever be explicitly referenced) if (type.hasClinit()) { rescue(type.getMethods().get(0)); } // JLS 12.4.1: don't rescue my super interfaces just because I'm rescued. // However, if I'm instantiated, let's mark them as instantiated. for (JInterfaceType intfType : type.getImplements()) { rescue(intfType, false, isInstantiated); } rescueMembersIfInstantiable(type); return false; } @Override public boolean visit(JDeclarationStatement x, Context ctx) { /* * A declaration by itself doesn't rescue a local (even if it has an * initializer). Writes don't count, only reads. */ if (x.getInitializer() != null) { if (!isStaticFieldInitializedToLiteral(x.getVariableRef().getTarget())) { /* * Don't traverse literal initializers, because those become live when * the variable is accessed, not when its declaration runs. */ accept(x.getInitializer()); if (x.getVariableRef().getTarget() instanceof JField) { fieldsWritten.add((JField) x.getVariableRef().getTarget()); } } } // If the lhs is a field ref, we have to visit its qualifier. JVariableRef variableRef = x.getVariableRef(); if (variableRef instanceof JFieldRef) { JFieldRef fieldRef = (JFieldRef) variableRef; JExpression instance = fieldRef.getInstance(); if (instance != null) { accept(instance); } } return false; } @Override public boolean visit(JFieldRef ref, Context ctx) { JField target = ref.getField(); /* * JLS 12.4.1: references to static, non-final, or * non-compile-time-constant fields rescue the enclosing class. JDT * already folds in compile-time constants as literals, so we must rescue * the enclosing types for any static fields that make it here. */ if (target.isStatic()) { rescue(target.getEnclosingType(), true, false); } if (target.isStatic() || instantiatedTypes.contains(target.getEnclosingType())) { rescue(target); } else { // It's a field whose class is not instantiable if (!liveFieldsAndMethods.contains(target)) { membersToRescueIfTypeIsInstantiated.add(target); } } return true; } @Override public boolean visit(JInterfaceType type, Context ctx) { boolean isReferenced = referencedTypes.contains(type); boolean isInstantiated = instantiatedTypes.contains(type); assert (isReferenced || isInstantiated); // Rescue my clinit (it won't ever be explicitly referenced) if (type.hasClinit()) { rescue(type.getMethods().get(0)); } // JLS 12.4.1: don't rescue my super interfaces just because I'm rescued. // However, if I'm instantiated, let's mark them as instantiated. if (isInstantiated) { for (JInterfaceType intfType : type.getImplements()) { rescue(intfType, false, true); } } rescueMembersIfInstantiable(type); return false; } @Override public boolean visit(JLocalRef ref, Context ctx) { JLocal target = ref.getLocal(); rescue(target); return true; } @Override public boolean visit(final JMethod x, Context ctx) { JReferenceType enclosingType = x.getEnclosingType(); if (program.isJavaScriptObject(enclosingType)) { // Calls to JavaScriptObject types rescue those types. boolean instance = !x.isStatic() || program.isStaticImpl(x); rescue(enclosingType, true, instance); } else if (x.isStatic()) { // JLS 12.4.1: references to static methods rescue the enclosing class rescue(enclosingType, true, false); } if (x.isNative()) { // Manually rescue native parameter references final JsniMethodBody body = (JsniMethodBody) x.getBody(); final JsFunction func = body.getFunc(); new JsVisitor() { @Override public void endVisit(JsNameRef nameRef, JsContext ctx) { JsName ident = nameRef.getName(); if (ident != null) { // If we're referencing a parameter, rescue the associated // JParameter int index = func.getParameters().indexOf(ident.getStaticRef()); if (index != -1) { rescue(x.getParams().get(index)); } } } }.accept(func); } return true; } @Override public boolean visit(JMethodCall call, Context ctx) { JMethod method = call.getTarget(); if (call.isVolatile() && method == runAsyncOnsuccess) { /* * Note: In order to preserve code splitting, don't allow code flow from the * AsyncFragmentLoader implementation back into the * callback.onSuccess(). If we did, the rescue path would look like * JRunAsync -> AsyncFragmentLoader.runAsync() -> callback.onSuccess(). * This would completely defeat code splitting as all the code on the * other side of the barrier would become reachable. * * Code flow analysis is run separately on methods which implement * RunAsyncCallback.onSuccess() as top-level entry points. */ return true; } if (method.isStatic() || program.isJavaScriptObject(method.getEnclosingType()) || instantiatedTypes.contains(method.getEnclosingType())) { rescue(method); } else { // It's a virtual method whose class is not instantiable if (!liveFieldsAndMethods.contains(method)) { membersToRescueIfTypeIsInstantiated.add(method); } } if (argsToRescueIfParameterRead == null || method.canBePolymorphic() || call instanceof JsniMethodRef) { return true; } if (program.staticImplFor(method) != null) { // CleanUpRefsVisitor does not prune these params, must rescue. return true; } return rescueArgumentsIfParametersCanBeRead(call, method); } @Override public boolean visit(JNewArray newArray, Context ctx) { // rescue and instantiate the array type JArrayType arrayType = newArray.getArrayType(); if (newArray.dims != null) { // rescue my type and all the implicitly nested types (with fewer dims) int nDims = arrayType.getDims(); JType leafType = arrayType.getLeafType(); assert (newArray.dims.size() == nDims); for (int i = 0; i < nDims; ++i) { if (newArray.dims.get(i) instanceof JAbsentArrayDimension) { break; } rescue(program.getTypeArray(leafType, nDims - i), true, true); } } else { // just rescue my own specific type rescue(arrayType, true, true); } return true; } @Override public boolean visit(JNewInstance x, Context ctx) { // rescue and instantiate the target class! rescueAndInstantiate(x.getClassType()); return super.visit(x, ctx); } @Override public boolean visit(JParameterRef x, Context ctx) { // rescue the parameter for future pruning purposes rescue(x.getParameter()); return true; } @Override public boolean visit(JsniFieldRef x, Context ctx) { /* * SPECIAL: this could be an assignment that passes a value from * JavaScript into Java. */ if (x.isLvalue()) { maybeRescueJavaScriptObjectPassingIntoJava(x.getField().getType()); } // JsniFieldRef rescues as JFieldRef return visit((JFieldRef) x, ctx); } @Override public boolean visit(JsniMethodBody body, Context ctx) { liveStrings.addAll(body.getUsedStrings()); return true; } @Override public boolean visit(JsniMethodRef x, Context ctx) { /* * SPECIAL: each argument of the call passes a value from JavaScript into * Java. */ List<JParameter> params = x.getTarget().getParams(); for (int i = 0, c = params.size(); i < c; ++i) { JParameter param = params.get(i); maybeRescueJavaScriptObjectPassingIntoJava(param.getType()); /* * Because we're not currently tracking methods through JSNI, we need to * assume that it's not safe to prune parameters of a method referenced * as such. * * A better solution would be to perform basic escape analysis to ensure * that the function reference never escapes, or at minimum, ensure that * the method is immediately called after retrieving the method * reference. */ rescue(param); } // JsniMethodRef rescues as a JMethodCall if (x.getTarget() instanceof JConstructor) { // But if a constructor is targeted, there is an implicit 'new' op. JConstructor ctor = (JConstructor) x.getTarget(); rescueAndInstantiate(ctor.getEnclosingType()); } return visit((JMethodCall) x, ctx); } @Override public boolean visit(JStringLiteral literal, Context ctx) { liveStrings.add(literal.getValue()); // rescue and instantiate java.lang.String rescue(program.getTypeJavaLangString(), true, true); return true; } private boolean isStaticFieldInitializedToLiteral(JVariable var) { if (var instanceof JField) { JField field = (JField) var; return field.isStatic() && field.getLiteralInitializer() != null; } return false; } private boolean isVolatileField(JExpression x) { if (x instanceof JFieldRef) { JFieldRef xFieldRef = (JFieldRef) x; if (xFieldRef.getField().isVolatile()) { return true; } } return false; } private void maybeRescueClassLiteral(JReferenceType type) { if (liveFieldsAndMethods.contains(getClassMethod) || liveFieldsAndMethods.contains(getClassField)) { // getClass() already live so rescue class literal immediately rescue(program.getClassLiteralField(type)); } else { // getClass() not live yet, so mark for later rescue classLiteralsToBeRescuedIfGetClassIsLive.add(type); } } /** * Subclasses of JavaScriptObject are never instantiated directly. They are * implicitly created when a JSNI method passes a reference to an existing * JS object into Java code. If any point in the program can pass a value * from JS into Java which could potentially be cast to JavaScriptObject, we * must rescue JavaScriptObject. * * @param type The type of the value passing from Java to JavaScript. * @see com.google.gwt.core.client.JavaScriptObject */ private void maybeRescueJavaScriptObjectPassingIntoJava(JType type) { boolean doIt = false; if (program.typeOracle.canBeJavaScriptObject(type) || program.isJavaLangString(type)) { doIt = true; } else if (type instanceof JArrayType) { /* * Hackish: in our own JRE we sometimes create "not quite baked" arrays * in JavaScript for expediency. */ JArrayType arrayType = (JArrayType) type; JType elementType = arrayType.getElementType(); if (elementType instanceof JPrimitiveType || program.isJavaLangString(elementType) || program.typeOracle.canBeJavaScriptObject(elementType)) { doIt = true; } } if (doIt) { rescue((JReferenceType) type, true, true); if (program.typeOracle.isSingleJsoImpl(type)) { // Cast of JSO into SingleJso interface, rescue the implementor rescue(program.typeOracle.getSingleJsoImpl((JReferenceType) type), true, true); } } } private boolean rescue(JMethod method) { if (method != null) { if (!liveFieldsAndMethods.contains(method)) { liveFieldsAndMethods.add(method); membersToRescueIfTypeIsInstantiated.remove(method); if (dependencyRecorder != null) { curMethodStack.add(method); dependencyRecorder.methodIsLiveBecause(method, curMethodStack); } accept(method); if (dependencyRecorder != null) { curMethodStack.remove(curMethodStack.size() - 1); } if (method.isNative()) { /* * SPECIAL: returning from this method passes a value from * JavaScript into Java. */ maybeRescueJavaScriptObjectPassingIntoJava(method.getType()); } rescueOverridingMethods(method); if (method == getClassMethod) { rescueClassLiteralsIfGetClassIsLive(); } return true; } } return false; } private void rescue(JReferenceType type, boolean isReferenced, boolean isInstantiated) { if (type == null) { return; } /* * Track references and instantiability at the granularity of run-time * types. For example, ignore nullness. */ type = type.getUnderlyingType(); boolean doVisit = false; if (isInstantiated && !instantiatedTypes.contains(type)) { instantiatedTypes.add(type); maybeRescueClassLiteral(type); doVisit = true; } if (isReferenced && !referencedTypes.contains(type)) { referencedTypes.add(type); doVisit = true; } if (doVisit) { accept(type); if (type instanceof JDeclaredType) { for (JNode artificial : ((JDeclaredType) type).getArtificialRescues()) { if (artificial instanceof JReferenceType) { rescue((JReferenceType) artificial, true, true); } else if (artificial instanceof JVariable) { rescue((JVariable) artificial); } else if (artificial instanceof JMethod) { rescue((JMethod) artificial); } } } } } private void rescue(JVariable var) { if (var != null) { if (liveFieldsAndMethods.add(var)) { membersToRescueIfTypeIsInstantiated.remove(var); if (var == getClassField) { rescueClassLiteralsIfGetClassIsLive(); } if (isStaticFieldInitializedToLiteral(var)) { /* * Rescue literal initializers when the field is rescued, not when * the static initializer runs. This allows fields initialized to * string literals to only need the string literals when the field * itself becomes live. */ accept(((JField) var).getLiteralInitializer()); } else if (var instanceof JField && (program.getTypeClassLiteralHolder().equals(((JField) var).getEnclosingType()))) { /* * Rescue just slightly less than what would normally be rescued for * a field reference to the literal's field. Rescue the field * itself, and its initializer, but do NOT rescue the whole * enclosing class. That would pull in the clinit of that class, * which has initializers for all the class literals, which in turn * have all of the strings of all of the class names. * * This work is done in rescue() to allow JSNI references to class * literals (via the @Foo::class syntax) to correctly rescue class * literal initializers. * * TODO: Model ClassLiteral access a different way to avoid special * handling. See * Pruner.transformToNullFieldRef()/transformToNullMethodCall(). */ JField field = (JField) var; accept(field.getInitializer()); referencedTypes.add(field.getEnclosingType()); liveFieldsAndMethods.add(field.getEnclosingType().getMethods().get(0)); } else if (argsToRescueIfParameterRead != null && var instanceof JParameter) { List<JExpression> list = argsToRescueIfParameterRead.remove(var); if (list != null) { for (JExpression arg : list) { this.accept(arg); } } } } } } private void rescueAndInstantiate(JClassType type) { rescue(type, true, true); } /** * The code is very tightly tied to the behavior of * Pruner.CleanupRefsVisitor. CleanUpRefsVisitor will prune unread * parameters, and also prune any matching arguments that don't have side * effects. We want to make control flow congruent to pruning, to avoid the * need to iterate over Pruner until reaching a stable point, so we avoid * actually rescuing such arguments until/unless the parameter is read. */ private boolean rescueArgumentsIfParametersCanBeRead(JMethodCall call, JMethod method) { if (call.getInstance() != null) { // Explicitly visit instance since we're returning false below. this.accept(call.getInstance()); } List<JExpression> args = call.getArgs(); List<JParameter> params = method.getParams(); int i = 0; for (int c = params.size(); i < c; ++i) { JExpression arg = args.get(i); JParameter param = params.get(i); if (arg.hasSideEffects() || liveFieldsAndMethods.contains(param)) { this.accept(arg); continue; } List<JExpression> list = argsToRescueIfParameterRead.get(param); if (list == null) { argsToRescueIfParameterRead.put(param, Lists.create(arg)); } else { argsToRescueIfParameterRead.put(param, Lists.add(list, arg)); } } // Visit any "extra" arguments that exceed the param list. for (int c = args.size(); i < c; ++i) { this.accept(args.get(i)); } return false; } /** * Handle special rescues needed implicitly to support concat. */ private void rescueByConcat(JType type) { JPrimitiveType charType = program.getTypePrimitiveChar(); JClassType stringType = program.getTypeJavaLangString(); if (type instanceof JReferenceType && !program.typeOracle.canTriviallyCast((JReferenceType) type, stringType) && type != program.getTypeNull()) { /* * Any reference types (except String, which works by default) that take * part in a concat must rescue java.lang.Object.toString(). * * TODO: can we narrow the focus by walking up the type hierarchy or * doing explicit toString calls? */ JMethod toStringMethod = program.getIndexedMethod("Object.toString"); rescue(toStringMethod); } else if (type == charType) { /* * Characters must rescue String.valueOf(char) */ if (stringValueOfChar == null) { for (JMethod meth : stringType.getMethods()) { if (meth.getName().equals("valueOf")) { List<JType> params = meth.getOriginalParamTypes(); if (params.size() == 1) { if (params.get(0) == charType) { stringValueOfChar = meth; break; } } } } assert (stringValueOfChar != null); } rescue(stringValueOfChar); } } private void rescueClassLiteralsIfGetClassIsLive() { if (classLiteralsToBeRescuedIfGetClassIsLive != null) { // guard against re-entrant calls. This only needs to run once. Set<JReferenceType> toRescue = classLiteralsToBeRescuedIfGetClassIsLive; classLiteralsToBeRescuedIfGetClassIsLive = null; for (JReferenceType classLit : toRescue) { maybeRescueClassLiteral(classLit); } } } /** * If the type is instantiable, rescue any of its virtual methods that a * previously seen method call could call. */ private void rescueMembersIfInstantiable(JDeclaredType type) { if (instantiatedTypes.contains(type)) { for (JMethod method : type.getMethods()) { if (!method.isStatic()) { if (membersToRescueIfTypeIsInstantiated.contains(method)) { rescue(method); continue; } } } for (JField field : type.getFields()) { if (!field.isStatic()) { if (membersToRescueIfTypeIsInstantiated.contains(field)) { rescue(field); continue; } } } } } /** * Assume that <code>method</code> is live. Rescue any overriding methods * that might be called if <code>method</code> is called through virtual * dispatch. */ private void rescueOverridingMethods(JMethod method) { if (!method.isStatic()) { List<JMethod> overriders = methodsThatOverrideMe.get(method); if (overriders != null) { for (JMethod overrider : overriders) { if (liveFieldsAndMethods.contains(overrider)) { // The override is already alive, do nothing. } else if (instantiatedTypes.contains(overrider.getEnclosingType())) { // The enclosing class is alive, make my override reachable. rescue(overrider); } else { // The enclosing class is not yet alive, put override in limbo. membersToRescueIfTypeIsInstantiated.add(overrider); } } } } } } /** * These are arguments that have not yet been rescued on account of the * associated parameter not having been read yet. If the parameter becomes * read, we will need to rescue the associated arguments. See comments in * {@link #rescueArgumentsIfParametersCanBeRead}. */ private Map<JParameter, List<JExpression>> argsToRescueIfParameterRead; private final JMethod asyncFragmentOnLoad; private final JDeclaredType baseArrayType; /** * Schrodinger set of classLiterals to be rescued if type is instantiated AND getClass() * is live. */ private Set<JReferenceType> classLiteralsToBeRescuedIfGetClassIsLive = new HashSet<JReferenceType>(); private DependencyRecorder dependencyRecorder; private Set<JField> fieldsWritten = new HashSet<JField>(); private Set<JReferenceType> instantiatedTypes = new HashSet<JReferenceType>(); private Set<JNode> liveFieldsAndMethods = new HashSet<JNode>(); private Set<String> liveStrings = new HashSet<String>(); /** * Schrodinger's members... aka "limbo". :) These are instance methods and * fields that seem to be reachable, only their enclosing type is * uninstantiable. We place these methods into purgatory until/unless the * enclosing type is found to be instantiable. */ private Set<JNode> membersToRescueIfTypeIsInstantiated = new HashSet<JNode>(); /** * A precomputed map of all instance methods onto a set of methods that * override each key method. */ private Map<JMethod, List<JMethod>> methodsThatOverrideMe; private final JField getClassField; private final JMethod getClassMethod; private final JProgram program; private Set<JReferenceType> referencedTypes = new HashSet<JReferenceType>(); private final RescueVisitor rescuer = new RescueVisitor(); private final JMethod runAsyncOnsuccess; private JMethod stringValueOfChar = null; public ControlFlowAnalyzer(ControlFlowAnalyzer cfa) { program = cfa.program; asyncFragmentOnLoad = cfa.asyncFragmentOnLoad; runAsyncOnsuccess = cfa.runAsyncOnsuccess; baseArrayType = cfa.baseArrayType; fieldsWritten = new HashSet<JField>(cfa.fieldsWritten); instantiatedTypes = new HashSet<JReferenceType>(cfa.instantiatedTypes); liveFieldsAndMethods = new HashSet<JNode>(cfa.liveFieldsAndMethods); referencedTypes = new HashSet<JReferenceType>(cfa.referencedTypes); stringValueOfChar = cfa.stringValueOfChar; liveStrings = new HashSet<String>(cfa.liveStrings); membersToRescueIfTypeIsInstantiated = new HashSet<JNode>(cfa.membersToRescueIfTypeIsInstantiated); if (cfa.argsToRescueIfParameterRead != null) { argsToRescueIfParameterRead = new HashMap<JParameter, List<JExpression>>(cfa.argsToRescueIfParameterRead); } methodsThatOverrideMe = cfa.methodsThatOverrideMe; getClassField = program.getIndexedField("Object.___clazz"); getClassMethod = program.getIndexedMethod("Object.getClass"); } public ControlFlowAnalyzer(JProgram program) { this.program = program; asyncFragmentOnLoad = program.getIndexedMethod("AsyncFragmentLoader.onLoad"); runAsyncOnsuccess = program.getIndexedMethod("RunAsyncCallback.onSuccess"); baseArrayType = program.getIndexedType("Array"); getClassField = program.getIndexedField("Object.___clazz"); getClassMethod = program.getIndexedMethod("Object.getClass"); buildMethodsOverriding(); } /** * Return the set of all fields that are written. */ public Set<JField> getFieldsWritten() { return fieldsWritten; } /** * Return the complete set of types that have been instantiated. */ public Set<JReferenceType> getInstantiatedTypes() { return instantiatedTypes; } /** * Return all methods that could be executed, and all variables that could be * read, based on the given entry points so far. */ public Set<? extends JNode> getLiveFieldsAndMethods() { return liveFieldsAndMethods; } public Set<String> getLiveStrings() { return liveStrings; } /** * Return the complete set of types that have been referenced. */ public Set<? extends JReferenceType> getReferencedTypes() { return referencedTypes; } /** * Specify the {@link DependencyRecorder} to be used for future traversals. * Specifying <code>null</code> means to stop recording dependencies. */ public void setDependencyRecorder(DependencyRecorder dr) { if (dependencyRecorder != null && dr != null) { throw new IllegalArgumentException("Attempting to set multiple dependency recorders"); } this.dependencyRecorder = dr; } public void setForPruning() { assert argsToRescueIfParameterRead == null; argsToRescueIfParameterRead = new HashMap<JParameter, List<JExpression>>(); } /** * Traverse the program entry points, but don't traverse any runAsync * fragments. */ public void traverseEntryMethods() { for (JMethod method : program.getEntryMethods()) { traverseFrom(method); } if (program.getRunAsyncs().size() > 0) { /* * Explicitly rescue AsyncFragmentLoader.onLoad(). It is never explicitly * called anyway, until late code gen. Also, we want it in the initial * fragment so all other fragments can share the code. */ traverseFrom(asyncFragmentOnLoad); } } public void traverseEverything() { traverseEntryMethods(); traverseFromRunAsyncs(); /* * Keep callback.onSuccess() from being pruned since we explicitly avoid * visiting it. */ liveFieldsAndMethods.add(runAsyncOnsuccess); } /** * Assume <code>method</code> is live, and find out what else might execute. */ public void traverseFrom(JMethod method) { rescuer.rescue(method); } /** * Assume <code>type</code> is instantiated, and find out what else will * execute as a result. */ public void traverseFromInstantiationOf(JDeclaredType type) { rescuer.rescue(type, true, true); } public void traverseFromReferenceTo(JDeclaredType type) { rescuer.rescue(type, true, false); } /** * Traverse the fragment for a specific runAsync. */ public void traverseFromRunAsync(JRunAsync runAsync) { runAsync.traverseOnSuccess(rescuer); } /** * Traverse the fragments for all runAsyncs. */ public void traverseFromRunAsyncs() { for (JRunAsync runAsync : program.getRunAsyncs()) { traverseFromRunAsync(runAsync); } } private void buildMethodsOverriding() { methodsThatOverrideMe = new HashMap<JMethod, List<JMethod>>(); for (JDeclaredType type : program.getDeclaredTypes()) { for (JMethod method : type.getMethods()) { for (JMethod overridden : program.typeOracle.getAllOverrides(method)) { List<JMethod> overs = methodsThatOverrideMe.get(overridden); if (overs == null) { overs = new ArrayList<JMethod>(); methodsThatOverrideMe.put(overridden, overs); } overs.add(method); } } } } }