| /* |
| * 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.js; |
| |
| import com.google.gwt.core.ext.UnableToCompleteException; |
| import com.google.gwt.core.ext.linker.SymbolData; |
| import com.google.gwt.core.ext.linker.impl.StandardSymbolData; |
| import com.google.gwt.dev.CompilerContext; |
| import com.google.gwt.dev.MinimalRebuildCache; |
| import com.google.gwt.dev.PrecompileTaskOptions; |
| import com.google.gwt.dev.PrecompileTaskOptionsImpl; |
| import com.google.gwt.dev.cfg.BindingProperties; |
| import com.google.gwt.dev.cfg.BindingProperty; |
| import com.google.gwt.dev.cfg.ConditionNone; |
| import com.google.gwt.dev.cfg.ConfigurationProperties; |
| import com.google.gwt.dev.cfg.ConfigurationProperty; |
| import com.google.gwt.dev.cfg.PermutationProperties; |
| import com.google.gwt.dev.javac.CompilationState; |
| import com.google.gwt.dev.javac.CompilationStateBuilder; |
| import com.google.gwt.dev.javac.testing.impl.MockJavaResource; |
| import com.google.gwt.dev.javac.testing.impl.MockResourceOracle; |
| import com.google.gwt.dev.jjs.AstConstructor; |
| import com.google.gwt.dev.jjs.JavaAstConstructor; |
| import com.google.gwt.dev.jjs.JsOutputOption; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.impl.ArrayNormalizer; |
| import com.google.gwt.dev.jjs.impl.CatchBlockNormalizer; |
| import com.google.gwt.dev.jjs.impl.ComputeCastabilityInformation; |
| import com.google.gwt.dev.jjs.impl.FullCompileTestBase; |
| import com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST; |
| import com.google.gwt.dev.jjs.impl.ImplementCastsAndTypeChecks; |
| import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap; |
| import com.google.gwt.dev.jjs.impl.MethodInliner; |
| import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences; |
| import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.StringTypeMapper; |
| import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.TypeOrder; |
| import com.google.gwt.dev.js.ast.JsFunction; |
| import com.google.gwt.dev.js.ast.JsName; |
| import com.google.gwt.dev.js.ast.JsProgram; |
| import com.google.gwt.dev.js.ast.JsVisitable; |
| import com.google.gwt.dev.js.ast.JsVisitor; |
| import com.google.gwt.dev.util.DefaultTextOutput; |
| import com.google.gwt.dev.util.TextOutput; |
| import com.google.gwt.thirdparty.guava.common.base.Joiner; |
| |
| import java.util.Arrays; |
| import java.util.Map; |
| import java.util.TreeMap; |
| |
| /** |
| * Tests that {@link JsStackEmulator} generates the expected JavaScript code. |
| */ |
| public class JsStackEmulatorTest extends FullCompileTestBase { |
| |
| private final ConfigurationProperty recordFileNamesProp = |
| new ConfigurationProperty("compiler.emulatedStack.recordFileNames", false); |
| |
| private final ConfigurationProperty recordLineNumbersProp = |
| new ConfigurationProperty("compiler.emulatedStack.recordLineNumbers", false); |
| |
| private boolean inline = false; |
| |
| public void testEmptyMethod() throws Exception { |
| recordFileNamesProp.setValue("true"); |
| recordLineNumbersProp.setValue("true"); |
| |
| JsProgram program = compileClass( |
| "package test;", |
| "public class EntryPoint {", |
| " public static void onModuleLoad() {", |
| " }", |
| "}"); |
| |
| checkOnModuleLoad(program, "function onModuleLoad(){" + |
| "var stackIndex;$stack[stackIndex=++$stackDepth]=onModuleLoad;" + |
| "$location[stackIndex]='EntryPoint.java:'+'3',$clinit_EntryPoint();" + |
| "$stackDepth=stackIndex-1}"); |
| } |
| |
| public void testCallWithNoArguments() throws Exception { |
| recordFileNamesProp.setValue("true"); |
| recordLineNumbersProp.setValue("true"); |
| |
| JsProgram program = compileClass( |
| "package test;", |
| "public class EntryPoint {", |
| " static void foo() {}", |
| " public static void onModuleLoad() {", |
| " foo();", |
| " }", |
| "}"); |
| |
| checkOnModuleLoad(program, "function onModuleLoad(){" + |
| "var stackIndex;$stack[stackIndex=++$stackDepth]=onModuleLoad;" + |
| "$location[stackIndex]='EntryPoint.java:'+'4',$clinit_EntryPoint();" + |
| "$location[stackIndex]='EntryPoint.java:'+'5',foo();" + |
| "$stackDepth=stackIndex-1}"); |
| } |
| |
| public void testCallWithArguments() throws Exception { |
| recordFileNamesProp.setValue("true"); |
| recordLineNumbersProp.setValue("true"); |
| |
| JsProgram program = compileClass( |
| "package test;", |
| "public class EntryPoint {", |
| " static void foo(int x) {}", |
| " public static void onModuleLoad() {", |
| " foo(123);", |
| " }", |
| "}"); |
| |
| checkOnModuleLoad(program, "function onModuleLoad(){" + |
| "var stackIndex;$stack[stackIndex=++$stackDepth]=onModuleLoad;" + |
| "$location[stackIndex]='EntryPoint.java:'+'4',$clinit_EntryPoint();" + |
| "foo(($tmp=123,$location[stackIndex]='EntryPoint.java:'+'5',$tmp));" + |
| "$stackDepth=stackIndex-1}"); |
| } |
| |
| public void testSimpleThrow() throws Exception { |
| recordFileNamesProp.setValue("true"); |
| recordLineNumbersProp.setValue("true"); |
| |
| JsProgram program = compileClass( |
| "package test;", |
| "public class EntryPoint {", |
| " public static void onModuleLoad() {", |
| " throw new RuntimeException();", |
| " }", |
| "}"); |
| |
| // Note: it's up to the catch block to fix $stackDepth. |
| checkOnModuleLoad(program, "function onModuleLoad(){" + |
| "var stackIndex;$stack[stackIndex=++$stackDepth]=onModuleLoad;" + |
| "$location[stackIndex]='EntryPoint.java:'+'3',$clinit_EntryPoint();" + |
| "throw toJs(($location[stackIndex]='EntryPoint.java:'+'4',new RuntimeException))" + |
| "}"); |
| } |
| |
| public void testThrowWithInlineMethodCall() throws Exception { |
| recordFileNamesProp.setValue("true"); |
| recordLineNumbersProp.setValue("true"); |
| inline = true; |
| |
| JsProgram program = compileClass( |
| "package test;", |
| "public class EntryPoint {", |
| " static Object thing = \"hello\";", |
| " private static String message() { return thing", // line 4 |
| " .toString(); }", |
| " public static void onModuleLoad() {", // line 6 |
| " throw new RuntimeException(message());", // line 7 |
| " }", |
| "}"); |
| |
| // Line 7 should be current when the RuntimeException constructor is called. |
| checkOnModuleLoad(program, "function onModuleLoad(){" + |
| "var stackIndex;$stack[stackIndex=++$stackDepth]=onModuleLoad;" + |
| "$location[stackIndex]='EntryPoint.java:'+'6',$clinit_EntryPoint();" + |
| "throw toJs(new RuntimeException(" + |
| "($tmp=($location[stackIndex]='EntryPoint.java:'+'4',thing).toString()," + |
| "$location[stackIndex]='EntryPoint.java:'+'7',$tmp)))" + |
| "}"); |
| } |
| |
| public void testThrowWithChainedMethodCall() throws Exception { |
| recordFileNamesProp.setValue("true"); |
| recordLineNumbersProp.setValue("true"); |
| inline = true; |
| |
| JsProgram program = compileClass( |
| "package test;", |
| "public class EntryPoint {", |
| " static Factory factory;", |
| " static Factory getFactory() {", |
| " return factory;", // line 5 |
| " }", |
| " public static void onModuleLoad() {", // line 7 |
| " throw getFactory().makeException();", // line 8 |
| " }", |
| " static class Factory {", |
| " RuntimeException makeException() {", |
| " return new RuntimeException();", |
| " }", |
| " }", |
| "}"); |
| |
| checkOnModuleLoad(program, "function onModuleLoad(){" + |
| "var stackIndex;$stack[stackIndex=++$stackDepth]=onModuleLoad;" + |
| "$location[stackIndex]='EntryPoint.java:'+'7',$clinit_EntryPoint();" + |
| "throw toJs(($tmp=($location[stackIndex]='EntryPoint.java:'+'5',factory)," + |
| "$location[stackIndex]='EntryPoint.java:'+'8',$tmp).makeException())" + |
| "}"); |
| } |
| |
| public void testTryCatch() throws Exception { |
| recordFileNamesProp.setValue("true"); |
| recordLineNumbersProp.setValue("true"); |
| |
| JsProgram program = compileClass( |
| "package test;", |
| "public class EntryPoint {", |
| " public static void onModuleLoad() {", |
| " try {", |
| " throw new RuntimeException();", |
| " } catch (RuntimeException e) {" , |
| " String s = e.getMessage();", |
| " }", |
| " }", |
| "}"); |
| |
| // Note: it's up to the catch block to fix $stackDepth. |
| checkOnModuleLoad(program, "function onModuleLoad(){" + |
| "var stackIndex;$stack[stackIndex=++$stackDepth]=onModuleLoad;" + |
| "$location[stackIndex]='EntryPoint.java:'+'3',$clinit_EntryPoint();var e,s;" + |
| "try{throw toJs(($location[stackIndex]='EntryPoint.java:'+'5',new RuntimeException))" + |
| "}catch($e0){$e0=toJava($e0);" + |
| "$stackDepth=($location[stackIndex]='EntryPoint.java:'+'6',stackIndex);" + |
| "if(instanceOf($e0,'java.lang.RuntimeException')){" + |
| "e=$e0;s=($location[stackIndex]='EntryPoint.java:'+'7',e).getMessage()}" + |
| "else throw toJs(($location[stackIndex]='EntryPoint.java:'+'6',$e0))}" + |
| "$stackDepth=stackIndex-1" + |
| "}"); |
| } |
| |
| /** |
| * Given the source code to a Java class named <code>test.EntryPoint</code>, |
| * compiles it with emulated stack traces turned on and returns the JavaScript. |
| */ |
| private JsProgram compileClass(String... lines) throws UnableToCompleteException { |
| |
| // Gather the Java source code to compile. |
| |
| final String code = Joiner.on("\n").join(lines); |
| |
| MockResourceOracle sourceOracle = new MockResourceOracle(); |
| sourceOracle.addOrReplace(new MockJavaResource("test.EntryPoint") { |
| @Override |
| public CharSequence getContent() { |
| return code; |
| } |
| }); |
| sourceOracle.add(JavaAstConstructor.getCompilerTypes()); |
| |
| PrecompileTaskOptions options = new PrecompileTaskOptionsImpl(); |
| options.setOutput(JsOutputOption.PRETTY); |
| options.setRunAsyncEnabled(false); |
| CompilerContext context = new CompilerContext.Builder().options(options) |
| .minimalRebuildCache(new MinimalRebuildCache()).build(); |
| |
| ConfigurationProperties config = new ConfigurationProperties(Arrays.asList(recordFileNamesProp, |
| recordLineNumbersProp)); |
| |
| CompilationState state = |
| CompilationStateBuilder.buildFrom(logger, context, sourceOracle.getResources()); |
| JProgram jProgram = AstConstructor.construct(logger, state, options, config); |
| jProgram.addEntryMethod(findMethod(jProgram, "onModuleLoad")); |
| |
| if (inline) { |
| MethodInliner.exec(jProgram); |
| } |
| |
| CatchBlockNormalizer.exec(jProgram); |
| // Construct the JavaScript AST. |
| |
| // These passes are needed by GenerateJavaScriptAST. |
| ComputeCastabilityInformation.exec(jProgram, false); |
| ImplementCastsAndTypeChecks.exec(jProgram, false); |
| ArrayNormalizer.exec(jProgram); |
| |
| StringTypeMapper typeMapper = new StringTypeMapper(jProgram); |
| ResolveRuntimeTypeReferences.exec(jProgram, typeMapper, TypeOrder.FREQUENCY); |
| Map<StandardSymbolData, JsName> symbolTable = |
| new TreeMap<StandardSymbolData, JsName>(new SymbolData.ClassIdentComparator()); |
| |
| BindingProperty stackMode = new BindingProperty("compiler.stackMode"); |
| stackMode.addDefinedValue(new ConditionNone(), "EMULATED"); |
| |
| PermutationProperties properties = new PermutationProperties(Arrays.asList( |
| new BindingProperties(new BindingProperty[]{stackMode}, new String[]{"EMULATED"}, config) |
| )); |
| |
| JsProgram jsProgram = new JsProgram(); |
| JavaToJavaScriptMap jjsmap = GenerateJavaScriptAST.exec( |
| logger, jProgram, jsProgram, context, typeMapper, |
| symbolTable, properties).getLeft(); |
| |
| // Finally, run the pass we care about. |
| JsStackEmulator.exec(jProgram, jsProgram, properties, jjsmap); |
| |
| return jsProgram; |
| } |
| |
| /** |
| * Verifies the JavaScript function corresponding to <code>test.EntryPoint.onModuleLoad</code>. |
| */ |
| private static void checkOnModuleLoad(JsProgram program, String expectedJavascript) { |
| JsName onModuleLoad = program.getScope().findExistingName("test_EntryPoint_onModuleLoad__V"); |
| assertNotNull(onModuleLoad); |
| assert onModuleLoad.getStaticRef() instanceof JsFunction; |
| assertEquals(expectedJavascript, serializeJs(onModuleLoad.getStaticRef())); |
| } |
| |
| private static String serializeJs(JsVisitable node) { |
| TextOutput text = new DefaultTextOutput(true); |
| JsVisitor generator = new JsSourceGenerationVisitor(text); |
| generator.accept(node); |
| return text.toString(); |
| } |
| |
| @Override |
| protected void optimizeJava() { |
| } |
| } |