| /* |
| * 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.SourceInfo; |
| import com.google.gwt.dev.jjs.ast.Context; |
| 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.JExpression; |
| import com.google.gwt.dev.jjs.ast.JInstanceOf; |
| 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.JModVisitor; |
| 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.JRuntimeTypeReference; |
| import com.google.gwt.dev.jjs.ast.JType; |
| import com.google.gwt.thirdparty.guava.common.collect.Maps; |
| |
| import java.util.EnumSet; |
| import java.util.Map; |
| |
| /** |
| * Replace cast and instanceof operations with calls to the Cast class. Depends |
| * on {@link CatchBlockNormalizer}, {@link CompoundAssignmentNormalizer}, |
| * {@link Devirtualizer}, and {@link LongCastNormalizer} having already run. |
| * <p> |
| * May or may not prune trivial casts depending on configuration. |
| */ |
| public class ImplementCastsAndTypeChecks { |
| /** |
| * Replaces all casts and instanceof operations with calls to implementation |
| * methods. |
| */ |
| private class ReplaceTypeChecksVisitor extends JModVisitor { |
| |
| @Override |
| public void endVisit(JCastOperation x, Context ctx) { |
| JType toType = x.getCastType(); |
| JExpression expr = x.getExpr(); |
| |
| // Even if disableCastChecking is enabled, we need to rescue JSOs |
| if (disableCastChecking && toType instanceof JReferenceType) { |
| // Just leave the cast in, GenerateJavaScriptAST will ignore it. |
| return; |
| } |
| SourceInfo info = x.getSourceInfo(); |
| if (pruneTrivialCasts && toType.isNullType()) { |
| /** |
| * A null type cast is used as a placeholder value to indicate that 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. |
| * |
| * See {@link TypeTightener.TightenTypesVisitor#endVisit(JCastOperation, Context)} |
| * |
| * We handle this cast by throwing a ClassCastException, unless the |
| * argument is null. |
| */ |
| JMethod method = program.getIndexedMethod("Cast.throwClassCastExceptionUnlessNull"); |
| // Note, we must update the method call to return the null type. |
| JMethodCall call = new JMethodCall(info, null, method, toType); |
| call.addArg(expr); |
| ctx.replaceMe(call); |
| return; |
| } |
| |
| if (toType instanceof JReferenceType) { |
| JExpression curExpr = expr; |
| JReferenceType refType = (JReferenceType) toType.getUnderlyingType(); |
| JReferenceType argType = (JReferenceType) expr.getType(); |
| |
| if (refType instanceof JArrayType) { |
| // Arrays of any subclass of JavaScriptObject are considered arrays of JavaScriptObject |
| // for casting and instanceof purposes. |
| refType = (JReferenceType) program.normalizeJsoType(refType); |
| } |
| |
| if (pruneTrivialCasts && program.typeOracle.castSucceedsTrivially(argType, refType)) { |
| // just remove the cast |
| ctx.replaceMe(curExpr); |
| return; |
| } else if (program.typeOracle.isEffectivelyJavaScriptObject(argType) |
| && program.typeOracle.isEffectivelyJavaScriptObject(refType)) { |
| // leave the cast instance for Pruner/CFA, remove in GenJSAST |
| return; |
| } |
| // A cast is still needed. Substitute the appropriate Cast implementation. |
| ctx.replaceMe(implementCastOrInstanceOfOperation(x.getSourceInfo(), curExpr, refType, |
| dynamicCastMethodsByTargetTypeCategory, true)); |
| return; |
| } |
| |
| // It is a primitive type, perform the necessary coercion. |
| |
| assert toType instanceof JPrimitiveType; |
| /* |
| * 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. |
| */ |
| 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 = expr.getType(); |
| |
| String methodName = null; |
| |
| if (tLong == fromType && tLong != toType) { |
| if (tByte == toType || tShort == toType || tChar == toType) { |
| /* |
| * We need a double call here, one to convert long->int, and another |
| * one to narrow. Construct the inner call here and fall through to |
| * do the narrowing conversion. |
| */ |
| JMethod castMethod = program.getIndexedMethod("LongLib.toInt"); |
| JMethodCall call = new JMethodCall(info, null, castMethod); |
| call.addArg(expr); |
| expr = call; |
| fromType = tInt; |
| } else if (tInt == toType) { |
| methodName = "LongLib.toInt"; |
| } else if (tFloat == toType || tDouble == toType) { |
| methodName = "LongLib.toDouble"; |
| } |
| } |
| |
| if (toType == tLong && fromType != tLong) { |
| // Longs get special treatment. |
| if (tByte == fromType || tShort == fromType || tChar == fromType || tInt == fromType) { |
| methodName = "LongLib.fromInt"; |
| } else if (tFloat == fromType || tDouble == fromType) { |
| methodName = "LongLib.fromDouble"; |
| } |
| } else if (tByte == fromType) { |
| if (tChar == toType) { |
| methodName = "Cast.narrow_" + toType.getName(); |
| } |
| } else if (tShort == fromType) { |
| if (tByte == toType || tChar == toType) { |
| methodName = "Cast.narrow_" + toType.getName(); |
| } |
| } else if (tChar == fromType) { |
| if (tByte == toType || tShort == toType) { |
| methodName = "Cast.narrow_" + toType.getName(); |
| } |
| } else if (tInt == fromType) { |
| if (tByte == toType || tShort == toType || tChar == toType) { |
| methodName = "Cast.narrow_" + toType.getName(); |
| } |
| } else if (tFloat == fromType || tDouble == fromType) { |
| if (tByte == toType || tShort == toType || tChar == toType || tInt == toType) { |
| methodName = "Cast.round_" + toType.getName(); |
| } |
| } |
| |
| if (methodName != null) { |
| JMethod castMethod = program.getIndexedMethod(methodName); |
| JMethodCall call = new JMethodCall(info, null, castMethod, toType); |
| call.addArg(expr); |
| ctx.replaceMe(call); |
| } else { |
| // Just remove the cast |
| ctx.replaceMe(expr); |
| } |
| } |
| |
| @Override |
| public void endVisit(JInstanceOf x, Context ctx) { |
| JReferenceType argType = (JReferenceType) x.getExpr().getType(); |
| JReferenceType toType = x.getTestType(); |
| // Only tests on run-time types are supported |
| assert (toType == toType.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); |
| } |
| |
| boolean isTrivialCast = program.typeOracle.castSucceedsTrivially(argType, toType) |
| // don't depend on type-tightener having run |
| || (program.typeOracle.isEffectivelyJavaScriptObject(argType) |
| && program.typeOracle.isEffectivelyJavaScriptObject(toType)); |
| if (pruneTrivialCasts && isTrivialCast) { |
| // trivially true if non-null; replace with a null test |
| JBinaryOperation eq = |
| new JBinaryOperation(x.getSourceInfo(), program.getTypePrimitiveBoolean(), |
| JBinaryOperator.NEQ, x.getExpr(), program.getLiteralNull()); |
| ctx.replaceMe(eq); |
| } else { |
| // Replace the instance of check by a call to the appropriate instanceof method in class |
| // Cast. |
| ctx.replaceMe(implementCastOrInstanceOfOperation(x.getSourceInfo(), x.getExpr(), toType, |
| instanceOfMethodsByTargetTypeCategory, false)); |
| } |
| } |
| } |
| |
| /** |
| * Determines the type category for a specific reference type. |
| */ |
| private TypeCategory determineTypeCategoryForType(JReferenceType type) { |
| TypeCategory typeCategory = TypeCategory.typeCategoryForType(type, program); |
| |
| assert EnumSet.of(TypeCategory.TYPE_JSO, TypeCategory.TYPE_JAVA_OBJECT_OR_JSO, |
| TypeCategory.TYPE_JAVA_LANG_OBJECT, TypeCategory.TYPE_JAVA_LANG_STRING, |
| TypeCategory.TYPE_JAVA_OBJECT, TypeCategory.TYPE_JS_PROTOTYPE, |
| TypeCategory.TYPE_JS_FUNCTION).contains(typeCategory); |
| |
| return typeCategory; |
| } |
| |
| /** |
| * Returns an expression implementing the instanceof/dynamicCast operations. |
| */ |
| private JMethodCall implementCastOrInstanceOfOperation(SourceInfo sourceInfo, |
| JExpression targetExpression, JReferenceType targetType, |
| Map<TypeCategory, JMethod> targetMethodByTypeCategory, boolean overrideReturnType) { |
| |
| TypeCategory targetTypeCategory = determineTypeCategoryForType(targetType); |
| JMethod method = targetMethodByTypeCategory.get(targetTypeCategory); |
| assert method != null; |
| JMethodCall call; |
| if (overrideReturnType) { |
| // Create a method call overriding the return type so that operations like Cast.dynamicCast |
| // don't change the type of the original method call expression. |
| call = new JMethodCall(sourceInfo, null, method, targetType); |
| } else { |
| call = new JMethodCall(sourceInfo, null, method); |
| } |
| call.addArg(targetExpression); |
| if (method.getParams().size() >= 2) { |
| // checking/casting to JSOs or Strings does not require a second parameter |
| call.addArg((new JRuntimeTypeReference(sourceInfo, program.getTypeJavaLangObject(), |
| targetType))); |
| } |
| if (method.getParams().size() == 3) { |
| |
| assert targetTypeCategory == TypeCategory.TYPE_JS_PROTOTYPE; |
| call.addArg(program.getStringLiteral(sourceInfo, |
| ((JInterfaceType) targetType).getJsPrototype())); |
| } |
| return call; |
| } |
| |
| public static void exec(JProgram program, boolean disableCastChecking, |
| boolean pruneTrivialCasts) { |
| new ImplementCastsAndTypeChecks(program, disableCastChecking, pruneTrivialCasts).execImpl(); |
| } |
| |
| public static void exec(JProgram program, boolean disableCastChecking) { |
| new ImplementCastsAndTypeChecks(program, disableCastChecking, true).execImpl(); |
| } |
| |
| private final boolean disableCastChecking; |
| private final boolean pruneTrivialCasts; |
| private final JProgram program; |
| |
| private Map<TypeCategory, JMethod> instanceOfMethodsByTargetTypeCategory = |
| Maps.newEnumMap(TypeCategory.class); |
| |
| private Map<TypeCategory, JMethod> dynamicCastMethodsByTargetTypeCategory = |
| Maps.newEnumMap(TypeCategory.class); |
| |
| private ImplementCastsAndTypeChecks(JProgram program, boolean disableCastChecking, |
| boolean pruneTrivialCasts) { |
| this.program = program; |
| this.disableCastChecking = disableCastChecking; |
| this.pruneTrivialCasts = pruneTrivialCasts; |
| |
| // Populate the necessary instanceOf methods. |
| this.instanceOfMethodsByTargetTypeCategory.put( |
| TypeCategory.TYPE_JAVA_OBJECT, program.getIndexedMethod("Cast.instanceOf")); |
| this.instanceOfMethodsByTargetTypeCategory.put( |
| TypeCategory.TYPE_JAVA_LANG_OBJECT, program.getIndexedMethod("Cast.instanceOfOrJso")); |
| this.instanceOfMethodsByTargetTypeCategory.put( |
| TypeCategory.TYPE_JAVA_OBJECT_OR_JSO, program.getIndexedMethod("Cast.instanceOfOrJso")); |
| this.instanceOfMethodsByTargetTypeCategory.put( |
| TypeCategory.TYPE_JSO, program.getIndexedMethod("Cast.instanceOfJso")); |
| this.instanceOfMethodsByTargetTypeCategory.put( |
| TypeCategory.TYPE_JAVA_LANG_STRING, program.getIndexedMethod("Cast.isJavaString")); |
| this.instanceOfMethodsByTargetTypeCategory.put( |
| TypeCategory.TYPE_JS_PROTOTYPE, program.getIndexedMethod("Cast.instanceOfJsPrototype")); |
| this.instanceOfMethodsByTargetTypeCategory.put( |
| TypeCategory.TYPE_JS_FUNCTION, program.getIndexedMethod("Cast.instanceOfJsFunction")); |
| |
| // Populate the necessary dynamicCast methods. |
| this.dynamicCastMethodsByTargetTypeCategory.put( |
| TypeCategory.TYPE_JAVA_OBJECT, program.getIndexedMethod("Cast.dynamicCast")); |
| this.dynamicCastMethodsByTargetTypeCategory.put( |
| TypeCategory.TYPE_JAVA_LANG_OBJECT, program.getIndexedMethod("Cast.dynamicCastAllowJso")); |
| this.dynamicCastMethodsByTargetTypeCategory.put( |
| TypeCategory.TYPE_JAVA_OBJECT_OR_JSO, program.getIndexedMethod("Cast.dynamicCastAllowJso")); |
| this.dynamicCastMethodsByTargetTypeCategory.put( |
| TypeCategory.TYPE_JSO, program.getIndexedMethod("Cast.dynamicCastJso")); |
| this.dynamicCastMethodsByTargetTypeCategory.put( |
| TypeCategory.TYPE_JAVA_LANG_STRING, program.getIndexedMethod("Cast.dynamicCastToString")); |
| this.dynamicCastMethodsByTargetTypeCategory.put( |
| TypeCategory.TYPE_JS_PROTOTYPE, program.getIndexedMethod("Cast.dynamicCastWithPrototype")); |
| this.dynamicCastMethodsByTargetTypeCategory.put( |
| TypeCategory.TYPE_JS_FUNCTION, program.getIndexedMethod("Cast.dynamicCastToJsFunction")); |
| } |
| |
| private void execImpl() { |
| ReplaceTypeChecksVisitor replacer = new ReplaceTypeChecksVisitor(); |
| replacer.accept(program); |
| } |
| } |