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() {