/* * 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.JDeclaredType; 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.dev.jjs.ast.RuntimeConstants; import com.google.gwt.dev.jjs.ast.js.JsniMethodRef; import com.google.gwt.thirdparty.guava.common.collect.Maps; 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(); 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( RuntimeConstants.CAST_THROW_CLASS_CAST_EXCEPTION_UNLESS_NULL); // Note, we must update the method call to return the null type. JMethodCall call = new JMethodCall(info, null, method, expr); call.overrideReturnType(toType); 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) || determineTypeCategoryForType(refType) == TypeCategory.TYPE_JAVA_LANG_OBJECT) { // 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.isPrimitiveType(); /* * 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(RuntimeConstants.LONG_LIB_TO_INT); JMethodCall call = new JMethodCall(info, null, castMethod); call.addArg(expr); expr = call; fromType = tInt; } else if (tInt == toType) { methodName = RuntimeConstants.LONG_LIB_TO_INT; } else if (tFloat == toType || tDouble == toType) { methodName = RuntimeConstants.LONG_LIB_TO_DOUBLE; } } if (toType == tLong && fromType != tLong) { // Longs get special treatment. if (tByte == fromType || tShort == fromType || tChar == fromType || tInt == fromType) { methodName = RuntimeConstants.LONG_LIB_FROM_INT; } else if (tFloat == fromType || tDouble == fromType) { methodName = RuntimeConstants.LONG_LIB_FROM_DOUBLE; } } 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, expr); call.overrideReturnType(toType); 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); } assert !toType.isJsNative() || !(toType instanceof JInterfaceType); 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 || determineTypeCategoryForType(toType) == TypeCategory.TYPE_JAVA_LANG_OBJECT) { // 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 typeCategory.castInstanceOfQualifier() != null; 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 = new JMethodCall(sourceInfo, null, method); 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.overrideReturnType(targetType); } call.addArg(targetExpression); if (method.getParams().size() < 2) { // The cast checking method does not require an additional parameter. This situation arises // when the call is a cast check and cast checking has been disabled or when the type category // provides enough information, e.g. TYPE_UNTYPED_ARRAY. return call; } else if (targetTypeCategory.requiresTypeId()) { call.addArg((new JRuntimeTypeReference(sourceInfo, program.getTypeJavaLangObject(), targetType))); return call; } else if (targetTypeCategory.requiresJsConstructor()) { JDeclaredType declaredType = (JDeclaredType) targetType; JMethod jsConstructor = JjsUtils.getJsNativeConstructorOrNull(declaredType); assert jsConstructor != null && declaredType.isJsNative(); call.addArg(new JsniMethodRef(sourceInfo, declaredType.getQualifiedJsName(), jsConstructor, program.getJavaScriptObject())); return call; } else { throw new AssertionError(); } } public static void exec(JProgram program, boolean pruneTrivialCasts) { new ImplementCastsAndTypeChecks(program, pruneTrivialCasts).execImpl(); } public static void exec(JProgram program) { new ImplementCastsAndTypeChecks(program, true).execImpl(); } 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 pruneTrivialCasts) { this.program = program; this.pruneTrivialCasts = pruneTrivialCasts; for (TypeCategory t : TypeCategory.values()) { String castInstanceOfQualifier = t.castInstanceOfQualifier(); if (castInstanceOfQualifier == null) { continue; } String instanceOfMethod = "Cast.instanceOf" + castInstanceOfQualifier; instanceOfMethodsByTargetTypeCategory.put(t, program.getIndexedMethod(instanceOfMethod)); String castMethod = "Cast.castTo" + castInstanceOfQualifier; dynamicCastMethodsByTargetTypeCategory.put(t, program.getIndexedMethod(castMethod)); } } private void execImpl() { new ReplaceTypeChecksVisitor().accept(program); } }