blob: 88bd4f5a568da45159bc032d8833a0f463627278 [file] [log] [blame]
/*
* Copyright 2011 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.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.thirdparty.guava.common.base.Joiner;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
/**
* Tests for the {@link Devirtualizer} visitor.
*/
public class DevirtualizerTest extends OptimizerTestBase {
/**
* Devirtualizer should allow dual Java/JSO implementations of the same
* interface, so long as there is only one of each. If there are multiple
* methods with the same method name, it should distinguish between them.
*/
public void testDualJsoImpl() throws UnableToCompleteException {
addSnippetImport("com.google.gwt.lang.Cast");
addSnippetImport("com.google.gwt.core.client.JavaScriptObject");
// Defines a bunch of JSO interfaces and classes with functions a() and b().
addSnippetClassDecl(
"interface Iface1 { int a(); int b(); }",
"static class J1 implements Iface1 {",
" public int a() { return 1; }",
" public int b() { return 1; }",
"}",
"static class Jso1 extends JavaScriptObject implements Iface1 {",
" protected Jso1() { }",
" public final int a() { return 2; }",
" public final int b() { return 2; }",
" public static native Jso1 create() /*-{ return {} }-*/;",
"}",
"static interface Iface2 { int a(); int b(); }",
"static class J2 implements Iface2 {",
" public int a() { return 3; }",
" public int b() { return 3; }",
"}",
"static class Jso2 extends JavaScriptObject implements Iface2 {",
" protected Jso2() { }",
" public final int a() { return 4; }",
" public final int b() { return 4; }",
" public static native Jso2 create() /*-{ return {} }-*/;",
"}",
"static Iface1 val1 = new J1();",
"static Iface1 val2 = Jso1.create();",
"static Iface2 val3 = new J2();",
"static Iface2 val4 = Jso2.create();");
// Constructs a code snippet that calls a() but NOT b().
String code = "int result = val1.a() + val2.a() + val3.a() + val4.a();";
// Constructs an expectation about the resulting devirtualized method calls of a(). The salient
// point in the results below is that the JSO method used for val1 and val1 has a different name
// the method used for val2 and val3.
String expected = Joiner.on("").join(
"int result = ",
"EntryPoint$Jso1.a__I__devirtual$(EntryPoint.val1) + ",
"EntryPoint$Jso1.a__I__devirtual$(EntryPoint.val2) + ",
"EntryPoint$Jso2.a__I__devirtual$(EntryPoint.val3) + ",
"EntryPoint$Jso2.a__I__devirtual$(EntryPoint.val4);");
Result result = optimize("void", code);
// Asserts that a() method calls were redirected to the devirtualized version.
result.intoString(expected);
// Asserts that a() AND b() method definitions were both duplicated as devirtualized versions
// even though b() was never called.
result.classHasMethods("EntryPoint$Jso1", Lists.newArrayList(
"a()I", "b()I", "$a(Ltest/EntryPoint$Jso1;)I", "$b(Ltest/EntryPoint$Jso1;)I"));
}
public void testDevirtualizeString() throws UnableToCompleteException {
addSnippetImport("com.google.gwt.lang.Cast");
addSnippetImport("com.google.gwt.core.client.JavaScriptObject");
// Defines a JSO and a Java object that implements Comparable.
addSnippetClassDecl(
"static class J1 implements Comparable<J1> {",
" public int compareTo(J1 other) { return 1; }",
"}",
"static class Jso1 extends JavaScriptObject implements Comparable<Jso1> {",
" protected Jso1() { }",
" final public int compareTo(Jso1 other) { return 2; }",
" public static native Jso1 create() /*-{ return {} }-*/;",
"}",
"static Comparable javaVal = new J1();",
"static Comparable jsoVal = Jso1.create();",
"static Comparable stringVal = \"string\";",
"static String aString = \"string\";",
"static CharSequence stringCharSeq = \"string\";");
// Constructs a code snippet that calls a() but NOT b().
String code = Joiner.on("").join(
"int result = javaVal.compareTo(javaVal) + jsoVal.compareTo(jsoVal) +",
" stringVal.compareTo(stringVal) + stringCharSeq.length() + aString.length();");
// Constructs an expectation about the resulting devirtualized method calls for
// Comparable.compareTo() and CharSequence.length(). Note that calls to CharSequence.length and
// String.length are devirtualized separately.
String expected = String.format(Joiner.on("").join(
"int result = ",
// Methods in Comparable and CharSequence end up in String even if used by a JSO.
"String.compareTo_%s__I__devirtual$(EntryPoint.javaVal, EntryPoint.javaVal) + ",
"String.compareTo_%s__I__devirtual$(EntryPoint.jsoVal, EntryPoint.jsoVal) + ",
"String.compareTo_%s__I__devirtual$(EntryPoint.stringVal, EntryPoint.stringVal) + ",
"String.length__I__devirtual$(EntryPoint.stringCharSeq) + ",
"String.length__I__devirtual$(EntryPoint.aString);"), "Ljava_lang_Object",
"Ljava_lang_Object", "Ljava_lang_Object");
Result result = optimize("void", code.toString());
result.intoString(expected.toString());
}
public void testDevirtualizeJsOverlay() throws UnableToCompleteException {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsOverlay");
addSnippetClassDecl(
"@JsType(isNative=true) public static class NativeClass {",
" @JsOverlay public final void m() { };",
"}");
String code = Joiner.on('\n').join(
"NativeClass object = null;",
"object.m();");
String expected = Joiner.on('\n').join(
"EntryPoint$NativeClass object = null;",
"EntryPoint$NativeClass.$m(object);");
Result result = optimize("void", code);
result.intoString(expected);
}
public void testDevirtualizeObjectMethods() throws UnableToCompleteException {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsOverlay");
addSnippetClassDecl(
"@JsType(isNative=true) public static class NativeClass {",
"}",
"public static class NativeClassSubclass extends NativeClass {",
"}");
String code = Joiner.on('\n').join(
"NativeClass nativeClass = null;",
"nativeClass.toString();",
"nativeClass.equals(nativeClass);",
"nativeClass.hashCode();",
"NativeClassSubclass subclass = null;",
"subclass.toString();",
"subclass.equals(subclass);",
"subclass.hashCode();"
);
String expected = Joiner.on('\n').join(
"EntryPoint$NativeClass nativeClass = null;",
"Runtime.toString(nativeClass);",
"Object.equals_Ljava_lang_Object__Z__devirtual$(nativeClass, nativeClass);",
"Object.hashCode__I__devirtual$(nativeClass);",
"EntryPoint$NativeClassSubclass subclass = null;",
"Runtime.toString(subclass);",
"Object.equals_Ljava_lang_Object__Z__devirtual$(subclass, subclass);",
"Object.hashCode__I__devirtual$(subclass);");
Result result = optimize("void", code);
result.intoString(expected);
}
public void testDevirtualizeObjectMethodsExplicitelyDefined() throws UnableToCompleteException {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsOverlay");
addSnippetClassDecl(
"@JsType(isNative=true) public static class NativeClass {",
" public native String toString();",
" public native int hashCode();",
" public native boolean equals(Object o);",
"}",
"public static class NativeClassSubclass extends NativeClass {",
"}");
String code = Joiner.on('\n').join(
"NativeClass nativeClass = null;",
"nativeClass.toString();",
"nativeClass.equals(nativeClass);",
"nativeClass.hashCode();",
"NativeClassSubclass subclass = null;",
"subclass.toString();",
"subclass.equals(subclass);",
"subclass.hashCode();"
);
String expected = Joiner.on('\n').join(
"EntryPoint$NativeClass nativeClass = null;",
"Runtime.toString(nativeClass);",
"Object.equals_Ljava_lang_Object__Z__devirtual$(nativeClass, nativeClass);",
"Object.hashCode__I__devirtual$(nativeClass);",
"EntryPoint$NativeClassSubclass subclass = null;",
"Runtime.toString(subclass);",
"Object.equals_Ljava_lang_Object__Z__devirtual$(subclass, subclass);",
"Object.hashCode__I__devirtual$(subclass);");
Result result = optimize("void", code);
result.intoString(expected);
}
@Override
protected boolean doOptimizeMethod(TreeLogger logger, JProgram program, JMethod method) {
ReplaceCallsToNativeJavaLangObjectOverrides.exec(program);
Devirtualizer.exec(program);
return true;
}
}