Merge web-mode stack trace support.
svn merge -c 4763,4764,4804 https://google-web-toolkit.googlecode.com/svn/changes/bobv/web_mode_stack_traces_r4761
Patch by: bobv
Review by: jgw, mmastrac
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@4807 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java b/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java
index 093f578..c9f3e3e 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java
@@ -34,12 +34,12 @@
* Returns the JavaScript compilation. The first element of the array contains
* the code that should be run when the application starts up. The remaining
* elements are loaded via
- * {@link com.google.gwt.core.client.GWT#runAsync(com.google.gwt.core.client.RunAsyncCallback) GWT.runAsync}.
- * The linker should provide a function named
- * <code>__gwtStartLoadingFragment</code> that can takes an integer as argument
- * and loads that specified code segment. To see how this function is used,
- * see
- * {@link com.google.gwt.core.client.AsyncFragmentLoader AsyncFragmentLoader}.
+ * {@link com.google.gwt.core.client.GWT#runAsync(com.google.gwt.core.client.RunAsyncCallback)
+ * GWT.runAsync}. The linker should provide a function named
+ * <code>__gwtStartLoadingFragment</code> that can takes an integer as
+ * argument and loads that specified code segment. To see how this function is
+ * used, see {@link com.google.gwt.core.client.AsyncFragmentLoader
+ * AsyncFragmentLoader}.
*/
public abstract String[] getJavaScript();
@@ -52,11 +52,19 @@
public abstract SortedSet<SortedMap<SelectionProperty, String>> getPropertyMap();
/**
- * Return a string that uniquely identifies this compilation result. Typically
+ * Return a string that uniquely identifies this compilation result. Typically
* this is a cryptographic hash of the compiled data.
*/
public abstract String getStrongName();
+ /**
+ * Returns a map of obfuscated symbol names in the compilation to JSNI-style
+ * identifiers. This data can allow for on-the-fly deobfuscation of stack
+ * trace information or to allow server components to have in-depth knowledge
+ * of the runtime structure of compiled objects.
+ */
+ public abstract SortedMap<String, String> getSymbolMap();
+
@Override
public final int hashCode() {
int hash = 17;
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
index c7e3a69..6485c5d 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
@@ -64,8 +64,7 @@
* Determines whether or not the URL is relative.
*
* @param src the test url
- * @return <code>true</code> if the URL is relative, <code>false</code> if
- * not
+ * @return <code>true</code> if the URL is relative, <code>false</code> if not
*/
@SuppressWarnings("unused")
protected static boolean isRelativeURL(String src) {
@@ -351,8 +350,8 @@
/**
* Get the partial path on which a CompilationResult has been emitted.
*
- * @return the partial path, or <code>null</code> if the CompilationResult
- * has not been emitted.
+ * @return the partial path, or <code>null</code> if the CompilationResult has
+ * not been emitted.
*/
protected String getCompilationStrongName(CompilationResult result) {
return compilationStrongNames.get(result);
@@ -372,8 +371,8 @@
* {@link #getModulePrefix(TreeLogger, LinkerContext, String)}.
*
* @param strongName strong name of the module being emitted
- * @param numFragments the number of fragments for this module, including
- * the main fragment (fragment 0)
+ * @param numFragments the number of fragments for this module, including the
+ * main fragment (fragment 0)
*/
protected String getModulePrefix(TreeLogger logger, LinkerContext context,
String strongName, int numFragments) throws UnableToCompleteException {
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
index bf4306b..62be9ce 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
@@ -70,9 +70,12 @@
*/
public static final Comparator<SortedMap<SelectionProperty, String>> MAP_COMPARATOR = new MapComparator();
- private final FileBackedObject<PermutationResult> resultFile;
private transient SoftReference<String[]> js;
+ private final FileBackedObject<PermutationResult> resultFile;
+
+ private transient SoftReference<SortedMap<String, String>> symbolMap;
+
private final SortedSet<SortedMap<SelectionProperty, String>> propertyValues = new TreeSet<SortedMap<SelectionProperty, String>>(
MAP_COMPARATOR);
private final String strongName;
@@ -104,16 +107,11 @@
}
if (toReturn == null) {
- PermutationResult result;
- try {
- result = resultFile.newInstance(TreeLogger.NULL);
- toReturn = result.getJs();
- js = new SoftReference<String[]>(toReturn);
- } catch (UnableToCompleteException e) {
- throw new RuntimeException(
- "Unexpectedly unable to read PermutationResult");
- }
+ PermutationResult result = loadPermutationResult();
+ toReturn = result.getJs();
+ js = new SoftReference<String[]>(toReturn);
}
+
return toReturn;
}
@@ -126,4 +124,29 @@
public String getStrongName() {
return strongName;
}
+
+ @Override
+ public SortedMap<String, String> getSymbolMap() {
+ SortedMap<String, String> toReturn = null;
+ if (symbolMap != null) {
+ toReturn = symbolMap.get();
+ }
+
+ if (toReturn == null) {
+ PermutationResult result = loadPermutationResult();
+ toReturn = result.getSymbolMap();
+ symbolMap = new SoftReference<SortedMap<String, String>>(toReturn);
+ }
+
+ return toReturn;
+ }
+
+ private PermutationResult loadPermutationResult() {
+ try {
+ return resultFile.newInstance(TreeLogger.NULL);
+ } catch (UnableToCompleteException e) {
+ throw new RuntimeException(
+ "Unexpectedly unable to read PermutationResult");
+ }
+ }
}
diff --git a/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java b/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
index f5787fc..a02d487 100644
--- a/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
@@ -132,7 +132,7 @@
/**
* This is the real implementation of <code>getModulePrefix</code> for this
- * linker. The other versions forward to this one.
+ * linker. The other versions forward to this one.
*/
private String getModulePrefix(LinkerContext context, String strongName,
boolean supportRunAsync) {
@@ -151,6 +151,8 @@
out.newlineOpt();
out.print("var $moduleName, $moduleBase;");
out.newlineOpt();
+ out.print("var $strongName = '" + strongName + "';");
+ out.newlineOpt();
if (supportRunAsync) {
out.print("function __gwtStartLoadingFragment(frag) {");
out.newlineOpt();
diff --git a/dev/core/src/com/google/gwt/core/linker/SingleScriptLinker.java b/dev/core/src/com/google/gwt/core/linker/SingleScriptLinker.java
index b31ff09..044e68b 100644
--- a/dev/core/src/com/google/gwt/core/linker/SingleScriptLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/SingleScriptLinker.java
@@ -103,6 +103,9 @@
}
CompilationResult result = results.iterator().next();
+ out.print("var $strongName = '" + result.getStrongName() + "';");
+ out.newlineOpt();
+
String[] js = result.getJavaScript();
if (js.length != 1) {
logger = logger.branch(TreeLogger.ERROR,
diff --git a/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java b/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java
new file mode 100644
index 0000000..a449bed
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java
@@ -0,0 +1,118 @@
+/*
+ * 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.core.linker;
+
+import com.google.gwt.core.ext.LinkerContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.AbstractLinker;
+import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.CompilationResult;
+import com.google.gwt.core.ext.linker.EmittedArtifact;
+import com.google.gwt.core.ext.linker.LinkerOrder;
+import com.google.gwt.core.ext.linker.SelectionProperty;
+import com.google.gwt.core.ext.linker.LinkerOrder.Order;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.SortedMap;
+
+/**
+ * This Linker exports the symbol maps associated with each compilation result
+ * as a private file. The names of the symbol maps files are computed by
+ * appending {@value #STRONG_NAME_SUFFIX} to the value returned by
+ * {@link CompilationResult#getStrongName()}.
+ */
+@LinkerOrder(Order.POST)
+public class SymbolMapsLinker extends AbstractLinker {
+
+ /**
+ * This value is appended to the strong name of the CompilationResult to form
+ * the symbol map's filename.
+ */
+ public static final String STRONG_NAME_SUFFIX = "_sybolMap.properties";
+
+ @Override
+ public String getDescription() {
+ return "Export CompilationResult symbol maps";
+ }
+
+ @Override
+ public ArtifactSet link(TreeLogger logger, LinkerContext context,
+ ArtifactSet artifacts) throws UnableToCompleteException {
+
+ artifacts = new ArtifactSet(artifacts);
+
+ for (CompilationResult result : artifacts.find(CompilationResult.class)) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PrintWriter pw = new PrintWriter(out);
+
+ doWriteSymbolMap(logger, result, pw);
+ pw.close();
+
+ doEmitSymbolMap(logger, artifacts, result, out);
+ }
+
+ return artifacts;
+ }
+
+ /**
+ * Override to change the manner in which the symbol map is emitted.
+ */
+ protected void doEmitSymbolMap(TreeLogger logger, ArtifactSet artifacts,
+ CompilationResult result, ByteArrayOutputStream out)
+ throws UnableToCompleteException {
+ EmittedArtifact symbolMapArtifact = emitBytes(logger, out.toByteArray(),
+ result.getStrongName() + STRONG_NAME_SUFFIX);
+ symbolMapArtifact.setPrivate(true);
+ artifacts.add(symbolMapArtifact);
+ }
+
+ /**
+ * Override to change the format of the symbol map.
+ */
+ protected void doWriteSymbolMap(TreeLogger logger, CompilationResult result,
+ PrintWriter pw) throws UnableToCompleteException {
+ for (SortedMap<SelectionProperty, String> map : result.getPropertyMap()) {
+ pw.print("# { ");
+
+ boolean needsComma = false;
+ for (Map.Entry<SelectionProperty, String> entry : map.entrySet()) {
+ if (needsComma) {
+ pw.print(" , ");
+ } else {
+ needsComma = true;
+ }
+
+ pw.print("'");
+ pw.print(entry.getKey().getName());
+ pw.print("' : '");
+ pw.print(entry.getValue());
+ pw.print("'");
+ }
+ pw.println(" }");
+ }
+
+ for (Map.Entry<String, String> entry : result.getSymbolMap().entrySet()) {
+ // Don't use an actual Properties object because it emits a timestamp
+ pw.print(entry.getKey());
+ pw.print(" = ");
+ pw.print(entry.getValue());
+ pw.println();
+ }
+ }
+}
diff --git a/dev/core/src/com/google/gwt/core/linker/XSLinker.java b/dev/core/src/com/google/gwt/core/linker/XSLinker.java
index 6d551fe..8f3a486 100644
--- a/dev/core/src/com/google/gwt/core/linker/XSLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/XSLinker.java
@@ -75,6 +75,8 @@
out.newlineOpt();
out.print("var $moduleName, $moduleBase;");
out.newlineOpt();
+ out.print("var $strongName = '" + strongName + "';");
+ out.newlineOpt();
out.print("var $stats = $wnd.__gwtStatsEvent ? function(a) {return $wnd.__gwtStatsEvent(a);} : null;");
out.newlineOpt();
out.print("$stats && $stats({moduleName:'" + context.getModuleName()
diff --git a/dev/core/src/com/google/gwt/dev/PermutationResult.java b/dev/core/src/com/google/gwt/dev/PermutationResult.java
index 41fe2b0..67e7da8 100644
--- a/dev/core/src/com/google/gwt/dev/PermutationResult.java
+++ b/dev/core/src/com/google/gwt/dev/PermutationResult.java
@@ -18,6 +18,7 @@
import com.google.gwt.core.ext.linker.ArtifactSet;
import java.io.Serializable;
+import java.util.SortedMap;
/**
* An extensible return type for the results of compiling a single permutation.
@@ -33,4 +34,9 @@
* The compiled JavaScript code.
*/
String[] getJs();
+
+ /**
+ * The symbol map for the permutation.
+ */
+ SortedMap<String, String> getSymbolMap();
}
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 c64996e..0f934f9 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -100,6 +100,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
import java.util.TreeSet;
/**
@@ -111,9 +113,12 @@
private static class PermutationResultImpl implements PermutationResult {
private final ArtifactSet artifacts = new ArtifactSet();
private final String[] js;
+ private final SortedMap<String, String> symbolMap;
- public PermutationResultImpl(String[] js) {
+ public PermutationResultImpl(String[] js,
+ SortedMap<String, String> symbolMap) {
this.js = js;
+ this.symbolMap = symbolMap;
}
public ArtifactSet getArtifacts() {
@@ -123,6 +128,10 @@
public String[] getJs() {
return js;
}
+
+ public SortedMap<String, String> getSymbolMap() {
+ return symbolMap;
+ }
}
/**
@@ -151,6 +160,7 @@
JProgram jprogram = ast.getJProgram();
JsProgram jsProgram = ast.getJsProgram();
JJSOptions options = unifiedAst.getOptions();
+ Map<String, JsName> symbolTable = new HashMap<String, JsName>();
ResolveRebinds.exec(jprogram, rebindAnswers);
@@ -175,8 +185,8 @@
// (7) Generate a JavaScript code DOM from the Java type declarations
jprogram.typeOracle.recomputeAfterOptimizations();
- final JavaToJavaScriptMap map = GenerateJavaScriptAST.exec(jprogram,
- jsProgram, options.getOutput());
+ JavaToJavaScriptMap map = GenerateJavaScriptAST.exec(jprogram, jsProgram,
+ options.getOutput(), symbolTable);
// (8) Normalize the JS AST.
// Fix invalid constructs created during JS AST gen.
@@ -261,7 +271,9 @@
}
}
- PermutationResult toReturn = new PermutationResultImpl(js);
+ SortedMap<String, String> symbolMap = makeSymbolMap(symbolTable);
+
+ PermutationResult toReturn = new PermutationResultImpl(js, symbolMap);
if (sourceInfoMaps != null) {
toReturn.getArtifacts().add(
new StandardCompilationAnalysis(logger, sourceInfoMaps,
@@ -752,4 +764,15 @@
return amp.makeStatement();
}
+ private static SortedMap<String, String> makeSymbolMap(
+ Map<String, JsName> symbolTable) {
+
+ SortedMap<String, String> toReturn = new TreeMap<String, String>();
+
+ for (Map.Entry<String, JsName> entry : symbolTable.entrySet()) {
+ toReturn.put(entry.getKey(), entry.getValue().getShortIdent());
+ }
+
+ return toReturn;
+ }
}
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 9ae265b..079e17f 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
@@ -165,6 +165,7 @@
JsName jsName = topScope.declareName(mangleName, name);
x.getSourceInfo().addCorrelation(Correlation.by(jsName));
names.put(x, jsName);
+ recordSymbol(x, jsName);
} else {
JsName jsName;
if (x == arrayLengthField) {
@@ -178,6 +179,7 @@
}
x.getSourceInfo().addCorrelation(Correlation.by(jsName));
names.put(x, jsName);
+ recordSymbol(x, jsName);
}
}
@@ -245,6 +247,7 @@
JsName jsName = topScope.declareName(getNameString(x), x.getShortName());
x.getSourceInfo().addCorrelation(Correlation.by(jsName));
names.put(x, jsName);
+ recordSymbol(x, jsName);
// My class scope
if (x.extnds == null) {
@@ -313,6 +316,7 @@
globalName = topScope.declareName(mangleName, name);
x.getSourceInfo().addCorrelation(Correlation.by(globalName));
names.put(x, globalName);
+ recordSymbol(x, globalName);
JsFunction jsFunction;
if (x.isNative()) {
@@ -371,6 +375,45 @@
private void push(JsScope scope) {
scopeStack.push(scope);
}
+
+ private void recordSymbol(JReferenceType x, JsName jsName) {
+ assert !symbolTable.containsKey(x.getName());
+ symbolTable.put(x.getName(), jsName);
+ }
+
+ private <T extends HasEnclosingType & HasName> void recordSymbol(T x,
+ JsName jsName) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(x.getEnclosingType().getName());
+ sb.append("::");
+
+ /*
+ * NB: The use of x.getName() can produce confusion in cases where a type
+ * has both polymorphic and static dispatch for a method, because you
+ * might see HashSet::$add() and HashSet::add(). Logically, these methods
+ * should be treated equally, however they will be implemented with
+ * separate global functions and must be recorded independently.
+ *
+ * Automated systems that process the symbol information can easily map
+ * the statically-dispatched function by looking for method names that
+ * begin with a dollar-sign and whose first parameter is the enclosing
+ * type.
+ */
+ sb.append(x.getName());
+
+ if (x instanceof JMethod) {
+ sb.append('(');
+ for (JType t : ((JMethod) x).getOriginalParamTypes()) {
+ sb.append(t.getJsniSignatureName());
+ }
+ sb.append(')');
+ }
+
+ assert !symbolTable.containsKey(sb.toString()) : "Duplicate symbol "
+ + "recorded " + jsName.getIdent() + " for " + x.getName()
+ + " and key " + sb.toString();
+ symbolTable.put(sb.toString(), jsName);
+ }
}
private class GenerateJavaScriptVisitor extends GenerateJavaScriptLiterals {
@@ -388,8 +431,9 @@
private JsFunction[] entryFunctions;
/**
- * A reverse index for the entry methods of the program ({@link JProgram#getAllEntryMethods()}).
- * Each entry method is mapped to its integer index.
+ * A reverse index for the entry methods of the program (
+ * {@link JProgram#getAllEntryMethods()}). Each entry method is mapped to
+ * its integer index.
*/
private Map<JMethod, Integer> entryMethodToIndex;
@@ -1450,7 +1494,6 @@
JsNew newExpr = new JsNew(sourceInfo);
JsNameRef superPrototypeRef = names.get(x.extnds).makeRef(sourceInfo);
newExpr.setConstructorExpression(superPrototypeRef);
- JsNode<?> staticRef = superPrototypeRef.getName().getStaticRef();
rhs = newExpr;
} else {
rhs = new JsObjectLiteral(sourceInfo);
@@ -1760,9 +1803,9 @@
}
public static JavaToJavaScriptMap exec(JProgram program, JsProgram jsProgram,
- JsOutputOption output) {
+ JsOutputOption output, Map<String, JsName> symbolTable) {
GenerateJavaScriptAST generateJavaScriptAST = new GenerateJavaScriptAST(
- program, jsProgram, output);
+ program, jsProgram, output, symbolTable);
return generateJavaScriptAST.execImpl();
}
@@ -1831,8 +1874,13 @@
private final Map<JsStatement, JMethod> vtableInitForMethodMap = new HashMap<JsStatement, JMethod>();
+ /**
+ * Maps JsNames to machine-usable identifiers.
+ */
+ private final Map<String, JsName> symbolTable;
+
private GenerateJavaScriptAST(JProgram program, JsProgram jsProgram,
- JsOutputOption output) {
+ JsOutputOption output, Map<String, JsName> symbolTable) {
this.program = program;
typeOracle = program.typeOracle;
this.jsProgram = jsProgram;
@@ -1840,6 +1888,7 @@
objectScope = jsProgram.getObjectScope();
interfaceScope = new JsScope(objectScope, "Interfaces");
this.output = output;
+ this.symbolTable = symbolTable;
/*
* Because we modify String's prototype, all fields and polymorphic methods
diff --git a/user/src/com/google/gwt/core/Core.gwt.xml b/user/src/com/google/gwt/core/Core.gwt.xml
index 7448eca..b207319 100644
--- a/user/src/com/google/gwt/core/Core.gwt.xml
+++ b/user/src/com/google/gwt/core/Core.gwt.xml
@@ -20,12 +20,15 @@
<module>
<inherits name="com.google.gwt.dev.jjs.intrinsic.Intrinsic" />
<inherits name="com.google.gwt.emul.Emulation" />
- <define-linker name="std" class="com.google.gwt.core.linker.IFrameLinker" />
- <define-linker name="xs" class="com.google.gwt.core.linker.XSLinker" />
+
+ <define-linker name="soycReport" class="com.google.gwt.core.linker.soyc.SoycReportLinker" />
<define-linker name="sso" class="com.google.gwt.core.linker.SingleScriptLinker" />
-
+ <define-linker name="std" class="com.google.gwt.core.linker.IFrameLinker" />
+ <define-linker name="symbolMaps" class="com.google.gwt.core.linker.SymbolMapsLinker" />
+ <define-linker name="xs" class="com.google.gwt.core.linker.XSLinker" />
+
<add-linker name="std" />
-
- <define-linker class="com.google.gwt.core.linker.soyc.SoycReportLinker" name="soycReport" />
<add-linker name="soycReport" />
+ <add-linker name="symbolMaps" />
+
</module>
diff --git a/user/src/com/google/gwt/core/CoreWithUserAgent.gwt.xml b/user/src/com/google/gwt/core/CoreWithUserAgent.gwt.xml
new file mode 100644
index 0000000..f6a87e2
--- /dev/null
+++ b/user/src/com/google/gwt/core/CoreWithUserAgent.gwt.xml
@@ -0,0 +1,33 @@
+<!-- -->
+<!-- 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 -->
+<!-- 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. License for the specific language governing permissions and -->
+<!-- limitations under the License. -->
+
+<!-- Deferred binding rules for core classes based on user agent. -->
+<module>
+ <inherits name="com.google.gwt.core.Core"/>
+
+ <replace-with class="com.google.gwt.core.client.impl.StackTraceCreator.CollectorMoz">
+ <when-type-is class="com.google.gwt.core.client.impl.StackTraceCreator.Collector" />
+ <any>
+ <when-property-is name="user.agent" value="gecko" />
+ <when-property-is name="user.agent" value="gecko1_8" />
+ </any>
+ </replace-with>
+
+ <replace-with class="com.google.gwt.core.client.impl.StackTraceCreator.CollectorOpera">
+ <when-type-is class="com.google.gwt.core.client.impl.StackTraceCreator.Collector" />
+ <any>
+ <when-property-is name="user.agent" value="opera" />
+ </any>
+ </replace-with>
+</module>
\ No newline at end of file
diff --git a/user/src/com/google/gwt/core/client/GWT.java b/user/src/com/google/gwt/core/client/GWT.java
index 6062bd1..bd27959 100644
--- a/user/src/com/google/gwt/core/client/GWT.java
+++ b/user/src/com/google/gwt/core/client/GWT.java
@@ -50,6 +50,12 @@
}
/**
+ * This constant is used by {@link #getPermutationStrongName} when running in
+ * hosted mode.
+ */
+ public static final String HOSTED_MODE_PERMUTATION_STRONG_NAME = "HostedMode";
+
+ /**
* Always <code>null</code> in web mode; in hosted mode provides the
* implementation for certain methods.
*/
@@ -123,6 +129,19 @@
}
/**
+ * Returns the permutation's strong name. This can be used to distinguish
+ * between different permutations of the same module. In hosted mode, this
+ * method will return {@value #HOSTED_MODE_PERMUTATION_ID}.
+ */
+ public static String getPermutationStrongName() {
+ if (GWT.isScript()) {
+ return Impl.getPermutationStrongName();
+ } else {
+ return HOSTED_MODE_PERMUTATION_STRONG_NAME;
+ }
+ }
+
+ /**
* @deprecated Use {@link Object#getClass()}, {@link Class#getName()}.
*/
@Deprecated
diff --git a/user/src/com/google/gwt/core/client/HttpThrowableReporter.java b/user/src/com/google/gwt/core/client/HttpThrowableReporter.java
new file mode 100644
index 0000000..5088667
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/HttpThrowableReporter.java
@@ -0,0 +1,109 @@
+/*
+ * 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.core.client;
+
+import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
+import com.google.gwt.xhr.client.XMLHttpRequest;
+
+/**
+ * This is a utility class which can report Throwables to the server via a
+ * JSON-formatted payload.
+ */
+public final class HttpThrowableReporter {
+
+ private static class MyHandler implements UncaughtExceptionHandler {
+ private final String url;
+
+ public MyHandler(String url) {
+ this.url = url;
+ }
+
+ public void onUncaughtException(Throwable e) {
+ report(url, e);
+ }
+ }
+
+ /**
+ * Installs an {@link UncaughtExceptionHandler} that will automatically invoke
+ * {@link #report(String, Throwable)}.
+ *
+ * @param url A URL that is suitable for use with {@link XMLHttpRequest}
+ */
+ public static void installUncaughtExceptionHandler(String url) {
+ GWT.setUncaughtExceptionHandler(new MyHandler(url));
+ }
+
+ /**
+ * Report a Throwable to the server. This method will sent an HTTP
+ * <code>POST</code> request with a JSON-formatted payload. The payload will
+ * consist of a single JSON object with the following keys:
+ * <dl>
+ * <dt>strongName</dt>
+ * <dd>The result of calling {@link GWT#getPermutationStrongName()}</dd>
+ * <dt>message</dt>
+ * <dd>The result of calling {@link Throwable#getMessage()}</dd>
+ * <dt>stackTrace</dt>
+ * <dd>A list of the methods names in the Throwable's stack trace, derived
+ * from {@link StackTraceElement#getMethodName()}.</dd>
+ * </dl>
+ *
+ * The response from the server is ignored.
+ *
+ * @param url A URL that is suitable for use with {@link XMLHttpRequest}
+ * @param t The Throwable to report
+ * @return <code>true</code> if the request was successfully initiated
+ * @see com.google.gwt.core.linker.SymbolMapsLinker
+ */
+ public static boolean report(String url, Throwable t) {
+ try {
+ XMLHttpRequest xhr = XMLHttpRequest.create();
+ xhr.open("POST", url);
+ xhr.send(buildPayload(t));
+ return true;
+ } catch (Throwable t2) {
+ return false;
+ }
+ }
+
+ /**
+ * Visible for testing.
+ */
+ static String buildPayload(Throwable t) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{\"strongName\" : ");
+ sb.append(JsonUtils.escapeValue(GWT.getPermutationStrongName()));
+ sb.append(",\"message\" : ");
+ sb.append(JsonUtils.escapeValue(t.getMessage()));
+
+ sb.append(",\"stackTrace\" : [");
+ boolean needsComma = false;
+ for (StackTraceElement e : t.getStackTrace()) {
+ if (needsComma) {
+ sb.append(",");
+ } else {
+ needsComma = true;
+ }
+
+ sb.append(JsonUtils.escapeValue(e.getMethodName()));
+ }
+ sb.append("]}");
+
+ return sb.toString();
+ }
+
+ private HttpThrowableReporter() {
+ }
+}
diff --git a/user/src/com/google/gwt/core/client/JavaScriptException.java b/user/src/com/google/gwt/core/client/JavaScriptException.java
index 8bb9b72..c319838 100644
--- a/user/src/com/google/gwt/core/client/JavaScriptException.java
+++ b/user/src/com/google/gwt/core/client/JavaScriptException.java
@@ -15,12 +15,33 @@
*/
package com.google.gwt.core.client;
+import com.google.gwt.core.client.impl.StackTraceCreator;
+
/**
* Any JavaScript exceptions occurring within JSNI methods are wrapped as this
* class when caught in Java code. The wrapping does not occur until the
* exception passes out of JSNI into Java. Before that, the thrown object
* remains a native JavaScript exception object, and can be caught in JSNI as
* normal.
+ * <p>
+ * The return value of {@link #getStackTrace()} may vary between browsers due to
+ * variations in the underlying error-reporting capabilities. When possible, the
+ * stack trace will be the stack trace of the underlying error object. If it is
+ * not possible to accurately report a stack trace, a zero-length array will be
+ * returned. In those cases where the underlying stack trace cannot be
+ * determined, {@link #fillInStackTrace()} can be called in the associated catch
+ * block to create a stack trace corresponding to the location where the
+ * JavaScriptException object was created.
+ *
+ * <pre>
+ * try {
+ * nativeMethod();
+ * } catch (JavaScriptException e) {
+ * if (e.getStackTrace().length == 0) {
+ * e.fillInStackTrace();
+ * }
+ * }
+ * </pre>
*/
public final class JavaScriptException extends RuntimeException {
@@ -105,6 +126,17 @@
*/
public JavaScriptException(Object e) {
this.e = e;
+ /*
+ * In hosted mode, JavaScriptExceptions are created exactly when the native
+ * method returns and their stack traces are fixed-up by the hosted-mode
+ * plumbing.
+ *
+ * In web mode, we'll attempt to infer the stack trace from the thrown
+ * object, although this is not possible in all browsers.
+ */
+ if (GWT.isScript()) {
+ StackTraceCreator.createStackTrace(this);
+ }
}
public JavaScriptException(String name, String description) {
@@ -163,7 +195,7 @@
}
return name;
}
-
+
private void init() {
name = getName(e);
description = getDescription(e);
diff --git a/user/src/com/google/gwt/core/client/JsonUtils.java b/user/src/com/google/gwt/core/client/JsonUtils.java
new file mode 100644
index 0000000..c205ece
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/JsonUtils.java
@@ -0,0 +1,76 @@
+/*
+ * 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.core.client;
+
+/**
+ * Provides JSON-related utility methods.
+ */
+public class JsonUtils {
+ @SuppressWarnings("unused")
+ private static JavaScriptObject escapeTable = initEscapeTable();
+
+ /**
+ * Returns a quoted, escaped JSON String.
+ */
+ public static native String escapeValue(String toEscape) /*-{
+ var s = toEscape.replace(/[\x00-\x1F\u2028\u2029"\\]/g, function(x) {
+ return @com.google.gwt.core.client.JsonUtils::escapeChar(Ljava/lang/String;)(x);
+ });
+ return "\"" + s + "\"";
+ }-*/;
+
+ /*
+ * TODO: Implement safeEval using a proper parser.
+ */
+
+ /**
+ * Evaluates a JSON expression. This method does not validate the JSON text
+ * and should only be used on JSON from trusted sources.
+ *
+ * @param <T> The type of JavaScriptObject that should be returned
+ * @param json The source JSON text
+ * @return The evaluated object
+ */
+ public static native <T extends JavaScriptObject> T unsafeEval(String json) /*-{
+ return eval('(' + json + ')');
+ }-*/;
+
+ @SuppressWarnings("unused")
+ private static native String escapeChar(String c) /*-{
+ var lookedUp = @com.google.gwt.core.client.JsonUtils::escapeTable[c.charCodeAt(0)];
+ return (lookedUp == null) ? c : lookedUp;
+ }-*/;
+
+ private static native JavaScriptObject initEscapeTable() /*-{
+ var out = [
+ "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005",
+ "\\u0006", "\\u0007", "\\b", "\\t", "\\n", "\\u000B",
+ "\\f", "\\r", "\\u000E", "\\u000F", "\\u0010", "\\u0011",
+ "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017",
+ "\\u0018", "\\u0019", "\\u001A", "\\u001B", "\\u001C", "\\u001D",
+ "\\u001E", "\\u001F"];
+ out[34] = '\\"';
+ out[92] = '\\\\';
+
+ // Unicode line separator chars
+ out[0x2028] = '\\u2028';
+ out[0x2029] = '\\u2029';
+ return out;
+ }-*/;
+
+ private JsonUtils() {
+ }
+}
diff --git a/user/src/com/google/gwt/core/client/impl/Impl.java b/user/src/com/google/gwt/core/client/impl/Impl.java
index c8426c7..2bbeb48 100644
--- a/user/src/com/google/gwt/core/client/impl/Impl.java
+++ b/user/src/com/google/gwt/core/client/impl/Impl.java
@@ -66,6 +66,10 @@
return $moduleName;
}-*/;
+ public static native String getPermutationStrongName() /*-{
+ return $strongName;
+ }-*/;
+
/**
* Called from JSNI. Do not change this implementation without updating:
* <ul>
diff --git a/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java b/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java
new file mode 100644
index 0000000..559a58e
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java
@@ -0,0 +1,221 @@
+/*
+ * 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.core.client.impl;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptException;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+/**
+ * Encapsulates logic to create a stack trace. This class should only be used in
+ * web mode.
+ */
+public class StackTraceCreator {
+ /**
+ * This class acts as a deferred-binding hook point to allow more optimal
+ * versions to be substituted. This base version simply crawls
+ * <code>arguments.callee.caller</code>.
+ */
+ static class Collector {
+ public native JsArrayString collect() /*-{
+ var seen = {};
+ var toReturn = [];
+
+ // Ignore the collect() and fillInStackTrace call
+ var callee = arguments.callee.caller.caller;
+ while (callee) {
+ var name = this.@com.google.gwt.core.client.impl.StackTraceCreator.Collector::extractName(Ljava/lang/String;)(callee.toString());
+ toReturn.push(name);
+
+ // Avoid infinite loop by associating names to function objects. We
+ // record each caller in the withThisName variable to handle functions
+ // with identical names but separate identity (such as 'anonymous')
+ var keyName = ':' + name;
+ var withThisName = seen[keyName];
+ if (withThisName) {
+ var i, j;
+ for (i = 0, j = withThisName.length; i < j; i++) {
+ if (withThisName[i] === callee) {
+ return toReturn;
+ }
+ }
+ }
+
+ (withThisName || (seen[keyName] = [])).push(callee);
+ callee = callee.caller;
+ }
+ return toReturn;
+ }-*/;
+
+ /**
+ * Attempt to infer the stack from an unknown JavaScriptObject that had been
+ * thrown. The default implementation just returns an empty array.
+ */
+ public JsArrayString inferFrom(JavaScriptObject e) {
+ return JavaScriptObject.createArray().cast();
+ }
+
+ /**
+ * Extract the name of a function from it's toString() representation.
+ * Package-access for testing.
+ */
+ protected String extractName(String fnToString) {
+ return extractNameFromToString(fnToString);
+ }
+
+ /**
+ * Raise an exception and return it.
+ */
+ protected native JavaScriptObject makeException() /*-{
+ try {
+ null.a();
+ } catch (e) {
+ return e;
+ }
+ }-*/;
+ }
+
+ /**
+ * Mozilla provides a <code>stack</code> property in thrown objects.
+ */
+ static class CollectorMoz extends Collector {
+ /**
+ * This implementation doesn't suffer from the limitations of crawling
+ * <code>caller</code> since Mozilla provides proper activation records.
+ */
+ @Override
+ public JsArrayString collect() {
+ return splice(inferFrom(makeException()), toSplice());
+ }
+
+ @Override
+ public JsArrayString inferFrom(JavaScriptObject e) {
+ JsArrayString stack = getStack(e);
+ for (int i = 0, j = stack.length(); i < j; i++) {
+ stack.set(i, extractName(stack.get(i)));
+ }
+ return stack;
+ }
+
+ protected native JsArrayString getStack(JavaScriptObject e) /*-{
+ return !!e.stack ? e.stack.split('\n') : [];
+ }-*/;
+
+ protected int toSplice() {
+ return 2;
+ }
+ }
+
+ /**
+ * Opera encodes stack trace information in the error's message.
+ */
+ static class CollectorOpera extends CollectorMoz {
+ /**
+ * We have much a much simpler format to work with.
+ */
+ @Override
+ protected String extractName(String fnToString) {
+ return fnToString.length() == 0 ? "anonymous" : fnToString;
+ }
+
+ /**
+ * Opera has the function name on every-other line.
+ */
+ @Override
+ protected JsArrayString getStack(JavaScriptObject e) {
+ JsArrayString toReturn = getMessage(e);
+ assert toReturn.length() % 2 == 0 : "Expecting an even number of lines";
+
+ int i, i2, j;
+ for (i = 0, i2 = 0, j = toReturn.length(); i2 < j; i++, i2 += 2) {
+ int idx = toReturn.get(i2).lastIndexOf("function ");
+ if (idx == -1) {
+ toReturn.set(i, "");
+ } else {
+ toReturn.set(i, toReturn.get(i2).substring(idx + 9).trim());
+ }
+ }
+ setLength(toReturn, i);
+
+ return toReturn;
+ }
+
+ @Override
+ protected int toSplice() {
+ return 3;
+ }
+
+ private native JsArrayString getMessage(JavaScriptObject e) /*-{
+ return !!e.message ? e.message.split('\n') : [];
+ }-*/;
+
+ private native void setLength(JsArrayString obj, int length) /*-{
+ obj.length = length;
+ }-*/;
+ }
+
+ /**
+ * Create a stack trace based on the current execution stack. This method
+ * should only be called in web mode.
+ */
+ public static JsArrayString createStackTrace() {
+ if (!GWT.isScript()) {
+ throw new RuntimeException(
+ "StackTraceCreator should only be called in web mode");
+ }
+
+ return GWT.<Collector> create(Collector.class).collect();
+ }
+
+ /**
+ * Create a stack trace based on a JavaScriptException. This method should
+ * only be called in web mode.
+ */
+ public static void createStackTrace(JavaScriptException e) {
+ if (!GWT.isScript()) {
+ throw new RuntimeException(
+ "StackTraceCreator should only be called in web mode");
+ }
+
+ JsArrayString stack = GWT.<Collector> create(Collector.class).inferFrom(
+ e.getException());
+
+ StackTraceElement[] stackTrace = new StackTraceElement[stack.length()];
+ for (int i = 0, j = stackTrace.length; i < j; i++) {
+ stackTrace[i] = new StackTraceElement("Unknown", stack.get(i),
+ "Unknown source", 0);
+ }
+ e.setStackTrace(stackTrace);
+ }
+
+ static String extractNameFromToString(String fnToString) {
+ String toReturn = "";
+
+ int index = fnToString.indexOf("(");
+ if (index != -1) {
+ int start = fnToString.startsWith("function") ? 8 : 0;
+ toReturn = fnToString.substring(start, index).trim();
+ }
+
+ return toReturn.length() > 0 ? toReturn : "anonymous";
+ }
+
+ private static native JsArrayString splice(JsArrayString arr, int length) /*-{
+ arr.splice(0, length);
+ return arr;
+ }-*/;
+}
diff --git a/user/src/com/google/gwt/json/client/JSONObject.java b/user/src/com/google/gwt/json/client/JSONObject.java
index bb6867a..77080a3 100644
--- a/user/src/com/google/gwt/json/client/JSONObject.java
+++ b/user/src/com/google/gwt/json/client/JSONObject.java
@@ -17,6 +17,7 @@
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsonUtils;
import java.util.AbstractSet;
import java.util.ArrayList;
@@ -178,7 +179,7 @@
} else {
sb.append(", ");
}
- sb.append(JSONString.escapeValue(key));
+ sb.append(JsonUtils.escapeValue(key));
sb.append(":");
sb.append(get(key));
}
diff --git a/user/src/com/google/gwt/json/client/JSONString.java b/user/src/com/google/gwt/json/client/JSONString.java
index 5ad02d0..abdb44d 100644
--- a/user/src/com/google/gwt/json/client/JSONString.java
+++ b/user/src/com/google/gwt/json/client/JSONString.java
@@ -16,39 +16,13 @@
package com.google.gwt.json.client;
import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsonUtils;
/**
* Represents a JSON string.
*/
public class JSONString extends JSONValue {
- static JavaScriptObject escapeTable = initEscapeTable();
-
- static native String escapeChar(String c) /*-{
- var lookedUp = @com.google.gwt.json.client.JSONString::escapeTable[c.charCodeAt(0)];
- return (lookedUp == null) ? c : lookedUp;
- }-*/;
-
- static native String escapeValue(String toEscape) /*-{
- var s = toEscape.replace(/[\x00-\x1F"\\]/g, function(x) {
- return @com.google.gwt.json.client.JSONString::escapeChar(Ljava/lang/String;)(x);
- });
- return "\"" + s + "\"";
- }-*/;
-
- private static native JavaScriptObject initEscapeTable() /*-{
- var out = [
- "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005",
- "\\u0006", "\\u0007", "\\b", "\\t", "\\n", "\\u000B",
- "\\f", "\\r", "\\u000E", "\\u000F", "\\u0010", "\\u0011",
- "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017",
- "\\u0018", "\\u0019", "\\u001A", "\\u001B", "\\u001C", "\\u001D",
- "\\u001E", "\\u001F"];
- out[34] = '\\"';
- out[92] = '\\\\';
- return out;
- }-*/;
-
/**
* Called from {@link #getUnwrapper()}.
*/
@@ -107,7 +81,7 @@
*/
@Override
public String toString() {
- return escapeValue(value);
+ return JsonUtils.escapeValue(value);
}
@Override
diff --git a/user/src/com/google/gwt/user/UserAgent.gwt.xml b/user/src/com/google/gwt/user/UserAgent.gwt.xml
index 6e4a49f..d073cd7 100644
--- a/user/src/com/google/gwt/user/UserAgent.gwt.xml
+++ b/user/src/com/google/gwt/user/UserAgent.gwt.xml
@@ -51,4 +51,5 @@
<!-- Deferred binding to optimize JRE classes based on user agent. -->
<inherits name="com.google.gwt.emul.EmulationWithUserAgent"/>
+ <inherits name="com.google.gwt.core.CoreWithUserAgent"/>
</module>
diff --git a/user/super/com/google/gwt/emul/java/lang/StackTraceElement.java b/user/super/com/google/gwt/emul/java/lang/StackTraceElement.java
index 9f3c915..90abbba 100644
--- a/user/super/com/google/gwt/emul/java/lang/StackTraceElement.java
+++ b/user/super/com/google/gwt/emul/java/lang/StackTraceElement.java
@@ -32,6 +32,17 @@
private String methodName;
+ public StackTraceElement() {
+ }
+
+ public StackTraceElement(String className, String methodName, String fileName,
+ int lineNumber) {
+ this.className = className;
+ this.methodName = methodName;
+ this.fileName = fileName;
+ this.lineNumber = lineNumber;
+ }
+
public String getClassName() {
return className;
}
diff --git a/user/super/com/google/gwt/emul/java/lang/Throwable.java b/user/super/com/google/gwt/emul/java/lang/Throwable.java
index f212156..b41223c 100644
--- a/user/super/com/google/gwt/emul/java/lang/Throwable.java
+++ b/user/super/com/google/gwt/emul/java/lang/Throwable.java
@@ -15,6 +15,9 @@
*/
package java.lang;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.core.client.impl.StackTraceCreator;
+
import java.io.PrintStream;
import java.io.Serializable;
@@ -40,6 +43,10 @@
private String detailMessage;
private transient StackTraceElement[] stackTrace;
+ {
+ fillInStackTrace();
+ }
+
public Throwable() {
}
@@ -58,11 +65,17 @@
}
/**
- * Stack traces are not currently populated by GWT. This method does nothing.
+ * Populates the stack trace information for the Throwable.
*
* @return this
*/
public Throwable fillInStackTrace() {
+ JsArrayString stack = StackTraceCreator.createStackTrace();
+ stackTrace = new StackTraceElement[stack.length()];
+ for (int i = 0, j = stackTrace.length; i < j; i++) {
+ stackTrace[i] = new StackTraceElement("Unknown", stack.get(i),
+ "Unknown source", 0);
+ }
return this;
}
diff --git a/user/test/com/google/gwt/core/CoreSuite.java b/user/test/com/google/gwt/core/CoreSuite.java
index 0bc8039..18e67ad 100644
--- a/user/test/com/google/gwt/core/CoreSuite.java
+++ b/user/test/com/google/gwt/core/CoreSuite.java
@@ -16,8 +16,10 @@
package com.google.gwt.core;
import com.google.gwt.core.client.GWTTest;
+import com.google.gwt.core.client.HttpThrowableReporterTest;
import com.google.gwt.core.client.JavaScriptExceptionTest;
import com.google.gwt.core.client.JsArrayTest;
+import com.google.gwt.core.client.impl.StackTraceCreatorTest;
import com.google.gwt.junit.tools.GWTTestSuite;
import junit.framework.Test;
@@ -30,9 +32,11 @@
GWTTestSuite suite = new GWTTestSuite("All core tests");
// $JUnit-BEGIN$
+ suite.addTestSuite(HttpThrowableReporterTest.class);
suite.addTestSuite(JavaScriptExceptionTest.class);
suite.addTestSuite(JsArrayTest.class);
suite.addTestSuite(GWTTest.class);
+ suite.addTestSuite(StackTraceCreatorTest.class);
// $JUnit-END$
return suite;
diff --git a/user/test/com/google/gwt/core/client/HttpThrowableReporterTest.java b/user/test/com/google/gwt/core/client/HttpThrowableReporterTest.java
new file mode 100644
index 0000000..58ccfa0
--- /dev/null
+++ b/user/test/com/google/gwt/core/client/HttpThrowableReporterTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.core.client;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests the HttpThrowableReporter.
+ */
+public class HttpThrowableReporterTest extends GWTTestCase {
+ private static final class Payload extends JavaScriptObject {
+ protected Payload() {
+ }
+
+ public native String getMessage() /*-{
+ return this.message;
+ }-*/;
+
+ public native JsArrayString getStackTrace() /*-{
+ return this.stackTrace;
+ }-*/;
+
+ public native String getStrongName() /*-{
+ return this.strongName;
+ }-*/;
+ }
+
+ public String getModuleName() {
+ return "com.google.gwt.core.Core";
+ }
+
+ public void testPayload() {
+ String payload;
+ Throwable e;
+
+ try {
+ throw new RuntimeException("foo");
+ } catch (Throwable t) {
+ e = t;
+ payload = HttpThrowableReporter.buildPayload(t);
+ }
+
+ assertNotNull(payload);
+ Payload p = JsonUtils.unsafeEval(payload);
+
+ assertEquals("foo", p.getMessage());
+ assertEquals(GWT.getPermutationStrongName(), p.getStrongName());
+
+ JsArrayString stack = p.getStackTrace();
+ assertNotNull(stack);
+ assertEquals(e.getStackTrace().length, stack.length());
+
+ for (int i = 0, j = e.getStackTrace().length; i < j; i++) {
+ assertEquals(e.getStackTrace()[i].getMethodName(), stack.get(i));
+ }
+ }
+}
diff --git a/user/test/com/google/gwt/core/client/impl/StackTraceCreatorTest.java b/user/test/com/google/gwt/core/client/impl/StackTraceCreatorTest.java
new file mode 100644
index 0000000..d61492b
--- /dev/null
+++ b/user/test/com/google/gwt/core/client/impl/StackTraceCreatorTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.core.client.impl;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptException;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.core.client.impl.StackTraceCreator.Collector;
+import com.google.gwt.core.client.impl.StackTraceCreator.CollectorMoz;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests StackTraceCreator in web mode. The methods in this test class are
+ * static so that their names can be reliably determined in web mode.
+ */
+public class StackTraceCreatorTest extends GWTTestCase {
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.core.Core";
+ }
+
+ public static void testJavaScriptException() {
+ StackTraceElement[] stack = null;
+ try {
+ throwNative();
+ } catch (JavaScriptException e) {
+ /*
+ * Some browsers may or may not be able to implement this at all, so we'll
+ * at least make sure that an array is returned;
+ */
+ stack = e.getStackTrace();
+ }
+ assertNotNull(stack);
+
+ String myName = null;
+ if (!GWT.isScript()) {
+ myName = "testJavaScriptException";
+ } else if (GWT.<Collector> create(Collector.class) instanceof CollectorMoz) {
+ myName = throwNativeName();
+ }
+
+ if (myName != null) {
+ boolean found = false;
+ for (StackTraceElement elt : stack) {
+ if (elt.getMethodName().equals(myName)) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue("Did not find " + myName + " in the stack " + stack, found);
+ }
+ }
+
+ /**
+ * Just make sure that reentrant behavior doesn't fail.
+ */
+ public static void testReentrantCalls() {
+ if (!GWT.isScript()) {
+ // StackTraceCreator.createStackTrace() is useless in hosted mode
+ return;
+ }
+
+ JsArrayString stack = countDown(5);
+ assertNotNull(stack);
+ assertTrue(stack.length() > 0);
+ }
+
+ public static void testStackTraces() {
+ if (!GWT.isScript()) {
+ // StackTraceCreator.createStackTrace() is useless in hosted mode
+ return;
+ }
+
+ // Since we're in web mode, we can find the name of this method's function
+ String myName = testStackTracesName();
+
+ StackTraceElement[] stack;
+ try {
+ throw new RuntimeException();
+ } catch (Throwable t) {
+ stack = t.getStackTrace();
+ }
+
+ assertNotNull(stack);
+ assertTrue(stack.length > 0);
+
+ boolean found = false;
+ for (int i = 0, j = stack.length; i < j; i++) {
+ StackTraceElement elt = stack[i];
+ String value = elt.getMethodName();
+ assertNotNull(value);
+ assertTrue(value.length() > 0);
+ assertEquals(value.length(), value.trim().length());
+
+ found |= myName.equals(value);
+ }
+
+ assertTrue("Did not find " + myName + " in the stack " + stack, found);
+ }
+
+ private static JsArrayString countDown(int count) {
+ if (count > 0) {
+ return countDown(count - 1);
+ } else {
+ return StackTraceCreator.createStackTrace();
+ }
+ }
+
+ private static native String testStackTracesName() /*-{
+ var fn = @com.google.gwt.core.client.impl.StackTraceCreatorTest::testStackTraces();
+ return @com.google.gwt.core.client.impl.StackTraceCreator::extractNameFromToString(Ljava/lang/String;)(fn.toString());
+ }-*/;
+
+ private static native void throwNative() /*-{
+ throw new Error();
+ }-*/;
+
+ private static native String throwNativeName() /*-{
+ var fn = @com.google.gwt.core.client.impl.StackTraceCreatorTest::throwNative();
+ return @com.google.gwt.core.client.impl.StackTraceCreator::extractNameFromToString(Ljava/lang/String;)(fn.toString());
+ }-*/;
+
+ public void testExtractName() {
+ assertEquals("anonymous",
+ StackTraceCreator.extractNameFromToString("function(){}"));
+ assertEquals("anonymous",
+ StackTraceCreator.extractNameFromToString("function (){}"));
+ assertEquals("anonymous",
+ StackTraceCreator.extractNameFromToString("function (){}"));
+ assertEquals("foo",
+ StackTraceCreator.extractNameFromToString("function foo(){}"));
+ assertEquals("foo",
+ StackTraceCreator.extractNameFromToString("function foo (){}"));
+ }
+}
diff --git a/user/test/com/google/gwt/json/client/JSONTest.java b/user/test/com/google/gwt/json/client/JSONTest.java
index c10751f..ba639cd 100644
--- a/user/test/com/google/gwt/json/client/JSONTest.java
+++ b/user/test/com/google/gwt/json/client/JSONTest.java
@@ -205,7 +205,7 @@
// Null characters do not work in hosted mode
public void testEscaping() {
JSONObject o = new JSONObject();
- char[] charsToEscape = new char[40];
+ char[] charsToEscape = new char[42];
for (char i = 1; i < 32; i++) {
charsToEscape[i] = i;
}
@@ -217,7 +217,9 @@
charsToEscape[37] = '\r';
charsToEscape[38] = '\t';
charsToEscape[39] = '/';
- for (int i = 1; i < 40; i++) {
+ charsToEscape[40] = '\u2028';
+ charsToEscape[41] = '\u2029';
+ for (int i = 1; i < charsToEscape.length; i++) {
o.put("c" + i, new JSONString(new Character(charsToEscape[i]).toString()));
}
assertEquals("{\"c1\":\"\\u0001\", \"c2\":\"\\u0002\", "
@@ -233,7 +235,7 @@
+ "\"c30\":\"\\u001E\", \"c31\":\"\\u001F\", \"c32\":\"\\\"\", "
+ "\"c33\":\"\\\\\", \"c34\":\"\\b\", \"c35\":\"\\f\", "
+ "\"c36\":\"\\n\", \"c37\":\"\\r\", \"c38\":\"\\t\", "
- + "\"c39\":\"/\"}", o.toString());
+ + "\"c39\":\"/\", \"c40\":\"\\u2028\", \"c41\":\"\\u2029\"}", o.toString());
}
public void testLargeArrays() {