/* * 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); } } }