blob: d13ed829394aead63949074f0043b457d1c0fe15 [file] [log] [blame]
/*
* 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);
}
}
}