Implement UncheckedCast annotation. Change-Id: I7d3ac459a924082480d687353c4def33267977a4
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JTransformer.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JTransformer.java index 6478f28..0614d99 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JTransformer.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JTransformer.java
@@ -220,7 +220,8 @@ } public T missing(JNode x) { - throw new InternalCompilerException("Missing transform for " + x); + throw new InternalCompilerException( + "Missing transform for " + x + " (" + x.getClass().getSimpleName() + ")"); } public T transformNullLiteral(JNullLiteral x) { @@ -339,6 +340,10 @@ return transformExpression(x); } + public T transformUnsafeTypeCoercion(JUnsafeTypeCoercion x) { + return transformExpression(x); + } + public T transformWhileStatement(JWhileStatement x) { return transformStatement(x); } @@ -1023,6 +1028,12 @@ return false; } + public final boolean visit(JUnsafeTypeCoercion x, Context ctx) { + assert result == null; + result = transformUnsafeTypeCoercion(x); + return false; + } + public final boolean visit(JValueLiteral x, Context ctx) { assert result == null; result = transformValueLiteral(x);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JUnsafeTypeCoercion.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JUnsafeTypeCoercion.java new file mode 100644 index 0000000..6832647 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JUnsafeTypeCoercion.java
@@ -0,0 +1,73 @@ +/* + * Copyright 2016 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.ast; + +import com.google.gwt.dev.jjs.SourceInfo; + +/** + * An unsafe type coercion (mostly resulting form the UncheckedCast annotation). + */ +public class JUnsafeTypeCoercion extends JExpression { + + private JType coercionType; + private JExpression expression; + + public JUnsafeTypeCoercion(SourceInfo info, JType coercionType, JExpression expression) { + super(info); + this.coercionType = coercionType; + this.expression = expression; + } + + public JType getCoercionType() { + return coercionType; + } + + public JExpression getExpression() { + return expression; + } + + @Override + public JType getType() { + if (!expression.getType().canBeNull() && !coercionType.isNullType()) { + // Strengthen type to non null unless it has been determined that the type is not instantiable + // (and that is reflected by replacing the coercion type by the null type). + return coercionType.strengthenToNonNull(); + } + return coercionType; + } + + @Override + public boolean hasSideEffects() { + return expression.hasSideEffects(); + } + + /** + * Resolve an external reference during AST stitching. + */ + public void resolve(JType coercionType) { + assert coercionType.replaces(this.coercionType); + this.coercionType = coercionType; + } + + @Override + public void traverse(JVisitor visitor, Context ctx) { + if (visitor.visit(this, ctx)) { + expression = visitor.accept(expression); + } + visitor.endVisit(this, ctx); + } + +}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java index c83d046..a74785c 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java
@@ -477,6 +477,10 @@ endVisit((JExpression) x, ctx); } + public void endVisit(JUnsafeTypeCoercion x, Context ctx) { + endVisit((JExpression) x, ctx); + } + public void endVisit(JValueLiteral x, Context ctx) { endVisit((JLiteral) x, ctx); } @@ -793,6 +797,10 @@ return visit((JExpression) x, ctx); } + public boolean visit(JUnsafeTypeCoercion x, Context ctx) { + return visit((JExpression) x, ctx); + } + public boolean visit(JValueLiteral x, Context ctx) { return visit((JLiteral) x, ctx); }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java index a453256..9dfd509 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java
@@ -47,6 +47,7 @@ import com.google.gwt.dev.jjs.ast.JRunAsync; import com.google.gwt.dev.jjs.ast.JStringLiteral; import com.google.gwt.dev.jjs.ast.JThisRef; +import com.google.gwt.dev.jjs.ast.JUnsafeTypeCoercion; import com.google.gwt.dev.jjs.ast.JVisitor; import com.google.gwt.dev.jjs.ast.js.JMultiExpression; import com.google.gwt.dev.jjs.ast.js.JsniFieldRef; @@ -299,4 +300,11 @@ expression = new JThisRef(x.getSourceInfo(), x.getClassType()); return false; } + + @Override + public boolean visit(JUnsafeTypeCoercion x, Context ctx) { + expression = new JUnsafeTypeCoercion( + x.getSourceInfo(), x.getCoercionType(), cloneExpression(x.getExpression())); + return false; + } } \ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java index 1299734..cbc44f4 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
@@ -46,6 +46,7 @@ import com.google.gwt.dev.jjs.ast.JRunAsync; import com.google.gwt.dev.jjs.ast.JStringLiteral; import com.google.gwt.dev.jjs.ast.JType; +import com.google.gwt.dev.jjs.ast.JUnsafeTypeCoercion; import com.google.gwt.dev.jjs.ast.JVariable; import com.google.gwt.dev.jjs.ast.JVariableRef; import com.google.gwt.dev.jjs.ast.JVisitor; @@ -198,30 +199,7 @@ @Override public boolean visit(JCastOperation x, Context ctx) { - // Rescue any JavaScriptObject type that is the target of a cast. - JType targetType = x.getCastType(); - - if (!canBeInstantiatedInJavaScript(targetType)) { - return true; - } - rescue((JReferenceType) targetType, true); - JType exprType = x.getExpr().getType(); - if (program.typeOracle.isSingleJsoImpl(targetType)) { - /* - * It's a JSO interface, check if the source expr can be a live JSO: - * 1) source is java.lang.Object (JSO could have been assigned to it) - * 2) source is JSO - * 3) source is SingleJSO interface whose implementor is live - */ - if (program.getTypeJavaLangObject() == exprType - || program.typeOracle.canBeJavaScriptObject(exprType)) { - // source is JSO or SingleJso interface whose implementor is live - JClassType jsoImplementor = - program.typeOracle.getSingleJsoImpl((JReferenceType) targetType); - rescue(jsoImplementor, true); - } - } - + rescueByTypeCoercion(x.getCastType(), x.getExpr().getType()); return true; } @@ -509,6 +487,35 @@ return true; } + @Override + public boolean visit(JUnsafeTypeCoercion x, Context ctx) { + rescueByTypeCoercion(x.getCoercionType(), x.getExpression().getType()); + return true; + } + + private void rescueByTypeCoercion(JType targetType, JType expressionType) { + // Rescue any JavaScriptObject type that is the target of a cast. + if (!canBeInstantiatedInJavaScript(targetType)) { + return; + } + rescue((JReferenceType) targetType, true); + if (program.typeOracle.isSingleJsoImpl(targetType)) { + /* + * It's a JSO interface, check if the source expr can be a live JSO: + * 1) source is java.lang.Object (JSO could have been assigned to it) + * 2) source is JSO + * 3) source is SingleJSO interface whose implementor is live + */ + if (program.getTypeJavaLangObject() == expressionType + || program.typeOracle.canBeJavaScriptObject(expressionType)) { + // source is JSO or SingleJso interface whose implementor is live + JClassType jsoImplementor = + program.typeOracle.getSingleJsoImpl((JReferenceType) targetType); + rescue(jsoImplementor, true); + } + } + } + private boolean canBeInstantiatedInJavaScript(JType type) { // Technically, JsType/JsFunction are also instantiatable in JavaScript but we don't track // them using similar to JSO as if we do that then after cast normalization, they got pruned.
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java index f78edbb..f96c3d8 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -92,6 +92,7 @@ import com.google.gwt.dev.jjs.ast.JTryStatement; import com.google.gwt.dev.jjs.ast.JType; import com.google.gwt.dev.jjs.ast.JUnaryOperator; +import com.google.gwt.dev.jjs.ast.JUnsafeTypeCoercion; import com.google.gwt.dev.jjs.ast.JVariable; import com.google.gwt.dev.jjs.ast.JVisitor; import com.google.gwt.dev.jjs.ast.JWhileStatement; @@ -1153,6 +1154,11 @@ } @Override + public JsNode transformUnsafeTypeCoercion(JUnsafeTypeCoercion unsafeTypeCoercion) { + return transform(unsafeTypeCoercion.getExpression()); + } + + @Override public JsNode transformWhileStatement(JWhileStatement whileStatement) { SourceInfo info = whileStatement.getSourceInfo(); JsWhile stmt = new JsWhile(info);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java index 15d44e7..5930ad1 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
@@ -91,6 +91,7 @@ import com.google.gwt.dev.jjs.ast.JTryStatement; import com.google.gwt.dev.jjs.ast.JType; import com.google.gwt.dev.jjs.ast.JUnaryOperator; +import com.google.gwt.dev.jjs.ast.JUnsafeTypeCoercion; import com.google.gwt.dev.jjs.ast.JVariable; import com.google.gwt.dev.jjs.ast.JWhileStatement; import com.google.gwt.dev.jjs.ast.js.JMultiExpression; @@ -1541,24 +1542,26 @@ } } - JMethodCall call = new JMethodCall(info, receiver, method); + JMethodCall methodCall = new JMethodCall(info, receiver, method); // On a super ref, don't allow polymorphic dispatch. Oddly enough, // QualifiedSuperReference not derived from SuperReference! boolean isSuperRef = x.receiver instanceof SuperReference || x.receiver instanceof QualifiedSuperReference; if (isSuperRef) { - call.setStaticDispatchOnly(); + methodCall.setStaticDispatchOnly(); } - // The arguments come first... - call.addArgs(arguments); + // The arguments come first. + methodCall.addArgs(arguments); if (x.valueCast != null) { - JType castType = typeMap.get(x.valueCast); - push(maybeCast(castType, call)); + JType targetType = typeMap.get(x.valueCast); + push(isUncheckedGenericMethodCall(x) + ? maybeInsertUnsafeTypeCoersion(targetType, methodCall) + : maybeCast(targetType, methodCall)); } else { - push(call); + push(methodCall); } } catch (Throwable e) { throw translateException(x, e); @@ -1568,7 +1571,6 @@ @Override public void endVisit(MethodDeclaration x, ClassScope scope) { try { - if (x.isNative()) { processNativeMethod(x); } else { @@ -3068,12 +3070,20 @@ private JExpression maybeCast(JType expected, JExpression expression) { if (expected != expression.getType()) { - // Must be a generic; insert a cast operation. - JReferenceType toType = (JReferenceType) expected; - return new JCastOperation(expression.getSourceInfo(), toType, expression); - } else { - return expression; + // Must be a generic cast; insert a cast operation. + return new JCastOperation(expression.getSourceInfo(), expected, expression); } + + return expression; + } + + private JExpression maybeInsertUnsafeTypeCoersion(JType expected, JExpression expression) { + if (expected != expression.getType()) { + // A generic call marked as @UncheckedCast. + return new JUnsafeTypeCoercion(expression.getSourceInfo(), expected, expression); + } + + return expression; } private JNode pop() { @@ -4152,7 +4162,15 @@ x.setSuppressedWarnings(JdtUtil.getSuppressedWarnings(annotations)); } - private void maybeSetInliningMode(AbstractMethodDeclaration x, JMethod method) { + private static boolean isUncheckedGenericMethodCall(MessageSend messageSend) { + if (messageSend.binding.genericMethod() != null) { + return JdtUtil.getAnnotation(messageSend.binding.genericMethod(), + "javaemul.internal.annotations.UncheckedCast") != null; + } + return false; + } + + private static void maybeSetInliningMode(AbstractMethodDeclaration x, JMethod method) { MethodBinding bind = x.binding; if (JdtUtil.getAnnotation(bind, "javaemul.internal.annotations.DoNotInline") != null) { method.setInliningMode(InliningMode.DO_NOT_INLINE); @@ -4161,8 +4179,7 @@ } } - private void maybeSetHasNoSideEffects(AbstractMethodDeclaration x, - JMethod method) { + private static void maybeSetHasNoSideEffects(AbstractMethodDeclaration x, JMethod method) { if (JdtUtil.getAnnotation(x.binding, "javaemul.internal.annotations.HasNoSideEffects") != null) { method.setHasSideEffects(false);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java index ae9af27..feaf680 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
@@ -67,6 +67,7 @@ import com.google.gwt.dev.jjs.ast.JThisRef; import com.google.gwt.dev.jjs.ast.JTryStatement; import com.google.gwt.dev.jjs.ast.JType; +import com.google.gwt.dev.jjs.ast.JUnsafeTypeCoercion; import com.google.gwt.dev.jjs.ast.JVariable; import com.google.gwt.dev.jjs.ast.RuntimeConstants; import com.google.gwt.dev.jjs.ast.js.JDebuggerStatement; @@ -361,6 +362,11 @@ } @Override + public void endVisit(JUnsafeTypeCoercion x, Context ctx) { + x.resolve(translate(x.getCoercionType())); + } + + @Override public void endVisit(JVariable x, Context ctx) { x.setType(translate(x.getType())); }
diff --git a/user/super/com/google/gwt/emul/javaemul/internal/annotations/UncheckedCast.java b/user/super/com/google/gwt/emul/javaemul/internal/annotations/UncheckedCast.java new file mode 100644 index 0000000..82c5597 --- /dev/null +++ b/user/super/com/google/gwt/emul/javaemul/internal/annotations/UncheckedCast.java
@@ -0,0 +1,28 @@ +/* + * Copyright 2016 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 javaemul.internal.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * An annotation to mark a method as unchecked to prevent the compiler from inserting casts on + * returns call sites due to erasure. + */ +@Target(ElementType.METHOD) +@CompilerHint +public @interface UncheckedCast { +}
diff --git a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java index 3c5fc29..55f0681 100644 --- a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java +++ b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
@@ -39,6 +39,7 @@ import com.google.gwt.dev.jjs.test.NativeLongTest; import com.google.gwt.dev.jjs.test.ObjectIdentityTest; import com.google.gwt.dev.jjs.test.SingleJsoImplTest; +import com.google.gwt.dev.jjs.test.UncheckedCastTest; import com.google.gwt.dev.jjs.test.VarargsTest; import com.google.gwt.dev.jjs.test.singlejso.TypeHierarchyTest; import com.google.gwt.junit.tools.GWTTestSuite; @@ -81,6 +82,7 @@ suite.addTestSuite(ObjectIdentityTest.class); suite.addTestSuite(SingleJsoImplTest.class); suite.addTestSuite(TypeHierarchyTest.class); + suite.addTestSuite(UncheckedCastTest.class); suite.addTestSuite(VarargsTest.class); // $JUnit-END$
diff --git a/user/test/com/google/gwt/dev/jjs/OptimizedOnlyCompilerSuite.java b/user/test/com/google/gwt/dev/jjs/OptimizedOnlyCompilerSuite.java index b95797c..bb5c85a 100644 --- a/user/test/com/google/gwt/dev/jjs/OptimizedOnlyCompilerSuite.java +++ b/user/test/com/google/gwt/dev/jjs/OptimizedOnlyCompilerSuite.java
@@ -21,6 +21,7 @@ import com.google.gwt.dev.jjs.optimized.JsOverlayMethodOptimizationTest; import com.google.gwt.dev.jjs.optimized.SpecializationTest; import com.google.gwt.dev.jjs.optimized.StringOptimizationTest; +import com.google.gwt.dev.jjs.optimized.UncheckedCastOptimizationTest; import com.google.gwt.dev.jjs.test.HasNoSideEffectsTest; import com.google.gwt.dev.jjs.test.RunAsyncContentTest; import com.google.gwt.junit.tools.GWTTestSuite; @@ -43,6 +44,7 @@ suite.addTestSuite(JsOverlayMethodOptimizationTest.class); suite.addTestSuite(SpecializationTest.class); suite.addTestSuite(HasNoSideEffectsTest.class); + suite.addTestSuite(UncheckedCastOptimizationTest.class); // RunAsyncContentTest relies in string interning for its assertions which is now always off // in non optimzied compiles. suite.addTestSuite(RunAsyncContentTest.class);
diff --git a/user/test/com/google/gwt/dev/jjs/optimized/UncheckedCastOptimizationTest.java b/user/test/com/google/gwt/dev/jjs/optimized/UncheckedCastOptimizationTest.java new file mode 100644 index 0000000..021beed --- /dev/null +++ b/user/test/com/google/gwt/dev/jjs/optimized/UncheckedCastOptimizationTest.java
@@ -0,0 +1,60 @@ +/* + * Copyright 2016 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.optimized; + +import com.google.gwt.junit.DoNotRunWith; +import com.google.gwt.junit.Platform; + +import javaemul.internal.annotations.UncheckedCast; + +import jsinterop.annotations.JsPackage; +import jsinterop.annotations.JsType; + +/** + * Tests that unchecked casts allow other optimizations to happen. + */ +@DoNotRunWith(Platform.Devel) +public class UncheckedCastOptimizationTest extends OptimizationTestBase { + + @JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Array") + private static class JsArray { + public int length; + } + + @JsType(isNative = true) + private interface NativeObject { + } + + @UncheckedCast + public static <T> T uncheckedCast(Object o) { + return (T) o; + } + public static int unckeckedCastOperation() { + JsArray array = uncheckedCast(new NativeObject[] {null, null}); + return array.length; + } + + private static native String getGeneratedUncheckedCastFunctionDefinition() /*-{ + return function() { + @com.google.gwt.dev.jjs.optimized.UncheckedCastOptimizationTest::unckeckedCastOperation()(); + }.toString(); + }-*/; + + public void testUncheckedCastAllowsOptimizaionsC() throws Exception { + String functionDef = getGeneratedUncheckedCastFunctionDefinition(); + assertFunctionMatches(functionDef, "var<obf>;<obf>=[null,null]"); + } +}
diff --git a/user/test/com/google/gwt/dev/jjs/test/UncheckedCastTest.java b/user/test/com/google/gwt/dev/jjs/test/UncheckedCastTest.java new file mode 100644 index 0000000..bfe873b --- /dev/null +++ b/user/test/com/google/gwt/dev/jjs/test/UncheckedCastTest.java
@@ -0,0 +1,48 @@ +/* + * 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.test; + +import com.google.gwt.junit.DoNotRunWith; +import com.google.gwt.junit.Platform; +import com.google.gwt.junit.client.GWTTestCase; + +import javaemul.internal.annotations.UncheckedCast; + +/** + * Tests for {@link UncheckedCast}. + */ +@DoNotRunWith(Platform.Devel) +public class UncheckedCastTest extends GWTTestCase { + + @Override + public String getModuleName() { + return "com.google.gwt.dev.jjs.CompilerSuite"; + } + + @UncheckedCast + private static <T> T uncheckedCast(Object object) { + return (T) object; + } + + public void testUnsafeCast() { + Integer boxedInt = uncheckedCast(new Object()); + assertNotNull(boxedInt); + Double d = uncheckedCast(new Object()); + assertNotNull(d); + boolean unboxedBoolean = uncheckedCast(new Double(12)); + assertTrue(unboxedBoolean); + } +}