| /* |
| * Copyright 2014 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.StringAnalyzableTypeEnvironment; |
| import com.google.gwt.dev.jjs.ast.Context; |
| import com.google.gwt.dev.jjs.ast.JClassLiteral; |
| import com.google.gwt.dev.jjs.ast.JClassType; |
| import com.google.gwt.dev.jjs.ast.JDeclaredType; |
| 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.JMethod; |
| import com.google.gwt.dev.jjs.ast.JMethodCall; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.ast.JType; |
| 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.JsniMethodRef; |
| import com.google.gwt.thirdparty.guava.common.collect.Sets; |
| |
| import java.util.Set; |
| |
| /** |
| * Records control flow information. |
| * <p> |
| * Collects caller->callee, instantiating method->instantiated type, overridden method->overriding |
| * method, exported methods and other control flow information in TypeEnvironment indexes to support |
| * control flow based link time pruning. |
| */ |
| public class ControlFlowRecorder extends JVisitor { |
| |
| public static void exec(JProgram program, |
| StringAnalyzableTypeEnvironment stringAnalyzableTypeEnvironment, boolean onlyUpdate) { |
| new ControlFlowRecorder(stringAnalyzableTypeEnvironment, onlyUpdate, program).execImpl(); |
| } |
| |
| private static String computeName(JMethod method) { |
| return method.getJsniSignature(true, true); |
| } |
| |
| private final Set<String> bannedMethodNames = Sets.newHashSet(); |
| private String currentMethodName; |
| private final boolean onlyUpdate; |
| private final JProgram program; |
| private final StringAnalyzableTypeEnvironment stringAnalyzableTypeEnvironment; |
| |
| public ControlFlowRecorder(StringAnalyzableTypeEnvironment stringAnalyzableTypeEnvironment, |
| boolean onlyUpdate, JProgram program) { |
| this.stringAnalyzableTypeEnvironment = stringAnalyzableTypeEnvironment; |
| this.onlyUpdate = onlyUpdate; |
| this.program = program; |
| |
| bannedMethodNames.add(computeName(program.getTypeClassLiteralHolder().getClinitMethod())); |
| } |
| |
| @Override |
| public void endVisit(JClassLiteral x, Context ctx) { |
| JType type = x.getRefType(); |
| if (type instanceof JDeclaredType) { |
| String typeName = type.getName(); |
| stringAnalyzableTypeEnvironment.recordStaticReferenceInMethod(typeName, currentMethodName); |
| maybeRecordClinitCall(typeName); |
| } |
| // Any Enum subtype whose class literal is referenced might have its enumValueOfFunc |
| // reflectively called at runtime (see Enum.valueOf()). So to be safe the enumValueOfFunc |
| // must be assumed to be called. |
| if (type.isEnumOrSubclass() != null && !type.getName().equals("java.lang.Enum")) { |
| JMethod valueOfMethod = getValueOfMethod((JDeclaredType) type); |
| if (valueOfMethod != null) { |
| stringAnalyzableTypeEnvironment.recordMethodCallsMethod(currentMethodName, |
| computeName(valueOfMethod)); |
| } |
| } |
| } |
| |
| @Override |
| public void endVisit(JFieldRef x, Context ctx) { |
| processJFieldRef(x); |
| } |
| |
| @Override |
| public void endVisit(JsniFieldRef x, Context ctx) { |
| processJFieldRef(x); |
| } |
| |
| @Override |
| public void endVisit(JsniMethodRef x, Context ctx) { |
| processMethodCall(x); |
| } |
| |
| @Override |
| public boolean visit(JDeclaredType x, Context ctx) { |
| if (!onlyUpdate) { |
| stringAnalyzableTypeEnvironment.removeControlFlowIndexesFor(x.getName()); |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JField x, Context ctx) { |
| String typeName = x.getEnclosingType().getName(); |
| |
| if (x.isJsInteropEntryPoint()) { |
| stringAnalyzableTypeEnvironment.recordExportedStaticReferenceInType(typeName); |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JMethod x, Context ctx) { |
| String typeName = x.getEnclosingType().getName(); |
| currentMethodName = computeName(x); |
| |
| if (bannedMethodNames.contains(currentMethodName)) { |
| return false; |
| } |
| |
| stringAnalyzableTypeEnvironment.recordTypeEnclosesMethod(typeName, currentMethodName); |
| |
| for (JMethod overriddenMethod : x.getOverriddenMethods()) { |
| String overriddenMethodName = computeName(overriddenMethod); |
| stringAnalyzableTypeEnvironment.recordMethodOverridesMethod(currentMethodName, |
| overriddenMethodName); |
| } |
| |
| if (x.canBeReferencedExternally()) { |
| stringAnalyzableTypeEnvironment.recordExportedMethodInType(currentMethodName, typeName); |
| } |
| |
| if (x.isJsInteropEntryPoint()) { |
| stringAnalyzableTypeEnvironment.recordExportedStaticReferenceInType(typeName); |
| } |
| |
| if (x.isConstructor()) { |
| // Constructor calls if reachable are deemed to instantiate the class. |
| recordCurrentMethodInstantiatesType(x.getEnclosingType()); |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JMethodCall x, Context ctx) { |
| processMethodCall(x); |
| return true; |
| } |
| |
| private void execImpl() { |
| accept(program); |
| } |
| |
| private JMethod getValueOfMethod(JDeclaredType type) { |
| for (JMethod method : type.getMethods()) { |
| if (method.getName().equals("valueOf")) { |
| return method; |
| } |
| } |
| return null; |
| } |
| |
| private void maybeRecordClinitCall(String typeName) { |
| String typeClinitMethod = typeName + "::$clinit()V"; |
| if (!typeClinitMethod.equals(currentMethodName)) { |
| stringAnalyzableTypeEnvironment.recordMethodCallsMethod(currentMethodName, typeClinitMethod); |
| } |
| } |
| |
| private void processJFieldRef(JFieldRef x) { |
| if (x.getTarget() instanceof JField) { |
| JField field = (JField) x.getTarget(); |
| if (field.isStatic()) { |
| String typeName = field.getEnclosingType().getName(); |
| stringAnalyzableTypeEnvironment.recordStaticReferenceInMethod(typeName, currentMethodName); |
| maybeRecordClinitCall(typeName); |
| } |
| } |
| } |
| |
| private void processMethodCall(JMethodCall x) { |
| JMethod targetMethod = x.getTarget(); |
| String calleeMethodName = computeName(targetMethod); |
| stringAnalyzableTypeEnvironment.recordMethodCallsMethod(currentMethodName, calleeMethodName); |
| |
| if (targetMethod.isStatic()) { |
| String typeName = targetMethod.getEnclosingType().getName(); |
| stringAnalyzableTypeEnvironment.recordStaticReferenceInMethod(typeName, currentMethodName); |
| maybeRecordClinitCall(typeName); |
| } |
| |
| // Instantiations in JSNI don't use JNewInstance and must be recognized by method calls on |
| // Constructor functions. |
| if (targetMethod.isConstructor()) { |
| String typeName = targetMethod.getEnclosingType().getName(); |
| stringAnalyzableTypeEnvironment.recordMethodInstantiatesType(currentMethodName, typeName); |
| maybeRecordClinitCall(typeName); |
| } |
| } |
| |
| private void recordCurrentMethodInstantiatesType(JDeclaredType type) { |
| String typeName = type.getName(); |
| stringAnalyzableTypeEnvironment.recordMethodInstantiatesType(currentMethodName, typeName); |
| maybeRecordClinitCall(typeName); |
| |
| JClassType superClass = type.getSuperClass(); |
| if (superClass != null) { |
| recordCurrentMethodInstantiatesType(superClass); |
| } |
| |
| for (JInterfaceType interfaceType : type.getImplements()) { |
| recordCurrentMethodInstantiatesType(interfaceType); |
| } |
| } |
| } |