blob: 5dc924519e2dcf9898c3bfb2d633e16cae447f8e [file] [log] [blame]
/*
* 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.SourceOrigin;
import com.google.gwt.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.HasName;
import com.google.gwt.dev.jjs.ast.JArrayRef;
import com.google.gwt.dev.jjs.ast.JArrayType;
import com.google.gwt.dev.jjs.ast.JBinaryOperation;
import com.google.gwt.dev.jjs.ast.JCastMap;
import com.google.gwt.dev.jjs.ast.JCastOperation;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JInstanceOf;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JReferenceType;
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.ast.JTypeOracle;
import com.google.gwt.dev.jjs.ast.JVisitor;
import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Multimap;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Builds minimal cast maps to cover cast and instanceof operations. Depends
* on {@link CatchBlockNormalizer}, {@link CompoundAssignmentNormalizer},
* {@link Devirtualizer}, and {@link LongCastNormalizer} having already run.
* <p>
* May or may not include trivial casts depending on configuration.
*/
public class ComputeCastabilityInformation {
private class AssignTypeCastabilityVisitor extends JVisitor {
private final Set<JReferenceType> alreadyRan = Sets.newHashSet();
private final Map<JReferenceType, JCastMap> castableTypesMap = Maps.newIdentityHashMap();
private final List<JArrayType> instantiatedArrayTypes = Lists.newArrayList();
private final Multimap<JReferenceType, JReferenceType> castSourceTypesPerCastTargetType =
HashMultimap.create();
{
for (JArrayType arrayType : program.getAllArrayTypes()) {
if (typeOracle.isInstantiatedType(arrayType)) {
instantiatedArrayTypes.add(arrayType);
}
}
// Force entries for Object and String.
recordCastInternal(program.getTypeJavaLangObject(), program.getTypeJavaLangObject());
recordCastInternal(program.getTypeJavaLangString(), program.getTypeJavaLangObject());
// Force entries for interfaces implemented by String
recordCastInternal(program.getIndexedType("Serializable"), program.getTypeJavaLangObject());
recordCastInternal(program.getIndexedType("CharSequence"), program.getTypeJavaLangObject());
recordCastInternal(program.getIndexedType("Comparable"), program.getTypeJavaLangObject());
}
public void computeTypeCastabilityMaps() {
// do String first (which will pull in Object also, it's superclass).
computeCastMap(program.getTypeJavaLangString());
assert (castableTypesMap.size() == 2);
/*
* Compute the list of classes than can successfully satisfy cast
* requests, along with the set of types they can be successfully cast to.
* Do it in super type order.
*/
for (JReferenceType type : program.getDeclaredTypes()) {
if (type instanceof JClassType) {
computeCastMap(type);
}
}
for (JArrayType type : instantiatedArrayTypes) {
computeCastMap(type);
}
// pass our info to JProgram
program.initTypeInfo(castableTypesMap);
}
/*
* If this expression could possibly generate an ArrayStoreException, we
* must record a query on the element type being assigned to.
*/
@Override
public void endVisit(JBinaryOperation x, Context ctx) {
if (!x.getOp().isAssignment() || !(x.getLhs() instanceof JArrayRef)) {
return;
}
// first, calculate the transitive closure of all possible runtime types
// the lhs could be
JArrayRef lhsArrayRef = (JArrayRef) x.getLhs();
JType elementType = lhsArrayRef.getType();
if (elementType.isNullType()) {
// will generate a null pointer exception instead
return;
}
// primitives are statically correct
if (!(elementType instanceof JReferenceType)) {
return;
}
// This array reference always refers to an array of the declared class, not a subclass.
if (!lhsArrayRef.getInstance().getType().canBeSubclass()) {
return;
}
/*
* For every instantiated array type that could -in theory- be the
* runtime type of the lhs, we must record a cast from the rhs to the
* prospective element type of the lhs.
*/
JType rhsType = x.getRhs().getType();
assert (rhsType instanceof JReferenceType);
JArrayType lhsArrayType = lhsArrayRef.getArrayType();
for (JArrayType arrayType : instantiatedArrayTypes) {
if (!typeOracle.castFailsTrivially(arrayType, lhsArrayType)) {
JType itElementType = arrayType.getElementType();
if (itElementType instanceof JReferenceType) {
recordCast(itElementType, x.getRhs());
}
}
}
}
@Override
public void endVisit(JCastOperation x, Context ctx) {
if (x.getCastType().isNullType()) {
return;
}
recordCast(x.getCastType(), x.getExpr());
}
@Override
public void endVisit(JInstanceOf x, Context ctx) {
assert (!x.getTestType().isNullType());
recordCast(x.getTestType(), x.getExpr());
}
/**
* Returns true if the cast *might* succeed at runtime considering the semantics of JSO casts.
*/
private boolean castSucceedsTriviallyJsoSemantics(
JReferenceType fromType, JReferenceType toType) {
fromType = fromType.getUnderlyingType();
toType = toType.getUnderlyingType();
if (typeOracle.castSucceedsTrivially(fromType, toType)) {
return true;
}
// Casting to a native type might succeed (even in cases where the same cast would never
// succeed in the Java type system, due to JSOs and native JsType sematics).
if (toType.isJsNative()) {
return true;
}
if (fromType instanceof JArrayType && toType instanceof JArrayType) {
JArrayType fromArrayType = (JArrayType) fromType;
JArrayType toArrayType = (JArrayType) toType;
return (fromArrayType.getLeafType().isJsoType() &&
toArrayType.getLeafType().isJsoType());
}
return fromType.isJsoType() && toType.isJsoType();
}
/**
* Create the mapping from a class to the types it can be cast to.
*/
private void computeCastMap(JReferenceType type) {
if (type == null || alreadyRan.contains(type)) {
return;
}
assert (type == type.getUnderlyingType());
alreadyRan.add(type);
// Visit super type.
if (type instanceof JClassType) {
computeCastMap(((JClassType) type).getSuperClass());
}
if (!typeOracle.isInstantiatedType(type) ||
type.isJsoType() || type.isJsNative()) {
return;
}
// Find all possible query types which I can satisfy
Set<JReferenceType> castableTypes = Sets.newTreeSet(HasName.BY_NAME_COMPARATOR);
/*
* NOTE: non-deterministic iteration over HashSet and HashMap. Okay
* because we're sorting the results.
*/
for (JReferenceType castTargetType : castSourceTypesPerCastTargetType.keySet()) {
if (!castSucceedsTriviallyJsoSemantics(type, castTargetType)) {
// Cannot cast type to castTargetType, hence no entry in the cast map.
continue;
}
Collection<JReferenceType> castSourceTypes =
castSourceTypesPerCastTargetType.get(castTargetType);
/**
* Handles JSO[] -> JSO[] case now that canCastTrivially doesn't deal
* with JSO cross-casts anymore.
*/
for (JReferenceType castSourceType : castSourceTypes) {
if (castSucceedsTriviallyJsoSemantics(type, castSourceType)) {
boolean isTrivialCast = castTargetType == program.getTypeJavaLangObject()
|| castTargetType == program.getJavaScriptObject();
if (recordTrivialCasts || !isTrivialCast) {
castableTypes.add(castTargetType);
}
break;
}
}
}
/*
* Don't add an entry for empty answer sets, except for Object and String
* which require entries.
*/
if (castableTypes.isEmpty() && type != program.getTypeJavaLangObject()
&& type != program.getTypeJavaLangString()) {
return;
}
// add an entry for me
castableTypesMap.put(type, new JCastMap(SourceOrigin.UNKNOWN, program.getTypeJavaLangObject(),
Collections.unmodifiableSet(castableTypes)));
}
private void recordCast(JType targetType, JExpression rhs) {
if (!(targetType instanceof JReferenceType) || targetType.isJsNative()) {
return;
}
targetType = targetType.getUnderlyingType();
assert rhs.getType() instanceof JReferenceType;
JReferenceType rhsType = (JReferenceType) rhs.getType().getUnderlyingType();
if (!recordTrivialCasts
&& typeOracle.castSucceedsTrivially(rhsType, (JReferenceType) targetType)) {
// don't record a type for trivial casts that won't generate code
return;
}
if (!recordTrivialCasts && targetType.isJsoType()) {
// If the target type is a JavaScriptObject, don't record an id.
return;
}
recordCastInternal((JReferenceType) targetType, rhsType);
}
private void recordCastInternal(JReferenceType toType, JReferenceType rhsType) {
toType = toType.getUnderlyingType();
rhsType = rhsType.getUnderlyingType();
if (toType instanceof JArrayType) {
// Arrays of any subclass of JavaScriptObject are considered arrays of JavaScriptObject
// for casting and instanceof purposes.
toType = (JReferenceType) program.normalizeJsoType(toType);
}
castSourceTypesPerCastTargetType.put(toType, rhsType);
}
}
public static void exec(JProgram program, boolean recordTrivialCasts) {
new ComputeCastabilityInformation(program, recordTrivialCasts).execImpl();
}
public static void exec(JProgram program) {
new ComputeCastabilityInformation(program, false).execImpl();
}
private final boolean recordTrivialCasts;
private final JProgram program;
private final JTypeOracle typeOracle;
private ComputeCastabilityInformation(JProgram program, boolean recordTrivialCasts) {
this.program = program;
this.typeOracle = program.typeOracle;
this.recordTrivialCasts = recordTrivialCasts;
}
private void execImpl() {
AssignTypeCastabilityVisitor assigner = new AssignTypeCastabilityVisitor();
assigner.accept(program);
assigner.computeTypeCastabilityMaps();
}
}