/* * 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; } }