Fixes a bug in r2611: when doing a sloppy scan for JSNI 
references, @ signs that do not begin a valid JSNI
reference would cause an exception to be thrown.
This patch suppresses such errors and skips to the
next @ sign.

Review by: scottb



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2651 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/build.xml b/dev/core/build.xml
index a89c2eb..748985e 100755
--- a/dev/core/build.xml
+++ b/dev/core/build.xml
@@ -105,6 +105,9 @@
 				<pathelement location="${gwt.tools.lib}/eclipse/org.eclipse.swt.gtk-linux-3.2.1.jar" />
 			</classpath>
 		</gwt.javac>
+		<copy todir="${javac.out}">
+			<fileset dir="src" includes="**/*.properties"/>
+                </copy>
 	</target>
 
 	<target name="checkstyle" description="Static analysis of source">
@@ -131,7 +134,7 @@
 			</classpath>
 		</taskdef>
 	
-		<echo message="Writing test results to @{junit.out}/reports for @{test.cases}" />
+		<echo message="Writing test results to ${junit.out}/reports for ${test.cases}" />
 		<mkdir dir="${junit.out}/reports" />
 	
 		<echo message="${javac.out} ${javac.junit.out}" />
diff --git a/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java b/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java
index e9a1e5b..afbeef1 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java
@@ -24,6 +24,9 @@
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsStatement;
 import com.google.gwt.dev.js.ast.JsVisitor;
+import com.google.gwt.dev.js.rhino.Context;
+import com.google.gwt.dev.js.rhino.ErrorReporter;
+import com.google.gwt.dev.js.rhino.EvaluatorException;
 import com.google.gwt.dev.js.rhino.TokenStream;
 import com.google.gwt.dev.util.Jsni;
 
@@ -33,6 +36,7 @@
 import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
 
 import java.io.IOException;
+import java.io.Reader;
 import java.io.StringReader;
 import java.util.Collections;
 import java.util.LinkedHashSet;
@@ -46,6 +50,26 @@
  * quickly but it will return a superset of the actual JSNI references.
  */
 public class FindJsniRefVisitor extends ASTVisitor {
+  /**
+   * A Rhino error reporter that discards any errors it sees.
+   */
+  private static ErrorReporter NullErrorReporter = new ErrorReporter() {
+    public void error(String message, String sourceName, int line,
+        String lineSource, int lineOffset) {
+      // ignore it
+    }
+
+    public EvaluatorException runtimeError(String message, String sourceName,
+        int line, String lineSource, int lineOffset) {
+      throw new InternalCompilerException("Rhino run-time error: " + message);
+    }
+
+    public void warning(String message, String sourceName, int line,
+        String lineSource, int lineOffset) {
+      // ignore it
+    }
+  };
+
   private final Set<String> jsniRefs = new LinkedHashSet<String>();
   private boolean sloppy = false;
 
@@ -113,8 +137,7 @@
         }
       }.acceptList(result);
     } catch (IOException e) {
-      throw new InternalCompilerException(
-          "Internal error searching for JSNI references", e);
+      throw new InternalCompilerException(e.getMessage(), e);
     } catch (JsParserException e) {
       // ignore, we only care about finding valid references
     }
@@ -129,20 +152,19 @@
         break;
       }
       try {
-        // use Rhino to read a JavaScript identifier
         reader.reset();
         reader.skip(idx);
-        TokenStream tokStr = new TokenStream(reader, "(memory)", 0);
-        if (tokStr.getToken() != TokenStream.NAME) {
-          // scottb: Why do we break here?
-          break;
-        }
-        String jsniRefString = tokStr.getString();
+      } catch (IOException e) {
+        throw new InternalCompilerException(e.getMessage(), e);
+      }
+      String jsniRefString = readJsIdentifier(reader);
+      if (jsniRefString == null) {
+        // badly formatted identifier; skip to the next @ sign
+          idx++;
+      } else {
         assert (jsniRefString.charAt(0) == '@');
         jsniRefs.add(jsniRefString.substring(1));
         idx += jsniRefString.length();
-      } catch (IOException e) {
-        throw new InternalCompilerException("Unexpected IO error while reading a JS identifier", e);
       }
     }
   }
@@ -164,4 +186,24 @@
     return jsniCode;
   }
 
+  /**
+   * Read a JavaScript identifier using Rhino. If the parse fails, returns null.
+   */
+  private String readJsIdentifier(Reader reader)
+      throws InternalCompilerException {
+    try {
+      Context.enter().setErrorReporter(NullErrorReporter);
+      TokenStream tokStr = new TokenStream(reader, "(memory)", 0);
+      if (tokStr.getToken() == TokenStream.NAME) {
+        return tokStr.getString();
+      } else {
+        return null;
+      }
+    } catch (IOException e) {
+      throw new InternalCompilerException(e.getMessage(), e);
+    } finally {
+      Context.exit();
+    }
+  }
+
 }
diff --git a/dev/core/test/com/google/gwt/dev/jdt/LongFromJSNITest.java b/dev/core/test/com/google/gwt/dev/jdt/LongFromJSNITest.java
index 2412ad2..4bb445c 100644
--- a/dev/core/test/com/google/gwt/dev/jdt/LongFromJSNITest.java
+++ b/dev/core/test/com/google/gwt/dev/jdt/LongFromJSNITest.java
@@ -26,6 +26,19 @@
  * Test access to longs from JSNI.
  */
 public class LongFromJSNITest extends TestCase {
+  public void testBogusRef() throws UnableToCompleteException {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {                                  \n");
+    code.append("volatile long x = -1;                          \n");
+    code.append("native void jsniMeth() /*-{                    \n");
+    code.append("  // @\\bogus refs should just be skipped      \n");
+    code.append("  $wnd.alert(\"x is: \"+this.@Buggy::x); }-*/; \n");
+    code.append("}                                              \n");
+
+    shouldGenerateError(code, 3,
+        "Referencing field 'Buggy.x': type 'long' is not safe to access in JSNI code");
+  }
+
   public void testCyclicReferences() throws UnableToCompleteException {
     {
       StringBuffer buggy = new StringBuffer();
@@ -191,6 +204,19 @@
         "Referencing method 'Buggy.m': return type 'long' is not safe to access in JSNI code");
   }
 
+  public void testRefInString() throws UnableToCompleteException {
+    {
+      StringBuffer code = new StringBuffer();
+      code.append("import com.google.gwt.core.client.UnsafeNativeLong;");
+      code.append("class Buggy {\n");
+      code.append("  void print(long x) { }\n");
+      code.append("  native void jsniMeth() /*-{ 'this.@Buggy::print(J)(0)'; }-*/;\n");
+      code.append("}\n");
+
+      shouldGenerateNoError(code);
+    }
+  }
+
   public void testUnsafeAnnotation() throws UnableToCompleteException {
     {
       StringBuffer code = new StringBuffer();
@@ -204,19 +230,6 @@
       shouldGenerateNoError(code);
     }
   }
-  
-  public void testRefInString() throws UnableToCompleteException {
-    {
-      StringBuffer code = new StringBuffer();
-      code.append("import com.google.gwt.core.client.UnsafeNativeLong;");
-      code.append("class Buggy {\n");
-      code.append("  void print(long x) { }\n");
-      code.append("  native void jsniMeth() /*-{ 'this.@Buggy::print(J)(0)'; }-*/;\n");
-      code.append("}\n");
-
-      shouldGenerateNoError(code);
-    }
-  }
 
   public void testViolator() throws UnableToCompleteException {
     {
@@ -317,8 +330,8 @@
     shouldGenerateNoError(code, null);
   }
 
-  private void shouldGenerateNoError(CharSequence code,
-      CharSequence extraCode) throws UnableToCompleteException {
+  private void shouldGenerateNoError(CharSequence code, CharSequence extraCode)
+      throws UnableToCompleteException {
     shouldGenerateError(code, extraCode, -1, null);
   }
 }