Add $entry magic function to guarantee correct entry and exit of GWT code from ex

Patch by: bobv
Review by: scottb, bruce, rjrjr, jgw

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6400 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/hosted.html b/dev/core/src/com/google/gwt/core/ext/linker/impl/hosted.html
index 8436325..9b312e0 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/hosted.html
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/hosted.html
@@ -4,7 +4,7 @@
 //    separate functions.
 var $wnd = parent;
 var $doc = $wnd.document;
-var $moduleName, $moduleBase
+var $moduleName, $moduleBase, $entry
 ,$stats = $wnd.__gwtStatsEvent ? function(a) {return $wnd.__gwtStatsEvent(a);} : null;
 // Lightweight metrics
 if ($stats) {
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 3f83f66..7d80596 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -760,6 +760,11 @@
 
     JMethodBody body = (JMethodBody) bootStrapMethod.getBody();
     JBlock block = body.getBlock();
+
+    // Also remember $entry, which we'll handle specially in GenerateJsAst
+    JMethod registerEntry = program.getIndexedMethod("Impl.registerEntry");
+    program.addEntryMethod(registerEntry);
+
     for (String mainClassName : mainClassNames) {
       block.addStmt(makeStatsCalls(program, mainClassName));
       JDeclaredType mainType = program.getFromTypeMap(mainClassName);
@@ -986,10 +991,12 @@
         }
       } catch (ParserConfigurationException e) {
         throw new InternalCompilerException(
-            "Error reading compile report information that was just generated", e);
+            "Error reading compile report information that was just generated",
+            e);
       } catch (SAXException e) {
         throw new InternalCompilerException(
-            "Error reading compile report information that was just generated", e);
+            "Error reading compile report information that was just generated",
+            e);
       }
       dashboard.generateForOnePermutation();
       reportArtifacts = outDir.getArtifacts();
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 a42463c..a107a0d 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
@@ -1453,23 +1453,37 @@
         List<JsStatement> globalStmts) {
       /**
        * <pre>
+       * var $entry = Impl.registerEntry();
        * function gwtOnLoad(errFn, modName, modBase){
        *   $moduleName = modName;
        *   $moduleBase = modBase;
        *   if (errFn) {
        *     try {
-       *       init();
+       *       $entry(init)();
        *     } catch(e) {
        *       errFn(modName);
        *     }
        *   } else {
-       *     init();
+       *     $entry(init)();
        *   }
        * }
        * </pre>
        */
       SourceInfo sourceInfo = program.createSourceInfoSynthetic(
           GenerateJavaScriptAST.class, "gwtOnLoad");
+
+      JsName entryName = topScope.findExistingName("$entry");
+      entryName.setObfuscatable(true);
+      JsVar entryVar = new JsVar(sourceInfo, entryName);
+      JsInvocation registerEntryCall = new JsInvocation(sourceInfo);
+      JsFunction registerEntryFunction = indexedFunctions.get("Impl.registerEntry");
+      registerEntryCall.setQualifier(registerEntryFunction.getName().makeRef(
+          sourceInfo));
+      entryVar.setInitExpr(registerEntryCall);
+      JsVars entryVars = new JsVars(sourceInfo);
+      entryVars.add(entryVar);
+      globalStmts.add(entryVars);
+
       JsName gwtOnLoadName = topScope.declareName("gwtOnLoad");
       gwtOnLoadName.setObfuscatable(false);
       JsFunction gwtOnLoad = new JsFunction(sourceInfo, topScope,
@@ -1501,10 +1515,15 @@
       jsIf.setElseStmt(callBlock);
       jsTry.setTryBlock(callBlock);
       for (JsFunction func : entryFuncs) {
-        if (func != null) {
+        if (func == registerEntryFunction) {
+          continue;
+        } else if (func != null) {
           JsInvocation call = new JsInvocation(sourceInfo);
-          call.setQualifier(func.getName().makeRef(sourceInfo));
-          callBlock.getStatements().add(call.makeStmt());
+          call.setQualifier(entryName.makeRef(sourceInfo));
+          call.getArguments().add(func.getName().makeRef(sourceInfo));
+          JsInvocation entryCall = new JsInvocation(sourceInfo);
+          entryCall.setQualifier(call);
+          callBlock.getStatements().add(entryCall.makeStmt());
         }
       }
       JsCatch jsCatch = new JsCatch(sourceInfo, fnScope, "e");
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java b/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java
index 815115f..cadf257 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java
@@ -110,7 +110,7 @@
         "JavaArray", "JavaMember",
 
         // GWT-defined identifiers
-        "$wnd", "$doc", "$moduleName", "$moduleBase", "$gwt_version",
+        "$wnd", "$doc", "$entry", "$moduleName", "$moduleBase", "$gwt_version",
 
         // Identifiers used by JsStackEmulator; later set to obfuscatable
         "$stack", "$stackDepth", "$location",
diff --git a/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java b/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java
index 7b1f50f..47f8f0e 100644
--- a/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java
+++ b/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java
@@ -100,7 +100,7 @@
     try {
       Class<?> javaScriptExceptionClass = Class.forName(
           "com.google.gwt.core.client.JavaScriptException", true, cl);
-      
+
       if (!javaScriptExceptionClass.isInstance(javaScriptException)) {
         // Not a JavaScriptException
         return null;
@@ -341,11 +341,16 @@
     //
     String entryPointTypeName = null;
     try {
+      // Set up GWT-entry code
+      Class<?> clazz = loadClassFromSourceName("com.google.gwt.core.client.impl.Impl");
+      Method registerEntry = clazz.getMethod("registerEntry");
+      registerEntry.invoke(null);
+
       String[] entryPoints = host.getEntryPointTypeNames();
       if (entryPoints.length > 0) {
         for (int i = 0; i < entryPoints.length; i++) {
           entryPointTypeName = entryPoints[i];
-          Class<?> clazz = loadClassFromSourceName(entryPointTypeName);
+          clazz = loadClassFromSourceName(entryPointTypeName);
           Method onModuleLoad = null;
           try {
             onModuleLoad = clazz.getMethod("onModuleLoad");
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 d93e52e..458c7dd 100644
--- a/user/src/com/google/gwt/core/client/impl/Impl.java
+++ b/user/src/com/google/gwt/core/client/impl/Impl.java
@@ -16,6 +16,7 @@
 package com.google.gwt.core.client.impl;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
 
 /**
  * Private implementation class for GWT core. This API is should not be
@@ -23,9 +24,40 @@
  */
 public final class Impl {
 
+  /**
+   * Used by {@link #entry0(Object, Object)} to handle reentrancy.
+   */
+  private static int entryDepth = 0;
   private static int sNextHashId = 0;
 
   /**
+   * This method should be used whenever GWT code is entered from a JS context
+   * and there is no GWT code in the same module on the call stack. Examples
+   * include event handlers, exported methods, and module initialization.
+   * <p>
+   * The GWT compiler and hosted mode will provide a module-scoped variable,
+   * <code>$entry</code>, which is an alias for this method.
+   * <p>
+   * This method can be called reentrantly, which will simply delegate to the
+   * function.
+   * <p>
+   * The function passed to this method will be invoked via
+   * <code>Function.apply()</code> with the current <code>this</code> value and
+   * the invocation arguments passed to <code>$entry</code>.
+   * 
+   * @param jsFunction a JS function to invoke, which is typically a JSNI
+   *          reference to a static Java method
+   * @return the value returned when <code>jsFunction</code> is invoked, or
+   *         <code>undefined</code> if the UncaughtExceptionHandler catches an
+   *         exception raised by <code>jsFunction</code>
+   */
+  public static native JavaScriptObject entry(JavaScriptObject jsFunction) /*-{
+    return function() {
+      return @com.google.gwt.core.client.impl.Impl::entry0(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)(jsFunction, this, arguments);
+    };
+  }-*/;
+
+  /**
    * Gets an identity-based hash code on the passed-in Object by adding an
    * expando. This method should not be used with <code>null</code> or any
    * String. The former will crash and the later will produce unstable results
@@ -96,6 +128,76 @@
   }-*/;
 
   /**
+   * Implicitly called by JavaToJavaScriptCompiler.findEntryPoints().
+   */
+  public static native JavaScriptObject registerEntry() /*-{
+    if (@com.google.gwt.core.client.GWT::isScript()()) {
+      // Assignment to $entry is done by the compiler
+      return @com.google.gwt.core.client.impl.Impl::entry(Lcom/google/gwt/core/client/JavaScriptObject;);
+    } else {
+      // But we have to do in in hosted mode
+      return $entry = @com.google.gwt.core.client.impl.Impl::entry(Lcom/google/gwt/core/client/JavaScriptObject;);
+    }
+  }-*/;
+
+  private static native Object apply(Object jsFunction, Object thisObj,
+      Object arguments) /*-{
+    if (@com.google.gwt.core.client.GWT::isScript()()) {
+      return jsFunction.apply(thisObj, arguments);
+    } else {
+      _ = jsFunction.apply(thisObj, arguments);
+      if (_ != null) {
+        // Wrap for hosted mode
+        _ = Object(_);
+      }
+      return _;
+    }
+  }-*/;
+
+  /**
+   * Implements {@link #entry(JavaScriptObject)}.
+   */
+  @SuppressWarnings("unused")
+  private static Object entry0(Object jsFunction, Object thisObj,
+      Object arguments) throws Throwable {
+    assert entryDepth >= 0 : "Negative entryDepth value at entry " + entryDepth;
+
+    // We want to disable some actions in the reentrant case
+    boolean initialEntry = entryDepth++ == 0;
+
+    try {
+      /*
+       * Always invoke the UCE if we have one so that the exception never
+       * percolates up to the browser's event loop, even in a reentrant
+       * situation.
+       */
+      if (GWT.getUncaughtExceptionHandler() != null) {
+        /*
+         * This try block is guarded by the if statement so that we don't molest
+         * the exception object traveling up the stack unless we're capable of
+         * doing something useful with it.
+         */
+        try {
+          return apply(jsFunction, thisObj, arguments);
+        } catch (Throwable t) {
+          GWT.getUncaughtExceptionHandler().onUncaughtException(t);
+          return undefined();
+        }
+      } else {
+        // Can't handle any exceptions, let them percolate normally
+        return apply(jsFunction, thisObj, arguments);
+      }
+    } finally {
+      if (initialEntry) {
+        // TODO(bobv) FinallyCommand.flush() goes here
+      }
+      entryDepth--;
+      assert entryDepth >= 0 : "Negative entryDepth value at exit "
+          + entryDepth;
+    }
+  }
+
+  /**
    * Called from JSNI. Do not change this implementation without updating:
    * <ul>
    * <li>{@link com.google.gwt.user.client.rpc.impl.SerializerBase}</li>
@@ -105,4 +207,9 @@
   private static int getNextHashId() {
     return ++sNextHashId;
   }
+
+  private static native Object undefined() /*-{
+    // Intentionally not returning a value
+    return;
+  }-*/;
 }