Reitveld: http://gwt-code-reviews.appspot.com/89810
Changes normal GWT vtable setup from
function foo() { ... }
_.foo = foo;
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7587 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index bcdf051..c7d07f5 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -92,6 +92,7 @@
import com.google.gwt.dev.jjs.impl.CodeSplitter.MultipleDependencyGraphRecorder;
import com.google.gwt.dev.js.EvalFunctionsAtTopScope;
import com.google.gwt.dev.js.JsBreakUpLargeVarStatements;
+import com.google.gwt.dev.js.JsDuplicateFunctionRemover;
import com.google.gwt.dev.js.JsIEBlockSizeVisitor;
import com.google.gwt.dev.js.JsInliner;
import com.google.gwt.dev.js.JsNormalizer;
@@ -106,6 +107,7 @@
import com.google.gwt.dev.js.JsUnusedFunctionRemover;
import com.google.gwt.dev.js.JsVerboseNamer;
import com.google.gwt.dev.js.SizeBreakdown;
+import com.google.gwt.dev.js.ast.JsBlock;
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.util.AbstractTextOutput;
@@ -261,7 +263,7 @@
// (7) Generate a JavaScript code DOM from the Java type declarations
jprogram.typeOracle.recomputeAfterOptimizations();
JavaToJavaScriptMap map = GenerateJavaScriptAST.exec(jprogram, jsProgram,
- options.getOutput(), symbolTable);
+ options.getOutput(), symbolTable, propertyOracles);
// (8) Normalize the JS AST.
// Fix invalid constructs created during JS AST gen.
@@ -269,7 +271,7 @@
// Resolve all unresolved JsNameRefs.
JsSymbolResolver.exec(jsProgram);
// Move all function definitions to a top-level scope, to reduce weirdness
- EvalFunctionsAtTopScope.exec(jsProgram);
+ EvalFunctionsAtTopScope.exec(jsProgram, map);
// (9) Optimize the JS AST.
if (options.isAggressivelyOptimize()) {
@@ -315,6 +317,18 @@
case OBFUSCATED:
obfuscateMap = JsStringInterner.exec(jprogram, jsProgram);
JsObfuscateNamer.exec(jsProgram);
+ if (JsStackEmulator.getStackMode(propertyOracles) ==
+ JsStackEmulator.StackMode.STRIP) {
+ boolean changed = false;
+ for (int i = 0; i < jsProgram.getFragmentCount(); i++) {
+ JsBlock fragment = jsProgram.getFragmentBlock(i);
+ changed = JsDuplicateFunctionRemover.exec(jsProgram, fragment)
+ || changed;
+ }
+ if (changed) {
+ JsUnusedFunctionRemover.exec(jsProgram);
+ }
+ }
break;
case PRETTY:
// We don't intern strings in pretty mode to improve readability
@@ -781,7 +795,7 @@
// Also remember $entry, which we'll handle specially in GenerateJsAst
JMethod registerEntry = program.getIndexedMethod("Impl.registerEntry");
program.addEntryMethod(registerEntry);
-
+
for (String mainClassName : mainClassNames) {
block.addStmt(makeStatsCalls(program, mainClassName));
JDeclaredType mainType = program.getFromTypeMap(mainClassName);
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 7bc960f..fcbfe3c 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
@@ -15,6 +15,7 @@
*/
package com.google.gwt.dev.jjs.impl;
+import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.linker.impl.StandardSymbolData;
import com.google.gwt.dev.jjs.HasSourceInfo;
import com.google.gwt.dev.jjs.InternalCompilerException;
@@ -87,6 +88,7 @@
import com.google.gwt.dev.jjs.ast.js.JsonArray;
import com.google.gwt.dev.jjs.ast.js.JsonObject;
import com.google.gwt.dev.jjs.ast.js.JsonObject.JsonPropInit;
+import com.google.gwt.dev.js.JsStackEmulator;
import com.google.gwt.dev.js.ast.JsArrayAccess;
import com.google.gwt.dev.js.ast.JsArrayLiteral;
import com.google.gwt.dev.js.ast.JsBinaryOperation;
@@ -133,6 +135,7 @@
import com.google.gwt.dev.js.ast.JsVars;
import com.google.gwt.dev.js.ast.JsWhile;
import com.google.gwt.dev.js.ast.JsVars.JsVar;
+import com.google.gwt.dev.util.collect.IdentityHashSet;
import com.google.gwt.dev.util.collect.Maps;
import java.util.ArrayList;
@@ -337,14 +340,21 @@
}
// my global name
- JsName globalName;
+ JsName globalName = null;
assert x.getEnclosingType() != null;
String mangleName = mangleNameForGlobal(x);
- globalName = topScope.declareName(mangleName, name);
- x.getSourceInfo().addCorrelation(program.getCorrelator().by(globalName));
- names.put(x, globalName);
- recordSymbol(x, globalName);
+ /*
+ * Only allocate a name for a function if it is native, not polymorphic,
+ * or stack-stripping is disabled.
+ */
+ if (!stripStack || !polymorphicNames.containsKey(x) || x.isNative()) {
+ globalName = topScope.declareName(mangleName, name);
+ x.getSourceInfo().addCorrelation(
+ program.getCorrelator().by(globalName));
+ names.put(x, globalName);
+ recordSymbol(x, globalName);
+ }
JsFunction jsFunction;
if (x.isNative()) {
// set the global name of the JSNI peer
@@ -355,7 +365,19 @@
// create a new peer JsFunction
SourceInfo sourceInfo = x.getSourceInfo().makeChild(
CreateNamesAndScopesVisitor.class, "Translated JS function");
+ /*
+ * It would be more correct here to check for an inline assignment,
+ * such as var foo = function blah() {} and introduce a separate scope
+ * for the function's name according to EcmaScript-262, but this would
+ * mess up stack traces by allowing two inner scope function names to
+ * onfuscate to the same identifier, making function names no longer
+ * a 1:1 mapping to obfuscated symbols. Leaving them in global scope
+ * causes no harm.
+ */
jsFunction = new JsFunction(sourceInfo, topScope, globalName, true);
+ if (polymorphicNames.containsKey(x)) {
+ polymorphicJsFunctions.add(jsFunction);
+ }
}
methodBodyMap.put(x.getBody(), jsFunction);
jsFunction.getSourceInfo().addCorrelation(
@@ -632,7 +654,8 @@
// declare all methods into the global scope
for (int i = 0; i < jsFuncs.size(); ++i) {
JsFunction func = jsFuncs.get(i);
- if (func != null) {
+ // don't add polymorphic JsFuncs, inline decl into vtable assignment
+ if (func != null && !polymorphicJsFunctions.contains(func)) {
globalStmts.add(func.makeStmt());
}
}
@@ -1558,14 +1581,14 @@
// Add it first, so that script-tag chunking in IFrameLinker works
globalStatements.add(0, nullFunc.makeStmt());
}
-
+
private void generateSeedFuncAndPrototype(JClassType x,
List<JsStatement> globalStmts) {
SourceInfo sourceInfo = x.getSourceInfo().makeChild(
GenerateJavaScriptVisitor.class, "Seed and function prototype");
if (x != program.getTypeJavaLangString()) {
JsName seedFuncName = names.get(x);
-
+
// seed function
// function com_example_foo_Foo() { }
JsFunction seedFunc = new JsFunction(sourceInfo, topScope,
@@ -1705,7 +1728,11 @@
if (!method.isStatic() && !method.isAbstract()) {
JsNameRef lhs = polymorphicNames.get(method).makeRef(sourceInfo);
lhs.setQualifier(globalTemp.makeRef(sourceInfo));
- JsNameRef rhs = names.get(method).makeRef(sourceInfo);
+ /*
+ * Inline JsFunction rather than reference, e.g.
+ * _.vtableName = function functionName() { ... }
+ */
+ JsExpression rhs = methodBodyMap.get(method.getBody());
JsExpression asg = createAssignment(lhs, rhs);
JsExprStmt asgStat = new JsExprStmt(x.getSourceInfo(), asg);
globalStmts.add(asgStat);
@@ -1889,9 +1916,10 @@
}
public static JavaToJavaScriptMap exec(JProgram program, JsProgram jsProgram,
- JsOutputOption output, Map<StandardSymbolData, JsName> symbolTable) {
+ JsOutputOption output, Map<StandardSymbolData, JsName> symbolTable,
+ PropertyOracle[] propertyOracles) {
GenerateJavaScriptAST generateJavaScriptAST = new GenerateJavaScriptAST(
- program, jsProgram, output, symbolTable);
+ program, jsProgram, output, symbolTable, propertyOracles);
return generateJavaScriptAST.execImpl();
}
@@ -1932,9 +1960,9 @@
private final JsScope objectScope;
private final JsOutputOption output;
private final Map<JMethod, JsName> polymorphicNames = new IdentityHashMap<JMethod, JsName>();
-
+ private final Set<JsFunction> polymorphicJsFunctions = new IdentityHashSet<JsFunction>();
private final JProgram program;
-
+
/**
* All of the fields and polymorphic methods in String.
*
@@ -1952,6 +1980,12 @@
private final Set<JDeclaredType> specialObfuscatedTypes = new HashSet<JDeclaredType>();
/**
+ * If true, polymorphic functions are made anonymous vtable declarations and
+ * not assigned topScope identifiers.
+ */
+ private boolean stripStack;
+
+ /**
* Maps JsNames to machine-usable identifiers.
*/
private final Map<StandardSymbolData, JsName> symbolTable;
@@ -1968,7 +2002,8 @@
private final Map<JsStatement, JMethod> vtableInitForMethodMap = new HashMap<JsStatement, JMethod>();
private GenerateJavaScriptAST(JProgram program, JsProgram jsProgram,
- JsOutputOption output, Map<StandardSymbolData, JsName> symbolTable) {
+ JsOutputOption output, Map<StandardSymbolData, JsName> symbolTable,
+ PropertyOracle[] propertyOracles) {
this.program = program;
typeOracle = program.typeOracle;
this.jsProgram = jsProgram;
@@ -1978,6 +2013,8 @@
this.output = output;
this.symbolTable = symbolTable;
+ this.stripStack = JsStackEmulator.getStackMode(propertyOracles) ==
+ JsStackEmulator.StackMode.STRIP;
/*
* Because we modify String's prototype, all fields and polymorphic methods
* on String's super types need special handling.
diff --git a/dev/core/src/com/google/gwt/dev/js/EvalFunctionsAtTopScope.java b/dev/core/src/com/google/gwt/dev/js/EvalFunctionsAtTopScope.java
index b22a69c..7352d05 100644
--- a/dev/core/src/com/google/gwt/dev/js/EvalFunctionsAtTopScope.java
+++ b/dev/core/src/com/google/gwt/dev/js/EvalFunctionsAtTopScope.java
@@ -15,6 +15,7 @@
*/
package com.google.gwt.dev.js;
+import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
import com.google.gwt.dev.js.ast.JsBlock;
import com.google.gwt.dev.js.ast.JsContext;
import com.google.gwt.dev.js.ast.JsExpression;
@@ -23,6 +24,7 @@
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsProgramFragment;
import com.google.gwt.dev.js.ast.JsStatement;
+import com.google.gwt.dev.js.ast.JsExprStmt;
import java.util.HashSet;
import java.util.ListIterator;
@@ -38,17 +40,33 @@
*/
public class EvalFunctionsAtTopScope extends JsModVisitor {
- public static void exec(JsProgram jsProgram) {
- EvalFunctionsAtTopScope fev = new EvalFunctionsAtTopScope();
+
+ public static void exec(JsProgram jsProgram, JavaToJavaScriptMap map) {
+ EvalFunctionsAtTopScope fev = new EvalFunctionsAtTopScope(map);
fev.accept(jsProgram);
}
+ private JsStatement currentStatement;
+
private final Set<JsFunction> dontMove = new HashSet<JsFunction>();
private final Stack<ListIterator<JsStatement>> itrStack = new Stack<ListIterator<JsStatement>>();
+ private JavaToJavaScriptMap java2jsMap;
+
private final Stack<JsBlock> scopeStack = new Stack<JsBlock>();
-
+
+
+
+ public EvalFunctionsAtTopScope(JavaToJavaScriptMap java2jsMap) {
+ this.java2jsMap = java2jsMap;
+ }
+
+ @Override
+ public void endVisit(JsExprStmt x, JsContext<JsStatement> ctx) {
+ currentStatement = null;
+ }
+
@Override
public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
scopeStack.pop();
@@ -91,11 +109,22 @@
}
@Override
+ public boolean visit(JsExprStmt x, JsContext<JsStatement> ctx) {
+ currentStatement = x;
+ return true;
+ }
+
+ @Override
public boolean visit(JsFunction x, JsContext<JsExpression> ctx) {
+ JsFunction func = JsStaticEval.isFunctionDecl(currentStatement);
+
/*
* We do this during visit() to preserve first-to-last evaluation order.
+ * We check if this function is a vtable declaration and don't
+ * move functions used in other expressions or are in vtable assignments.
*/
- if (x.getName() != null && !dontMove.contains(x)) {
+ if (x.getName() != null && !dontMove.contains(x) &&
+ !isVtableDeclaration(currentStatement)) {
/*
* Reinsert this function into the statement immediately before the
* current statement. The current statement will have already been
@@ -116,6 +145,7 @@
return true;
}
+
@Override
public boolean visit(JsProgram x, JsContext<JsProgram> ctx) {
scopeStack.push(x.getGlobalBlock());
@@ -127,4 +157,8 @@
scopeStack.push(x.getGlobalBlock());
return true;
}
+
+ private boolean isVtableDeclaration(JsStatement currentStatement) {
+ return java2jsMap.vtableInitToMethod(currentStatement) != null;
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsDuplicateFunctionRemover.java b/dev/core/src/com/google/gwt/dev/js/JsDuplicateFunctionRemover.java
new file mode 100644
index 0000000..5e92127
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/JsDuplicateFunctionRemover.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2009 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.dev.js.ast.JsBlock;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsExpression;
+import com.google.gwt.dev.js.ast.JsFunction;
+import com.google.gwt.dev.js.ast.JsModVisitor;
+import com.google.gwt.dev.js.ast.JsName;
+import com.google.gwt.dev.js.ast.JsNameRef;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsVisitor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Replace references to functions which have post-obfuscation duplicate bodies
+ * by reference to a canonical one. Intended to run only when stack trace
+ * stripping is enabled.
+ */
+public class JsDuplicateFunctionRemover {
+
+ private class DuplicateFunctionBodyRecorder extends JsVisitor {
+
+ Map<String, JsName> uniqueBodies = new HashMap<String, JsName>();
+ Map<JsName, JsName> duplicateOriginalMap = new HashMap<JsName, JsName>();
+
+ public Map<JsName, JsName> getDuplicateMap() {
+ return duplicateOriginalMap;
+ }
+
+ @Override
+ public boolean visit(JsFunction x, JsContext<JsExpression> ctx) {
+ /*
+ * At this point, unpruned zero-arg functions with empty
+ * bodies are Js constructor seed functions.
+ * If constructors are ever inlined into seed functions, revisit this.
+ * Don't process anonymous functions.
+ */
+ if (x.getName() != null && x.getParameters().size() > 0 ||
+ x.getBody().getStatements().size() > 0) {
+ String fnSource = x.toSource();
+ String body = fnSource.substring(fnSource.indexOf("("));
+ JsName original = uniqueBodies.get(body);
+ if (original != null) {
+ duplicateOriginalMap.put(x.getName(), original);
+ } else {
+ uniqueBodies.put(body, x.getName());
+ }
+ }
+ return false;
+ }
+ }
+
+ private class ReplaceDuplicateInvocationNameRefs extends JsModVisitor {
+ private Map<JsName, JsName> duplicateMap;
+
+ public ReplaceDuplicateInvocationNameRefs(
+ Map<JsName, JsName> duplicateMap) {
+ this.duplicateMap = duplicateMap;
+ }
+
+ @Override
+ public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
+ JsName orig = duplicateMap.get(x.getName());
+ if (orig != null && x.getName().getEnclosing() == program.getScope()) {
+ ctx.replaceMe(orig.makeRef(x.getSourceInfo()));
+ }
+ }
+ }
+
+ public static boolean exec(JsProgram program, JsBlock fragment) {
+ return new JsDuplicateFunctionRemover(program).execImpl(fragment);
+ }
+
+ private JsProgram program;
+
+ public JsDuplicateFunctionRemover(JsProgram program) {
+ this.program = program;
+ }
+
+ private boolean execImpl(JsBlock fragment) {
+ DuplicateFunctionBodyRecorder dfbr = new DuplicateFunctionBodyRecorder();
+ dfbr.accept(fragment);
+ ReplaceDuplicateInvocationNameRefs rdup
+ = new ReplaceDuplicateInvocationNameRefs(dfbr.getDuplicateMap());
+ rdup.accept(fragment);
+ return rdup.didChange();
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsStackEmulator.java b/dev/core/src/com/google/gwt/dev/js/JsStackEmulator.java
index ab22d33..e47f597 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsStackEmulator.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsStackEmulator.java
@@ -70,6 +70,8 @@
*/
public class JsStackEmulator {
+ private static final String PROPERTY_NAME = "compiler.stackMode";
+
/**
* Resets the global stack depth to the local stack index and top stack frame
* after calls to Exceptions.caught. This is created by
@@ -799,9 +801,21 @@
}
}
- private static final String PROPERTY_NAME = "compiler.emulatedStack";
-
+ /**
+ * Corresponds to property compiler.stackMode in EmulateJsStack.gwt.xml
+ * module.
+ */
+ public enum StackMode {
+ STRIP, NATIVE, EMULATED;
+ }
+
public static void exec(JsProgram program, PropertyOracle[] propertyOracles) {
+ if (getStackMode(propertyOracles) == StackMode.EMULATED) {
+ (new JsStackEmulator(program, propertyOracles)).execImpl();
+ }
+ }
+
+ public static StackMode getStackMode(PropertyOracle[] propertyOracles) {
SelectionProperty property;
try {
property = propertyOracles[0].getSelectionProperty(TreeLogger.NULL,
@@ -814,9 +828,21 @@
String value = property.getCurrentValue();
assert value != null : property.getName() + " did not have a value";
- if (Boolean.valueOf(value)) {
- (new JsStackEmulator(program, propertyOracles)).execImpl();
+ StackMode stackMode = StackMode.valueOf(value.toUpperCase());
+ // Check for multiply defined properties
+ if (propertyOracles.length > 1) {
+ for (int i = 1; i < propertyOracles.length; ++i) {
+ try {
+ property = propertyOracles[i].getSelectionProperty(TreeLogger.NULL,
+ PROPERTY_NAME);
+ } catch (BadPropertyValueException e) {
+ // OK!
+ }
+ assert value.equals(property.getCurrentValue()) :
+ "compiler.stackMode property has multiple values.";
+ }
}
+ return stackMode;
}
private JsFunction caughtFunction;
diff --git a/user/src/com/google/gwt/core/CoreWithUserAgent.gwt.xml b/user/src/com/google/gwt/core/CoreWithUserAgent.gwt.xml
index 723063a..e97591c 100644
--- a/user/src/com/google/gwt/core/CoreWithUserAgent.gwt.xml
+++ b/user/src/com/google/gwt/core/CoreWithUserAgent.gwt.xml
@@ -42,4 +42,10 @@
<when-property-is name="user.agent" value="opera" />
</any>
</replace-with>
+
+ <replace-with
+ class="com.google.gwt.core.client.impl.StackTraceCreator.CollectorNull">
+ <when-type-is class="com.google.gwt.core.client.impl.StackTraceCreator.Collector" />
+ <when-property-is name="compiler.stackMode" value="strip" />
+ </replace-with>
</module>
diff --git a/user/src/com/google/gwt/core/EmulateJsStack.gwt.xml b/user/src/com/google/gwt/core/EmulateJsStack.gwt.xml
index 849cc54..5ae5b0e 100644
--- a/user/src/com/google/gwt/core/EmulateJsStack.gwt.xml
+++ b/user/src/com/google/gwt/core/EmulateJsStack.gwt.xml
@@ -34,4 +34,11 @@
<when-type-is class="com.google.gwt.core.client.impl.StackTraceCreator.Collector" />
<when-property-is name="compiler.emulatedStack" value="true" />
</replace-with>
+
+ <!-- if set to 'strip', stack information can be removed or corrupted -->
+ <define-property name="compiler.stackMode" values="strip,native,emulated" />
+ <set-property name="compiler.stackMode" value="native" />
+ <set-property name="compiler.stackMode" value="emulated" >
+ <when-property-is name="compiler.emulatedStack" value="true" />
+ </set-property>
</module>
diff --git a/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java b/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java
index b7e70cf..6555d8c 100644
--- a/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java
+++ b/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java
@@ -341,6 +341,26 @@
}
/**
+ * When compiler.stackMode = strip, we stub out the collector.
+ */
+ static class CollectorNull extends Collector {
+ @Override
+ public JsArrayString collect() {
+ return JsArrayString.createArray().cast();
+ }
+
+ @Override
+ public void createStackTrace(JavaScriptException e) {
+ e.setStackTrace(new StackTraceElement[0]);
+ }
+
+ @Override
+ public void fillInStackTrace(Throwable t) {
+ t.setStackTrace(new StackTraceElement[0]);
+ }
+ }
+
+ /**
* Create a stack trace based on a JavaScriptException. This method should
* only be called in web mode.
*/