Allow calls to devirtualized methods from JSNI.
Only disallow taking references to devirtualized methods but
allow invocations.
Bug: #9356
Bug-Link: http://github.com/gwtproject/gwt/issues/9356
Change-Id: I98d3248aedc705439db737b2d13e5bbe28469b5f
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java
index 795a91c..67d5e4d 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java
@@ -36,15 +36,25 @@
import com.google.gwt.dev.jjs.ast.JTypeOracle;
import com.google.gwt.dev.jjs.ast.JVariableRef;
import com.google.gwt.dev.jjs.ast.RuntimeConstants;
+import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
+import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
import com.google.gwt.dev.jjs.impl.MakeCallsStatic.CreateStaticImplsVisitor;
import com.google.gwt.dev.jjs.impl.MakeCallsStatic.StaticCallConverter;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsInvocation;
+import com.google.gwt.dev.js.ast.JsModVisitor;
+import com.google.gwt.dev.js.ast.JsNameRef;
+import com.google.gwt.thirdparty.guava.common.collect.Iterables;
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.Sets;
+import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Devirtualization is the process of converting virtual method calls on instances that might be
@@ -104,6 +114,32 @@
}
@Override
+ public void endVisit(JsniMethodRef x, Context ctx) {
+ JMethod method = x.getTarget();
+ if (method == null || !mightNeedDevirtualization(method)) {
+ return;
+ }
+ ensureDevirtualVersionExists(method);
+
+ // Replace the JMethod in jsni reference to a reference to the devirtualized method.
+ // Note that a JsniMethodRefs is a pair containing the Jsni reference as text (i.e.
+ // "@java.lang.Boolean::booleanValue") and a reference to the actual JMethod in the AST;
+ // in generation time the actual JMethod that is called is looked up from the reference text.
+ //
+ // Here we just replace the JMethod the reference is pointing to without updating the
+ // reference text.
+ //
+ // Keeping the "key" unchanged avoid the necessity to sync up with the modifications in the
+ // JS AST when the JsniMethodBody is processed.
+ JMethod devirtualMethod = devirtualMethodByMethod.get(method);
+ ctx.replaceMe(new JsniMethodRef(
+ x.getSourceInfo(),
+ x.getIdent(),
+ devirtualMethod,
+ program.getJavaScriptObject()));
+ }
+
+ @Override
public void endVisit(JMethodCall x, Context ctx) {
JMethod method = x.getTarget();
if (!method.needsDynamicDispatch()) {
@@ -137,6 +173,57 @@
return true;
}
+ @Override
+ public boolean visit(JsniMethodBody x, Context ctx) {
+ final Set<String> devirtualMethodJsniIdentifiers = Sets.newHashSet();
+ for (JsniMethodRef jsniMethodRef : x.getJsniMethodRefs()) {
+ JMethod target = jsniMethodRef.getTarget();
+ if (target != null && mightNeedDevirtualization(target)) {
+ devirtualMethodJsniIdentifiers.add(jsniMethodRef.getIdent());
+ }
+ }
+
+ // Devirtualize jsni method calls.
+ new JsModVisitor() {
+ @Override
+ public void endVisit(JsInvocation x, JsContext ctx) {
+ if (!(x.getQualifier() instanceof JsNameRef)) {
+ // If the invocation does not have a name as a qualifier then it is an expression and
+ // cannot be a jsni method reference.
+ return;
+ }
+ JsNameRef nameRef = (JsNameRef) x.getQualifier();
+ if (!nameRef.isJsniReference()) {
+ // The invocation is not to a JSNI method.
+ return;
+ }
+
+ // Retrieve the method referred by the JsniMethodRef and check whether it needs
+ // devirtualization.
+ if (!devirtualMethodJsniIdentifiers.contains(nameRef.getIdent())) {
+ return;
+ }
+ // Devirtualize method by rewriting
+ // a.@java.lang.Boolean::booleanValue() ==> @java.lang.Boolean::booleanValue(a).
+ //
+ // Not the the reference identifier is *NOT* changed and will act a the key in the lookup
+ // for the corresponding JMethod which is contained in the corresponding JsniMethodRef
+ // node.
+ ctx.replaceMe(
+ new JsInvocation(
+ x.getSourceInfo(),
+ new JsNameRef(
+ nameRef.getSourceInfo(), nameRef.getIdent()),
+ Iterables.concat(
+ Collections.singleton(nameRef.getQualifier()), x.getArguments())));
+ return;
+ }
+ }.accept(x.getFunc());
+
+ // Now go ahead and fix the corresponding JSNI references.
+ return true;
+ }
+
/**
* Constructs and caches a method that is a new static version of the given method or a
* trampoline function that wraps a new static version of the given method. It chooses which to
@@ -176,10 +263,7 @@
staticImplCreator.getOrCreateStaticImpl(program, overridingMethod);
devirtualMethodByMethod.put(method, jsoStaticImpl);
} else if (isOverlayMethod(method)) {
- // A virtual dispatch on a target that is already known to be a JavaScriptObject, this
- // should have been handled by MakeCallsStatic.
- // TODO(rluble): verify that this case can not arise in optimized mode and if so
- // remove as is an unnecessary optimization.
+ // A virtual dispatch on a target that is already known to be an overlay method,.
JMethod devirtualMethod = staticImplCreator.getOrCreateStaticImpl(program, method);
devirtualMethodByMethod.put(method, devirtualMethod);
} else {
@@ -193,7 +277,6 @@
}
private boolean mightNeedDevirtualization(JMethod method, JReferenceType instanceType) {
- // todo remove instance check
if (instanceType == null || !method.needsDynamicDispatch()) {
return false;
}
@@ -207,6 +290,9 @@
// Methods in a native JsType that are not JsOverlay should NOT be devirtualized.
return false;
}
+ if (instanceType.isNullType()) {
+ instanceType = method.getEnclosingType();
+ }
EnumSet<DispatchType> dispatchType = program.getDispatchType(instanceType);
dispatchType.remove(DispatchType.HAS_JAVA_VIRTUAL_DISPATCH);
return !dispatchType.isEmpty();
@@ -233,7 +319,7 @@
* Maps each Object instance methods (ie, {@link Object#equals(Object)}) onto
* its corresponding devirtualizing method.
*/
- protected Map<JMethod, JMethod> devirtualMethodByMethod = Maps.newHashMap();
+ private Map<JMethod, JMethod> devirtualMethodByMethod = Maps.newHashMap();
/**
* Contains the Cast.hasJavaObjectVirtualDispatch method.
@@ -311,8 +397,7 @@
return;
}
- RewriteVirtualDispatches rewriter = new RewriteVirtualDispatches();
- rewriter.accept(program);
+ new RewriteVirtualDispatches().accept(program);
}
/**
@@ -554,4 +639,8 @@
staticImplCreator.getOrCreateStaticImpl(program, overridingMethod));
}
}
+
+ private static String getJsniReferenceIdentifier(JMethod method) {
+ return "@" + method.getJsniSignature(true, false);
+ }
}
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 4a24c45..b076ab3 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
@@ -1508,15 +1508,15 @@
@Override
public JsFunction transformJsniMethodBody(JsniMethodBody jsniMethodBody) {
- final Map<String, JNode> jsniMap = Maps.newHashMap();
+ final Map<String, JNode> nodeByJsniReference = Maps.newHashMap();
for (JsniClassLiteral ref : jsniMethodBody.getClassRefs()) {
- jsniMap.put(ref.getIdent(), ref.getField());
+ nodeByJsniReference.put(ref.getIdent(), ref.getField());
}
for (JsniFieldRef ref : jsniMethodBody.getJsniFieldRefs()) {
- jsniMap.put(ref.getIdent(), ref.getField());
+ nodeByJsniReference.put(ref.getIdent(), ref.getField());
}
for (JsniMethodRef ref : jsniMethodBody.getJsniMethodRefs()) {
- jsniMap.put(ref.getIdent(), ref.getTarget());
+ nodeByJsniReference.put(ref.getIdent(), ref.getTarget());
}
final JsFunction function = jsniMethodBody.getFunc();
@@ -1549,7 +1549,7 @@
// Replace invocation to ctor with a new op.
String ident = ref.getIdent();
- JNode node = jsniMap.get(ident);
+ JNode node = nodeByJsniReference.get(ident);
assert node instanceof JConstructor;
assert ref.getQualifier() == null;
JsName jsName = names.get(node);
@@ -1567,7 +1567,7 @@
}
String ident = x.getIdent();
- JNode node = jsniMap.get(ident);
+ JNode node = nodeByJsniReference.get(ident);
assert (node != null);
if (node instanceof JField) {
JField field = (JField) node;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRestrictionChecker.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRestrictionChecker.java
index 631213f..8646b71 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRestrictionChecker.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRestrictionChecker.java
@@ -22,9 +22,17 @@
import com.google.gwt.dev.jjs.ast.JMethodBody;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JVisitor;
+import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsExpression;
+import com.google.gwt.dev.js.ast.JsInvocation;
+import com.google.gwt.dev.js.ast.JsModVisitor;
+import com.google.gwt.dev.js.ast.JsNameRef;
+import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
+import java.util.Map;
import java.util.Set;
/**
@@ -51,39 +59,82 @@
}
@Override
- public boolean visit(JsniMethodRef x, Context ctx) {
- checkJsniMethodReference(x);
- return true;
+ public boolean visit(final JsniMethodBody x, Context ctx) {
+ final Map<String, JsniMethodRef> methodsByJsniReference = Maps.newHashMap();
+ for (JsniMethodRef ref : x.getJsniMethodRefs()) {
+ methodsByJsniReference.put(ref.getIdent(), ref);
+ }
+ if (methodsByJsniReference.isEmpty()) {
+ return false;
+ }
+
+ // Examine the JS AST that represents the JSNI method body to check for devirtualizable
+ // methods references that are not directly called.
+ new JsModVisitor() {
+ @Override
+ public boolean visit(JsInvocation x, JsContext ctx) {
+ if (!(x.getQualifier() instanceof JsNameRef)) {
+ // If the invocation does not have a name as a qualifier (it might be an
+ // expression), the it is certainly not a JSNI method reference; but it might
+ // contain one so explore its subnodes the usual way.
+ return true;
+ }
+ JsNameRef ref = (JsNameRef) x.getQualifier();
+ if (!ref.isJsniReference()) {
+ // The invocation is not to a JSNI method; but its subnodes might contain one
+ // hence explore them the usual way.
+ return true;
+ }
+
+ // Skip the method JsNameRef but check the qualifier.
+ JsExpression methodQualifier = ref.getQualifier();
+ if (methodQualifier != null) {
+ // Even if it is a direct call, there might be a reference in the qualifier.
+ accept(methodQualifier);
+ }
+
+ // This is a direct call so if it was a JSNI reference to a devirtualized method
+ // it is safe, as it will be rewritten by {@see Devirtualizer}.
+ return false;
+ }
+
+ @Override
+ public void endVisit(JsNameRef x, JsContext ctx) {
+ JsniMethodRef jsniMethodReference = methodsByJsniReference.get(x.getIdent());
+ if (jsniMethodReference != null) {
+ // This is a JSNI reference that is not in a direct call, so check if it is valid.
+ checkJsniMethodReference(jsniMethodReference);
+ }
+ }
+ }.accept(x.getFunc());
+ return false;
}
private void checkJsniMethodReference(JsniMethodRef jsniMethodReference) {
JMethod method = jsniMethodReference.getTarget();
JDeclaredType enclosingType = method.getEnclosingType();
- if (isNonStaticJsoClassDispatch(method, enclosingType)) {
+ if (isNonStaticJsoClassDispatch(method, enclosingType)
+ || isJsoInterface(enclosingType)) {
logError(jsniMethodReference,
- "Cannot call non-static method %s on an instance which is a "
- + "subclass of JavaScriptObject. Only static method calls on JavaScriptObject "
- + "subclasses are allowed in JSNI.",
- getDescription(method));
- } else if (isJsoInterface(enclosingType)) {
- logError(jsniMethodReference,
- "Cannot call method %s on an instance which might be a JavaScriptObject. "
- + "Such a method call is only allowed in pure Java (non-JSNI) functions.",
+ "Method %s is implemented by a JSO and can only be used in calls "
+ + "within a JSNI method body.",
getDescription(method));
} else if (program.isRepresentedAsNativeJsPrimitive(enclosingType)
&& !method.isStatic()
&& !method.isConstructor()) {
logError(jsniMethodReference,
- "Cannot call method %s. Instance methods on %s cannot be called from JSNI.",
+ "Method %s is implemented by devirtualized type %s JSO and can only be used in "
+ + "calls within a JSNI method body.",
getDescription(method),
getDescription(enclosingType));
} else if (typesRequiringTrampolineDispatch.contains(enclosingType)
&& !method.isStatic()
&& !method.isConstructor()) {
logWarning(jsniMethodReference,
- "Unsafe call to method %s. Instance methods from %s should "
- + "not be called on Boolean, Double, String, Array or JSO instances from JSNI.",
+ "Unsafe reference to method %s. Instance methods from %s should "
+ + "not be called on Boolean, Double, String, Array or JSO instances "
+ + "from within a JSNI method body.",
getDescription(method),
getDescription(enclosingType));
}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JsniRestrictionCheckerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JsniRestrictionCheckerTest.java
index 9aabbf9..48cd201 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JsniRestrictionCheckerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JsniRestrictionCheckerTest.java
@@ -25,53 +25,67 @@
*/
public class JsniRestrictionCheckerTest extends OptimizerTestBase {
- public void testConstructorsOnDevirtualizedTypesSucceeds() throws Exception {
- addSnippetImport("com.google.gwt.core.client.JavaScriptObject");
+ public void testInvocationInDevirtualizedTypesSucceeds() throws Exception {
addSnippetClassDecl(
"static class Buggy {",
+ " interface IBar {",
+ " void bar();",
+ " }",
+ " static final class Foo ",
+ " extends com.google.gwt.core.client.JavaScriptObject implements IBar {",
+ " protected Foo() { };",
+ " void foo() { };",
+ " public void bar() { };",
+ " static void staticFoo() { };",
+ " }",
" native void jsniMethod(Object o) /*-{",
" @java.lang.Double::new(D)();",
" @java.lang.Boolean::new(Z)();",
+ " new Object().@java.lang.Number::doubleValue()();",
+ " new Object().@java.lang.Double::doubleValue()();",
+ " new Object().@Buggy.Foo::foo()();",
+ " new Object().@Buggy.IBar::bar()();",
+ " @Buggy.Foo::staticFoo()();",
" }-*/;",
"}");
-
assertCompileSucceeds("new Buggy().jsniMethod(null);");
}
- public void testInstanceCallToDevirtualizedFails() throws Exception {
+ public void testReferenceToDevirtualizedInstanceMethodFails() throws Exception {
addSnippetImport("com.google.gwt.core.client.JavaScriptObject");
addSnippetClassDecl(
"static class Buggy {",
" native void jsniMethod(Object o) /*-{",
- " new Object().@java.lang.Double::doubleValue()();",
+ " var a = new Object().@java.lang.Double::doubleValue();",
" }-*/;",
"}");
assertCompileFails("new Buggy().jsniMethod(null);",
- "Line 6: Cannot call method 'double Double.doubleValue()'. Instance methods on 'Double' "
- + "cannot be called from JSNI.");
+ "Line 6: Method 'double Double.doubleValue()' is implemented by devirtualized "
+ + "type 'Double' JSO and can only be used in calls within a JSNI method body.");
}
- public void testInstanceCallToTrampolineWarns() throws Exception {
+ public void testReferenceToTrampolineWarns() throws Exception {
addSnippetImport("com.google.gwt.core.client.JavaScriptObject");
addSnippetClassDecl(
"static class Buggy {",
" native void jsniMethod(Object o) /*-{",
- " new Object().@java.lang.Number::doubleValue()();",
- " new Object().@java.lang.CharSequence::charAt(I)(0);",
- " \"Hello\".@java.lang.Object::toString()();",
+ " var a = new Object().@java.lang.Number::doubleValue();",
+ " var a = new Object().@java.lang.CharSequence::charAt(I);",
+ " var a = \"Hello\".@java.lang.Object::toString();",
" }-*/;",
"}");
assertCompileSucceeds("new Buggy().jsniMethod(null);",
- "Line 6: Unsafe call to method 'double Number.doubleValue()'. Instance methods from "
- + "'Number' should not be called on Boolean, Double, String, Array or JSO instances "
- + "from JSNI.",
- "Line 7: Unsafe call to method 'char CharSequence.charAt(int)'. Instance methods from "
- + "'CharSequence' should not be called on Boolean, Double, String, Array or JSO "
- + "instances from JSNI.",
- "Line 8: Unsafe call to method 'String Object.toString()'. Instance methods from 'Object' "
- + "should not be called on Boolean, Double, String, Array or JSO instances from JSNI.");
+ "Line 6: Unsafe reference to method 'double Number.doubleValue()'. "
+ + "Instance methods from 'Number' should not be called on Boolean, Double, String, "
+ + "Array or JSO instances from within a JSNI method body.",
+ "Line 7: Unsafe reference to method 'char CharSequence.charAt(int)'. Instance methods from"
+ + " 'CharSequence' should not be called on Boolean, Double, String, Array or JSO"
+ + " instances from within a JSNI method body.",
+ "Line 8: Unsafe reference to method 'String Object.toString()'. Instance methods from "
+ + "'Object' should not be called on Boolean, Double, String, Array or JSO instances "
+ + "from within a JSNI method body.");
}
public void testStaticJsoDispatchSucceeds() throws Exception {
@@ -102,14 +116,13 @@
" public void foo() { };",
" }",
" native void jsniMethod(Object o) /*-{",
- " new Object().@Buggy.IFoo::foo()();",
+ " var a = new Object().@Buggy.IFoo::foo();",
" }-*/;",
"}");
assertCompileFails("new Buggy().jsniMethod(null);",
- "Line 13: Cannot call method 'void EntryPoint.Buggy.IFoo.foo()' on an instance which might "
- + "be a JavaScriptObject. Such a method call is only allowed in pure Java (non-JSNI) "
- + "functions.");
+ "Line 13: Method 'void EntryPoint.Buggy.IFoo.foo()' is implemented by a JSO and can only "
+ + "be used in calls within a JSNI method body.");
}
public void testNonstaticJsoDispatchFails() throws Exception {
@@ -117,14 +130,13 @@
addSnippetClassDecl(
"static class Buggy {",
" native void jsniMethod(Object o) /*-{",
- " new Object().@com.google.gwt.core.client.JavaScriptObject::toString()();",
+ " var a = new Object().@com.google.gwt.core.client.JavaScriptObject::toString();",
" }-*/;",
"}");
assertCompileFails("new Buggy().jsniMethod(null);",
- "Line 6: Cannot call non-static method 'String JavaScriptObject.toString()' on an instance "
- + "which is a subclass of JavaScriptObject. Only static method calls on "
- + "JavaScriptObject subclasses are allowed in JSNI.");
+ "Line 6: Method 'String JavaScriptObject.toString()' is implemented by a JSO and can "
+ + "only be used in calls within a JSNI method body.");
}
public void testNonstaticJsoSubclassDispatchFails() throws Exception {
@@ -136,14 +148,13 @@
" void foo() { };",
" }",
" native void jsniMethod(Object o) /*-{",
- " new Object().@Buggy.Foo::foo()();",
+ " var a = new Object().@Buggy.Foo::foo();",
" }-*/;",
"}");
assertCompileFails("new Buggy().jsniMethod(null);",
- "Line 10: Cannot call non-static method 'void EntryPoint.Buggy.Foo.foo()' on an instance "
- + "which is a subclass of JavaScriptObject. Only static method calls on "
- + "JavaScriptObject subclasses are allowed in JSNI.");
+ "Line 10: Method 'void EntryPoint.Buggy.Foo.foo()' is implemented by a JSO and can "
+ + "only be used in calls within a JSNI method body.");
}
public void testStringInstanceMethodCallFail() throws Exception {
@@ -151,13 +162,13 @@
"static class Buggy {",
" static String foo;",
" native void jsniMethod(Object o) /*-{",
- " \"Hello\".@java.lang.String::length()();",
+ " var a = \"Hello\".@java.lang.String::length();",
" }-*/;",
"}");
assertCompileFails("new Buggy().jsniMethod(null);",
- "Line 6: Cannot call method 'int String.length()'. Instance methods on 'String' cannot be "
- + "called from JSNI.");
+ "Line 6: Method 'int String.length()' is implemented by devirtualized type 'String' "
+ + "JSO and can only be used in calls within a JSNI method body.");
}
public void testStringStaticMethodCallSucceeds() throws Exception {
@@ -165,7 +176,7 @@
"static class Buggy {",
" static String foo;",
" native void jsniMethod(Object o) /*-{",
- " @java.lang.String::valueOf(Z)();",
+ " var a = @java.lang.String::valueOf(Z);",
" }-*/;",
"}");
diff --git a/user/test/com/google/gwt/dev/jjs/test/JsniDispatchTest.java b/user/test/com/google/gwt/dev/jjs/test/JsniDispatchTest.java
index ecdd3a5..67f160f 100644
--- a/user/test/com/google/gwt/dev/jjs/test/JsniDispatchTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/JsniDispatchTest.java
@@ -15,6 +15,7 @@
*/
package com.google.gwt.dev.jjs.test;
+import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.junit.client.GWTTestCase;
/**
@@ -95,4 +96,51 @@
assertEquals("Hello from SuperSuperSuperFoo 10 times",
callSayHelloNTimesAtSuperSuperSuperFoo(new Foo()));
}
+
+ public native int doubleToInt(Double d) /*-{
+ return d.@java.lang.Double::intValue()();
+ }-*/;
+
+ public native int numberToInt(Number n) /*-{
+ return n.@java.lang.Number::intValue()();
+ }-*/;
+
+ interface DualInterface {
+ int m();
+ }
+
+ private static class JavaImplementor implements DualInterface {
+ public int m() {
+ return 1;
+ }
+ }
+
+ private static class JsoImplementor extends JavaScriptObject implements DualInterface {
+ public final native int m() /*-{
+ return this.a;
+ }-*/;
+
+ protected JsoImplementor() {
+ }
+ }
+
+ public native int callMOnDual(DualInterface i) /*-{
+ return i.@JsniDispatchTest.DualInterface::m()();
+ }-*/;
+
+ public native int callMOnJso(JsoImplementor jso) /*-{
+ return jso.@JsniDispatchTest.JsoImplementor::m()();
+ }-*/;
+
+ public native JsoImplementor newImplementor(int n) /*-{
+ return {a:n};
+ }-*/;
+
+ public void testDevirtualization() {
+ assertEquals(2, doubleToInt(new Double(2.2)));
+ assertEquals(2, numberToInt(new Double(2.2)));
+ assertEquals(1, callMOnDual(new JavaImplementor()));
+ assertEquals(2, callMOnDual(newImplementor(2)));
+ assertEquals(3, callMOnJso(newImplementor(3)));
+ }
}