/*
* Copyright 2007 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.JArrayRef;
import com.google.gwt.dev.jjs.ast.JArrayType;
import com.google.gwt.dev.jjs.ast.JBinaryOperation;
import com.google.gwt.dev.jjs.ast.JBinaryOperator;
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.JIntLiteral;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JModVisitor;
import com.google.gwt.dev.jjs.ast.JNullLiteral;
import com.google.gwt.dev.jjs.ast.JNullType;
import com.google.gwt.dev.jjs.ast.JPrimitiveType;
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.dev.jjs.ast.js.JClassSeed;
import com.google.gwt.dev.jjs.ast.js.JsonObject;
import com.google.gwt.dev.jjs.ast.js.JsonObject.JsonPropInit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Replace cast and instanceof operations with calls to the Cast class. Depends
* on {@link com.google.gwt.dev.jjs.impl.CatchBlockNormalizer},
* {@link com.google.gwt.dev.jjs.impl.CompoundAssignmentNormalizer}, and
* {@link com.google.gwt.dev.jjs.impl.JavaScriptObjectCaster} having already
* run.
*/
public class CastNormalizer {
private class AssignTypeIdsVisitor extends JVisitor {
Set/* <JClassType> */alreadyRan = new HashSet/* <JClassType> */();
private Map/* <JReferenceType, Set<JReferenceType>> */queriedTypes = new IdentityHashMap();
private int nextQueryId = 1; // 0 is reserved
private final List/* <JArrayType> */instantiatedArrayTypes = new ArrayList/* <JArrayType> */();
private List/* <JClassType> */classes = new ArrayList/* <JClassType> */();
private List/* <JsonObject> */jsonObjects = new ArrayList/* <JsonObject> */();
{
JTypeOracle typeOracle = program.typeOracle;
for (Iterator it = program.getAllArrayTypes().iterator(); it.hasNext();) {
JArrayType arrayType = (JArrayType) it.next();
if (typeOracle.isInstantiatedType(arrayType)) {
instantiatedArrayTypes.add(arrayType);
}
}
}
public void computeTypeIds() {
// the 0th entry is the "always false" entry
classes.add(null);
jsonObjects.add(new JsonObject(program));
/*
* 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 (Iterator it = program.getDeclaredTypes().iterator(); it.hasNext();) {
JReferenceType type = (JReferenceType) it.next();
if (type instanceof JClassType) {
computeSourceClass((JClassType) type);
}
}
for (Iterator it = program.getAllArrayTypes().iterator(); it.hasNext();) {
JArrayType type = (JArrayType) it.next();
computeSourceClass(type);
}
// pass our info to JProgram
program.initTypeInfo(classes, jsonObjects);
program.recordQueryIds(queryIds);
}
/*
* 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() == JBinaryOperator.ASG && x.getLhs() instanceof JArrayRef) {
// first, calculate the transitive closure of all possible runtime types
// the lhs could be
JExpression instance = ((JArrayRef) x.getLhs()).getInstance();
if (instance.getType() instanceof JNullType) {
// will generate a null pointer exception instead
return;
}
JArrayType lhsArrayType = (JArrayType) instance.getType();
JType elementType = lhsArrayType.getElementType();
// primitives are statically correct
if (!(elementType instanceof JReferenceType)) {
return;
}
// element type being final means the assignment is statically correct
if (((JReferenceType) elementType).isFinal()) {
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.
*/
JTypeOracle typeOracle = program.typeOracle;
JType rhsType = x.getRhs().getType();
assert (rhsType instanceof JReferenceType);
JReferenceType refRhsType = (JReferenceType) rhsType;
for (Iterator it = instantiatedArrayTypes.iterator(); it.hasNext();) {
JArrayType arrayType = (JArrayType) it.next();
if (typeOracle.canTheoreticallyCast(arrayType, lhsArrayType)) {
JType itElementType = arrayType.getElementType();
if (itElementType instanceof JReferenceType) {
recordCastInternal((JReferenceType) itElementType, refRhsType);
}
}
}
}
}
// @Override
public void endVisit(JCastOperation x, Context ctx) {
if (x.getCastType() != program.getTypeNull()) {
recordCast(x.getCastType(), x.getExpr());
}
}
// @Override
public void endVisit(JInstanceOf x, Context ctx) {
assert (x.getTestType() != program.getTypeNull());
recordCast(x.getTestType(), x.getExpr());
}
/**
* Create the data for JSON table to capture the mapping from a class to its
* query types.
*/
private void computeSourceClass(JClassType type) {
if (type == null || alreadyRan.contains(type)) {
return;
}
alreadyRan.add(type);
/*
* IMPORTANT: Visit my supertype first. The implementation of
* com.google.gwt.lang.Cast.wrapJSO() depends on all superclasses having
* typeIds that are less than all their subclasses. This allows the same
* JSO to be wrapped stronger but not weaker.
*/
computeSourceClass(type.extnds);
if (!program.typeOracle.isInstantiatedType(type)) {
return;
}
// Find all possible query types which I can satisfy
Set/* <JReferenceType> */yesSet = null;
for (Iterator iter = queriedTypes.keySet().iterator(); iter.hasNext();) {
JReferenceType qType = (JReferenceType) iter.next();
Set/* <JReferenceType> */querySet = (Set) queriedTypes.get(qType);
if (program.typeOracle.canTriviallyCast(type, qType)) {
for (Iterator it = querySet.iterator(); it.hasNext();) {
JReferenceType argType = (JReferenceType) it.next();
if (program.typeOracle.canTriviallyCast(type, argType)) {
if (yesSet == null) {
yesSet = new HashSet/* <JReferenceType> */();
}
yesSet.add(qType);
break;
}
}
}
}
/*
* Weird: JavaScriptObjects MUST have a typeId, the implementation of
* Cast.wrapJSO depends on it.
*/
if (yesSet == null && !program.isJavaScriptObject(type)) {
return; // won't satisfy anything
}
// use an array to sort my yes set
JReferenceType[] yesArray = new JReferenceType[nextQueryId];
if (yesSet != null) {
for (Iterator it = yesSet.iterator(); it.hasNext();) {
JReferenceType yesType = (JReferenceType) it.next();
Integer boxedInt = (Integer) queryIds.get(yesType);
yesArray[boxedInt.intValue()] = yesType;
}
}
// create a sparse lookup object
JsonObject jsonObject = new JsonObject(program);
for (int i = 0; i < nextQueryId; ++i) {
if (yesArray[i] != null) {
JIntLiteral labelExpr = program.getLiteralInt(i);
JIntLiteral valueExpr = program.getLiteralInt(1);
jsonObject.propInits.add(new JsonPropInit(program, labelExpr,
valueExpr));
}
}
// add an entry for me
classes.add(type);
jsonObjects.add(jsonObject);
}
private void recordCast(JType targetType, JExpression rhs) {
if (targetType instanceof JReferenceType) {
// unconditional cast b/c it would've been a semantic error earlier
JReferenceType rhsType = (JReferenceType) rhs.getType();
// don't record a type for trivial casts that won't generate code
if (rhsType instanceof JClassType) {
if (program.typeOracle.canTriviallyCast(rhsType,
(JReferenceType) targetType)) {
return;
}
}
recordCastInternal((JReferenceType) targetType, rhsType);
}
}
private void recordCastInternal(JReferenceType targetType,
JReferenceType rhsType) {
JReferenceType toType = targetType;
Set/* <JReferenceType> */querySet = (Set) queriedTypes.get(toType);
if (querySet == null) {
queryIds.put(toType, new Integer(nextQueryId++));
querySet = new HashSet/* <JReferenceType> */();
queriedTypes.put(toType, querySet);
}
querySet.add(rhsType);
}
}
/**
* Explicitly convert any char-typed expressions within a concat operation
* into strings.
*/
private class ConcatVisitor extends JModVisitor {
private JMethod stringValueOfChar = null;
// @Override
public void endVisit(JBinaryOperation x, Context ctx) {
if (x.getType() != program.getTypeJavaLangString()) {
return;
}
if (x.getOp() == JBinaryOperator.ADD) {
JExpression newLhs = convertCharString(x.getLhs());
JExpression newRhs = convertCharString(x.getRhs());
if (newLhs != x.getLhs() || newRhs != x.getRhs()) {
JBinaryOperation newExpr = new JBinaryOperation(program,
x.getSourceInfo(), program.getTypeJavaLangString(),
JBinaryOperator.ADD, newLhs, newRhs);
ctx.replaceMe(newExpr);
}
} else if (x.getOp() == JBinaryOperator.ASG_ADD) {
JExpression newRhs = convertCharString(x.getRhs());
if (newRhs != x.getRhs()) {
JBinaryOperation newExpr = new JBinaryOperation(program,
x.getSourceInfo(), program.getTypeJavaLangString(),
JBinaryOperator.ASG_ADD, x.getLhs(), newRhs);
ctx.replaceMe(newExpr);
}
}
}
private JExpression convertCharString(JExpression expr) {
JPrimitiveType charType = program.getTypePrimitiveChar();
if (expr.getType() == charType) {
// Replace the character with a call to Cast.charToString()
if (stringValueOfChar == null) {
stringValueOfChar = program.getSpecialMethod("Cast.charToString");
assert (stringValueOfChar != null);
}
JMethodCall call = new JMethodCall(program, expr.getSourceInfo(), null,
stringValueOfChar);
call.getArgs().add(expr);
return call;
}
return expr;
}
}
/**
* Explicitly cast all integral divide operations to trigger replacements with
* narrowing calls in the next pass.
*/
private class DivVisitor extends JModVisitor {
// @Override
public void endVisit(JBinaryOperation x, Context ctx) {
JType type = x.getType();
if (x.getOp() == JBinaryOperator.DIV
&& type != program.getTypePrimitiveFloat()
&& type != program.getTypePrimitiveDouble()) {
x.setType(program.getTypePrimitiveDouble());
JCastOperation cast = new JCastOperation(program, x.getSourceInfo(),
type, x);
ctx.replaceMe(cast);
}
}
}
/**
* Replaces all casts and instanceof operations with calls to implementation
* methods.
*/
private class ReplaceTypeChecksVisitor extends JModVisitor {
// @Override
public void endVisit(JCastOperation x, Context ctx) {
JExpression replaceExpr;
JType toType = x.getCastType();
if (toType instanceof JNullType) {
/*
* Magic: a null type cast means the user tried a cast that couldn't
* possibly work. Typically this means either the statically resolvable
* arg type is incompatible with the target type, or the target type was
* globally uninstantiable. We handle this cast by throwing a
* ClassCastException, unless the argument is null.
*/
JMethod method = program.getSpecialMethod("Cast.throwClassCastExceptionUnlessNull");
/*
* Override the type of the magic method with the null type.
*/
JMethodCall call = new JMethodCall(program, x.getSourceInfo(), null,
method, program.getTypeNull());
call.getArgs().add(x.getExpr());
replaceExpr = call;
} else if (toType instanceof JReferenceType) {
JExpression curExpr = x.getExpr();
JReferenceType refType = (JReferenceType) toType;
JType argType = x.getExpr().getType();
if (program.isJavaScriptObject(argType)) {
/*
* A JSO-derived class that is about to be cast must be "wrapped"
* first. Since a JSO was never constructed, it may not have an
* accessible prototype. Instead we copy fields from the seed
* function's prototype directly onto the target object as expandos.
* See com.google.gwt.lang.Cast.wrapJSO().
*/
JMethod wrap = program.getSpecialMethod("Cast.wrapJSO");
// override the type of the called method with the JSO's type
JMethodCall call = new JMethodCall(program, x.getSourceInfo(), null,
wrap, argType);
JClassSeed seed = program.getLiteralClassSeed((JClassType) argType);
call.getArgs().add(curExpr);
call.getArgs().add(seed);
curExpr = call;
}
if (argType instanceof JClassType
&& program.typeOracle.canTriviallyCast((JClassType) argType,
refType)) {
// TODO(???): why is this only for JClassType?
// just remove the cast
replaceExpr = curExpr;
} else {
JMethod method = program.getSpecialMethod("Cast.dynamicCast");
// override the type of the called method with the target cast type
JMethodCall call = new JMethodCall(program, x.getSourceInfo(), null,
method, toType);
Integer boxedInt = (Integer) queryIds.get(refType);
JIntLiteral qId = program.getLiteralInt(boxedInt.intValue());
call.getArgs().add(curExpr);
call.getArgs().add(qId);
replaceExpr = call;
}
} else {
/*
* See JLS 5.1.3: if a cast narrows from one type to another, we must
* call a narrowing conversion function. EXCEPTION: we currently have no
* way to narrow double to float, so don't bother.
*/
boolean narrow = false, round = false;
JPrimitiveType tByte = program.getTypePrimitiveByte();
JPrimitiveType tChar = program.getTypePrimitiveChar();
JPrimitiveType tShort = program.getTypePrimitiveShort();
JPrimitiveType tInt = program.getTypePrimitiveInt();
JPrimitiveType tLong = program.getTypePrimitiveLong();
JPrimitiveType tFloat = program.getTypePrimitiveFloat();
JPrimitiveType tDouble = program.getTypePrimitiveDouble();
JType fromType = x.getExpr().getType();
if (tByte == fromType) {
if (tChar == toType) {
narrow = true;
}
} else if (tShort == fromType) {
if (tByte == toType || tChar == toType) {
narrow = true;
}
} else if (tChar == fromType) {
if (tByte == toType || tShort == toType) {
narrow = true;
}
} else if (tInt == fromType) {
if (tByte == toType || tShort == toType || tChar == toType) {
narrow = true;
}
} else if (tLong == fromType) {
if (tByte == toType || tShort == toType || tChar == toType
|| tInt == toType) {
narrow = true;
}
} else if (tFloat == fromType || tDouble == fromType) {
if (tByte == toType || tShort == toType || tChar == toType
|| tInt == toType || tLong == toType) {
round = true;
}
}
if (narrow || round) {
// Replace the expression with a call to the narrow or round method
String methodName = "Cast." + (narrow ? "narrow_" : "round_")
+ toType.getName();
JMethod castMethod = program.getSpecialMethod(methodName);
JMethodCall call = new JMethodCall(program, x.getSourceInfo(), null,
castMethod);
call.getArgs().add(x.getExpr());
replaceExpr = call;
} else {
// Just remove the cast
replaceExpr = x.getExpr();
}
}
ctx.replaceMe(replaceExpr);
}
// @Override
public void endVisit(JInstanceOf x, Context ctx) {
JType argType = x.getExpr().getType();
if (argType instanceof JClassType
&& program.typeOracle.canTriviallyCast((JClassType) argType,
x.getTestType())) {
// trivially true if non-null; replace with a null test
JNullLiteral nullLit = program.getLiteralNull();
JBinaryOperation eq = new JBinaryOperation(program, x.getSourceInfo(),
program.getTypePrimitiveBoolean(), JBinaryOperator.NEQ,
x.getExpr(), nullLit);
ctx.replaceMe(eq);
} else {
JMethod method = program.getSpecialMethod("Cast.instanceOf");
JMethodCall call = new JMethodCall(program, x.getSourceInfo(), null,
method);
Integer boxedInt = (Integer) queryIds.get(x.getTestType());
JIntLiteral qId = program.getLiteralInt(boxedInt.intValue());
call.getArgs().add(x.getExpr());
call.getArgs().add(qId);
ctx.replaceMe(call);
}
}
}
public static void exec(JProgram program) {
new CastNormalizer(program).execImpl();
}
private Map/* <JReferenceType, Integer> */queryIds = new IdentityHashMap();
private final JProgram program;
private CastNormalizer(JProgram program) {
this.program = program;
}
private void execImpl() {
{
ConcatVisitor visitor = new ConcatVisitor();
visitor.accept(program);
}
{
DivVisitor visitor = new DivVisitor();
visitor.accept(program);
}
{
AssignTypeIdsVisitor assigner = new AssignTypeIdsVisitor();
assigner.accept(program);
assigner.computeTypeIds();
}
{
ReplaceTypeChecksVisitor replacer = new ReplaceTypeChecksVisitor();
replacer.accept(program);
}
}
}