blob: e144f03c87337ace99d3e3fd25601811f513254a [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.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JConstructor;
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.JParameter;
import com.google.gwt.dev.jjs.ast.JParameterRef;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JThisRef;
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.ast.JVisitor;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.thirdparty.guava.common.base.Predicate;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import java.util.Set;
/**
* Determines conservatively which classes can potentially see uninitialized values of their
* subclasses' fields.
* <p>
* This simple conservative analysis relies on the fact that when: <ul>
* <li> (1) there are no virtual calls on "this" in any of the initialization methods
* (constructors, init) of all the superclasses, and </li>
* <li> (2) "this" does not escape through a parameter to other methods, and </li>
* <li>(3) "this" is not aliased (stored into another field, variable or array)</li>
* </ul>
* then uninitialized values for subclass fields can never be seen.
* <p>
* This analysis is used to strengthen the nullness analysis performed by {@link TypeTightener} and
* to hoist initialization of instance fields to the prototype in {@link GenerateJavaScriptAST}.
*/
public class ComputePotentiallyObservableUninitializedValues {
private static final String NAME =
ComputePotentiallyObservableUninitializedValues.class.getSimpleName();
private final JProgram program;
private final Set<JType> classesWhoseFieldsCanBeObservedUninitialized = Sets.newHashSet();
private ComputePotentiallyObservableUninitializedValues(JProgram program) {
this.program = program;
}
/**
* Perform the analysis to compute which fields can be observed uninitialized.
*/
public static Predicate<JField> analyze(JProgram program) {
return new ComputePotentiallyObservableUninitializedValues(program).analyzeImpl();
}
private Predicate<JField> analyzeImpl() {
SpeedTracerLogger.Event optimizeEvent =
SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "optimizer", NAME);
CanObserveSubclassUninitializedFieldsVisitor visitor =
new CanObserveSubclassUninitializedFieldsVisitor();
visitor.accept(program);
Set<JType> classesThatCanPotentiallyObserveUninitializedSubclassFields =
visitor.classesThatCanPotentiallyObserveUninitializedSubclassFields;
for (JType type : classesThatCanPotentiallyObserveUninitializedSubclassFields) {
if (classesWhoseFieldsCanBeObservedUninitialized.contains(type)) {
// Already processed.
continue;
}
classesWhoseFieldsCanBeObservedUninitialized.addAll(program.getSubclasses(type));
}
optimizeEvent.end();
return new Predicate<JField>() {
@Override
public boolean apply(JField field) {
return isUninitializedValueObservable(field);
}
};
}
private boolean isUninitializedValueObservable(JField x) {
if (x.getLiteralInitializer() != null && (x.isFinal() || x.isStatic())) {
// Static and final fields that are initialized to a (value) literal can not be observed in
// uninitialized state.
return false;
}
if (x.isStatic()) {
// Static fields can potentially be observed uninitialized if clinit dependencies are
// cyclical.
return true;
}
return classesWhoseFieldsCanBeObservedUninitialized.contains(x.getEnclosingType());
}
private class CanObserveSubclassUninitializedFieldsVisitor extends JVisitor {
private JClassType currentClass;
private JParameter devirtualizedThis;
private Set<JType> classesThatCanPotentiallyObserveUninitializedSubclassFields =
Sets.newHashSet();
@Override
public void endVisit(JClassType x, Context ctx) {
assert currentClass == x;
currentClass = null;
}
@Override
public void endVisit(JConstructor x, Context ctx) {
assert currentClass == x.getEnclosingType();
assert devirtualizedThis == null;
}
@Override
public void endVisit(JMethod x, Context ctx) {
assert currentClass == x.getEnclosingType();
devirtualizedThis = null;
}
@Override
public void endVisit(JThisRef x, Context ctx) {
// Seen a reference to "this" that can potentially escape or be used as instance in a
// method call.
classesThatCanPotentiallyObserveUninitializedSubclassFields.add(currentClass);
}
public void endVisit(JParameterRef x, Context ctx) {
if (x.getParameter() == devirtualizedThis) {
// Seen a reference to devirtualized "this" that can potentially escape or be used as
// instance in a method call.
classesThatCanPotentiallyObserveUninitializedSubclassFields.add(currentClass);
}
}
@Override
public boolean visit(JClassType x, Context ctx) {
assert currentClass == null;
currentClass = x;
return true;
}
@Override
public boolean visit(JConstructor x, Context ctx) {
// Only look at constructor bodies.
assert currentClass == x.getEnclosingType();
return true;
}
@Override
public boolean visit(JFieldRef x, Context ctx) {
if (isFieldReferenceThroughThis(x) && isFieldDeclaredInCurrentClassOrSuper(x)) {
// Accessing fields through this (or devirtualized this) from the current class or
// any super is ok, no further checks are needed.
// A subclass field ref can leak into superclass methods when optimizations are enabled.
return false;
}
return true;
}
@Override
public boolean visit(JInterfaceType x, Context ctx) {
// No need to examine interfaces.
return false;
}
@Override
public boolean visit(JMethod x, Context ctx) {
assert currentClass == x.getEnclosingType();
if (isInitMethod(x)) {
return true;
}
if (isDevirtualizedInitMethod(x) && x.getParams().size() > 0
&& x.getParams().get(0).getType() == currentClass) {
devirtualizedThis = x.getParams().get(0);
}
// Do not explore the method body if it is not a constructor or the instance initializer.
return false;
}
@Override
public boolean visit(JMethodCall x, Context ctx) {
// This is a method call inside a constructor.
assert currentClass != null;
// Calls to this/super constructors and instance initializers are okay, as they will also
// get examined.
if ((x.getTarget().isConstructor()) && x.getInstance() instanceof JThisRef ||
isInitMethod(x.getTarget())) {
// Make sure "this" references do not escape through parameters
accept(x.getArgs());
return false;
}
// The instance initializers are always devirtualized, handle them specially.
if (isDevirtualizedInitMethod(x.getTarget()) && x.getArgs().size() > 0 &&
x.getArgs().get(0) instanceof JThisRef) {
// Make sure "this" references do not escape through parameters other than the first.
accept(x.getArgs().subList(1, x.getArgs().size()));
return false;
}
if (!x.getTarget().isStatic() && !x.getTarget().isFinal() &&
x.getInstance() instanceof JThisRef) {
// This is polymorphic method call on this, hence it is potentially unsafe.
classesThatCanPotentiallyObserveUninitializedSubclassFields.add(currentClass);
return false;
}
// This is a static call, if there is no "this" references in its parameters then it might
// be ok.
return true;
}
private boolean isDevirtualizedInitMethod(JMethod method) {
return method.isStatic() && method.getName().equals(GwtAstBuilder.STATIC_INIT_METHOD_NAME) &&
method.getEnclosingType() == currentClass;
}
private boolean isInitMethod(JMethod method) {
return !method.isStatic() && method.getName().equals(GwtAstBuilder.INIT_NAME_METHOD_NAME) &&
method.getEnclosingType() == currentClass;
}
private boolean isFieldReferenceThroughThis(JFieldRef x) {
return x.getInstance() instanceof JThisRef || x.getInstance() instanceof JParameterRef &&
((JParameterRef) x.getInstance()).getParameter() == devirtualizedThis;
}
private boolean isFieldDeclaredInCurrentClassOrSuper(JFieldRef x) {
JClassType enclosingClass = (JClassType) x.getField().getEnclosingType();
return currentClass == enclosingClass ||
program.typeOracle.isSuperClass(enclosingClass, currentClass);
}
}
}