Implemented long-from-JSNI access warnings.  These warnings can be disabled by adding @SuppressWarnings("restriction") to the JSNI method or enclosing type.

TODO: review the actual warnings & make sure they are maximally helpful
Patch by: spoon
Review by: scottb (desk review)


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2220 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JPrimitiveType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JPrimitiveType.java
index f07be8d..e743709 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JPrimitiveType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JPrimitiveType.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -22,16 +22,23 @@
  * Represents a primitive type in a declaration.
  */
 public class JPrimitiveType extends JType {
-
-  public static final JPrimitiveType BOOLEAN = create("boolean", "Z");
-  public static final JPrimitiveType BYTE = create("byte", "B");
-  public static final JPrimitiveType CHAR = create("char", "C");
-  public static final JPrimitiveType DOUBLE = create("double", "D");
-  public static final JPrimitiveType FLOAT = create("float", "F");
-  public static final JPrimitiveType INT = create("int", "I");
-  public static final JPrimitiveType LONG = create("long", "J");
-  public static final JPrimitiveType SHORT = create("short", "S");
-  public static final JPrimitiveType VOID = create("void", "V");
+  public static final JPrimitiveType BOOLEAN = create("boolean",
+      JniConstants.DESC_BOOLEAN);
+  public static final JPrimitiveType BYTE = create("byte",
+      JniConstants.DESC_BYTE);
+  public static final JPrimitiveType CHAR = create("char",
+      JniConstants.DESC_CHAR);
+  public static final JPrimitiveType DOUBLE = create("double",
+      JniConstants.DESC_DOUBLE);
+  public static final JPrimitiveType FLOAT = create("float",
+      JniConstants.DESC_FLOAT);
+  public static final JPrimitiveType INT = create("int", JniConstants.DESC_INT);
+  public static final JPrimitiveType LONG = create("long",
+      JniConstants.DESC_LONG);
+  public static final JPrimitiveType SHORT = create("short",
+      JniConstants.DESC_SHORT);
+  public static final JPrimitiveType VOID = create("void",
+      JniConstants.DESC_VOID);
 
   private static Map<String, JPrimitiveType> map;
 
@@ -39,8 +46,8 @@
     return getMap().get(typeName);
   }
 
-  private static JPrimitiveType create(String name, String jni) {
-    JPrimitiveType type = new JPrimitiveType(name, jni);
+  private static JPrimitiveType create(String name, char jni) {
+    JPrimitiveType type = new JPrimitiveType(name, String.valueOf(jni));
     Object existing = getMap().put(name, type);
     assert (existing == null);
     return type;
@@ -133,6 +140,11 @@
   }
 
   @Override
+  public String toString() {
+    return name;
+  }
+  
+  @Override
   JPrimitiveType getSubstitutedType(JParameterizedType parameterizedType) {
     return this;
   }
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JniConstants.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JniConstants.java
new file mode 100644
index 0000000..7de90b6
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JniConstants.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2008 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.ext.typeinfo;
+
+/**
+ * Constants used with JNI type descriptors.
+ */
+public class JniConstants {
+  /*
+   * TODO: use this from more places; maybe provide mappings?
+   */
+  public static final char DESC_ARRAY = '[';
+  public static final char DESC_BOOLEAN = 'Z';
+  public static final char DESC_BYTE = 'B';
+  public static final char DESC_CHAR = 'C';
+  public static final char DESC_DOUBLE = 'D';
+  public static final char DESC_FLOAT = 'F';
+  public static final char DESC_INT = 'I';
+  public static final char DESC_LONG = 'J';
+  public static final char DESC_REF = 'L';
+  public static final char DESC_REF_END = ';';
+  public static final char DESC_SHORT = 'S';
+  public static final char DESC_VOID = 'V';
+}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/AbstractCompiler.java b/dev/core/src/com/google/gwt/dev/jdt/AbstractCompiler.java
index c95e9b5..3eac7c4 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/AbstractCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/AbstractCompiler.java
@@ -180,9 +180,7 @@
         resolvePossiblyNestedType(typeName);
       }
 
-      JSORestrictionsChecker.check(cud);
-
-      doCompilationUnitDeclarationValidation(cud);
+      doCompilationUnitDeclarationValidation(cud, logger);
 
       // Optionally remember this cud.
       //
@@ -573,9 +571,8 @@
   @SuppressWarnings("unused")
   // overrider may use unused parameter
   protected void doCompilationUnitDeclarationValidation(
-      CompilationUnitDeclaration cud) {
+      CompilationUnitDeclaration cud, TreeLogger logger) {
     // Do nothing by default.
-    //
   }
 
   @SuppressWarnings("unused")
diff --git a/dev/core/src/com/google/gwt/dev/jdt/AstCompiler.java b/dev/core/src/com/google/gwt/dev/jdt/AstCompiler.java
index 4a00d50..f7fe488 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/AstCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/AstCompiler.java
@@ -120,7 +120,8 @@
 
   @Override
   protected void doCompilationUnitDeclarationValidation(
-      CompilationUnitDeclaration cud) {
+      CompilationUnitDeclaration cud, TreeLogger logger) {
+    JSORestrictionsChecker.check(cud);
     BinaryTypeReferenceRestrictionsChecker.check(cud);
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/jdt/FindDeferredBindingSitesVisitor.java b/dev/core/src/com/google/gwt/dev/jdt/FindDeferredBindingSitesVisitor.java
index 03be599..f07fa53 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/FindDeferredBindingSitesVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/FindDeferredBindingSitesVisitor.java
@@ -16,18 +16,16 @@
 package com.google.gwt.dev.jdt;
 
 import org.eclipse.jdt.core.compiler.CharOperation;
-import org.eclipse.jdt.core.compiler.IProblem;
 import org.eclipse.jdt.internal.compiler.ASTVisitor;
-import org.eclipse.jdt.internal.compiler.CompilationResult;
 import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.Expression;
 import org.eclipse.jdt.internal.compiler.ast.MessageSend;
 import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
 import org.eclipse.jdt.internal.compiler.lookup.Scope;
-import org.eclipse.jdt.internal.compiler.problem.DefaultProblem;
-import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
-import org.eclipse.jdt.internal.compiler.util.Util;
 
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -60,23 +58,12 @@
       String message) {
     MessageSend messageSend = site.messageSend;
     Scope scope = site.scope;
-    CompilationResult compResult = scope.compilationUnitScope().referenceContext().compilationResult();
-    int[] lineEnds = compResult.getLineSeparatorPositions();
-    int startLine = Util.getLineNumber(messageSend.sourceStart(), lineEnds, 0,
-        lineEnds.length - 1);
-    int startColumn = Util.searchColumnNumber(lineEnds, startLine,
-        messageSend.sourceStart());
-    DefaultProblem problem = new DefaultProblem(compResult.fileName, message,
-        IProblem.ExternalProblemNotFixable, null, ProblemSeverities.Error,
-        messageSend.sourceStart(), messageSend.sourceEnd(), startLine, startColumn);
-    compResult.record(problem, scope.referenceContext());
+    // Safe since CUS.referenceContext is set in its constructor.
+    CompilationUnitDeclaration cud = scope.compilationUnitScope().referenceContext;
+    Shared.recordError(messageSend, cud, message);
   }
 
-  private final Map results;
-
-  public FindDeferredBindingSitesVisitor(Map requestedTypes) {
-    this.results = requestedTypes;
-  }
+  private final Map<String, DeferredBindingSite> results = new HashMap<String, DeferredBindingSite>();
 
   public void endVisit(MessageSend messageSend, BlockScope scope) {
     if (messageSend.binding == null) {
@@ -120,4 +107,8 @@
       results.put(typeName, site);
     }
   }
+
+  public Map<String, DeferredBindingSite> getSites() {
+    return Collections.unmodifiableMap(results);
+  }
 }
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 bba600c..908d25a 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2008 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
@@ -24,6 +24,7 @@
 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.util.Jsni;
 
 import org.eclipse.jdt.internal.compiler.ASTVisitor;
 import org.eclipse.jdt.internal.compiler.ast.Argument;
@@ -32,20 +33,25 @@
 
 import java.io.IOException;
 import java.io.StringReader;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
 /**
  * Walks the AST to find references to Java identifiers from within JSNI blocks.
+ * By default, it only records the class names referenced. If
+ * {@link #justRecordClasses(boolean)} is called with an argument of
+ * <code>false</code>, then full references to fields or methods are
+ * recorded.
  */
 public class FindJsniRefVisitor extends ASTVisitor {
-
-  private final Set<String> jsniClasses;
+  private final Set<String> jsniRefs = new HashSet<String>();
   private final JsParser jsParser = new JsParser();
   private final JsProgram jsProgram = new JsProgram();
 
-  public FindJsniRefVisitor(Set<String> jsniClasses) {
-    this.jsniClasses = jsniClasses;
+  public Set<String> getJsniRefs() {
+    return Collections.unmodifiableSet(jsniRefs);
   }
 
   public boolean visit(MethodDeclaration methodDeclaration, ClassScope scope) {
@@ -57,13 +63,13 @@
     char[] source = methodDeclaration.compilationResult().getCompilationUnit().getContents();
     String jsniCode = String.valueOf(source, methodDeclaration.bodyStart,
         methodDeclaration.bodyEnd - methodDeclaration.bodyStart + 1);
-    int startPos = jsniCode.indexOf("/*-{");
-    int endPos = jsniCode.lastIndexOf("}-*/");
+    int startPos = jsniCode.indexOf(Jsni.JSNI_BLOCK_START);
+    int endPos = jsniCode.lastIndexOf(Jsni.JSNI_BLOCK_END);
     if (startPos < 0 || endPos < 0) {
       return false; // ignore the error
     }
 
-    startPos += 3; // move up to open brace
+    startPos += Jsni.JSNI_BLOCK_START.length() - 1; // move up to open brace
     endPos += 1; // move past close brace
 
     jsniCode = jsniCode.substring(startPos, endPos);
@@ -90,8 +96,7 @@
         public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
           String ident = x.getIdent();
           if (ident.charAt(0) == '@') {
-            String className = ident.substring(1, ident.indexOf(':'));
-            jsniClasses.add(className);
+            jsniRefs.add(ident.substring(1));
           }
         }
       }.acceptList(result);
diff --git a/dev/core/src/com/google/gwt/dev/jdt/JSORestrictionsChecker.java b/dev/core/src/com/google/gwt/dev/jdt/JSORestrictionsChecker.java
index 908317d..6437f1c 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/JSORestrictionsChecker.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/JSORestrictionsChecker.java
@@ -18,9 +18,7 @@
 import com.google.gwt.dev.shell.JsValueGlue;
 
 import org.eclipse.jdt.core.compiler.CharOperation;
-import org.eclipse.jdt.core.compiler.IProblem;
 import org.eclipse.jdt.internal.compiler.ASTVisitor;
-import org.eclipse.jdt.internal.compiler.CompilationResult;
 import org.eclipse.jdt.internal.compiler.ast.ASTNode;
 import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
@@ -36,9 +34,6 @@
 import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
 import org.eclipse.jdt.internal.compiler.lookup.Scope;
 import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
-import org.eclipse.jdt.internal.compiler.problem.DefaultProblem;
-import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
-import org.eclipse.jdt.internal.compiler.util.Util;
 
 /**
  * Check a compilation unit for violations of
@@ -203,16 +198,7 @@
   }
 
   private void errorOn(ASTNode node, String error) {
-    CompilationResult compResult = cud.compilationResult();
-    int[] lineEnds = compResult.getLineSeparatorPositions();
-    int startLine = Util.getLineNumber(node.sourceStart(), lineEnds, 0,
-        lineEnds.length - 1);
-    int startColumn = Util.searchColumnNumber(lineEnds, startLine,
-        node.sourceStart());
-    DefaultProblem problem = new DefaultProblem(compResult.fileName, error,
-        IProblem.ExternalProblemNotFixable, null, ProblemSeverities.Error,
-        node.sourceStart(), node.sourceEnd(), startLine, startColumn);
-    compResult.record(problem, cud);
+    Shared.recordError(node, cud, error);
   }
 
   private boolean isForJSOSubclass(Scope scope) {
diff --git a/dev/core/src/com/google/gwt/dev/jdt/LongFromJSNIChecker.java b/dev/core/src/com/google/gwt/dev/jdt/LongFromJSNIChecker.java
new file mode 100644
index 0000000..40808d8
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jdt/LongFromJSNIChecker.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jdt;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JField;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.JniConstants;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.jjs.InternalCompilerException;
+import com.google.gwt.dev.util.Jsni;
+import com.google.gwt.dev.util.JsniRef;
+
+import org.eclipse.jdt.internal.compiler.ASTVisitor;
+import org.eclipse.jdt.internal.compiler.CompilationResult;
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
+import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
+import org.eclipse.jdt.internal.compiler.util.Util;
+
+import java.lang.reflect.Field;
+import java.util.Set;
+
+/**
+ * Tests for access to Java longs from JSNI. Issues a warning for:
+ * <ul>
+ * <li> JSNI methods with a parameter or return type of long.
+ * <li> Access from JSNI to a field whose type is long.
+ * <li> Access from JSNI to a method with a parameter or return type of long.
+ * </ul>
+ * All tests also apply for arrays of longs, arrays of arrays of longs, etc.
+ */
+public class LongFromJSNIChecker {
+  private class CheckingVisitor extends ASTVisitor implements
+      ClassFileConstants {
+    @Override
+    public void endVisit(MethodDeclaration meth, ClassScope scope) {
+      if (meth.isNative() && !suppressingWarnings(meth, scope)) {
+        // check return type
+        final TypeReference returnType = meth.returnType;
+        if (containsLong(returnType, scope)) {
+          warn(meth, "JSNI method with return type of " + returnType);
+        }
+
+        // check parameter types
+        if (meth.arguments != null) {
+          for (Argument arg : meth.arguments) {
+            if (containsLong(arg.type, scope)) {
+              warn(arg, "JSNI method with a parameter of type " + arg.type);
+            }
+          }
+        }
+
+        // check JSNI references
+        FindJsniRefVisitor jsniRefsVisitor = new FindJsniRefVisitor();
+        meth.traverse(jsniRefsVisitor, scope);
+        Set<String> jsniRefs = jsniRefsVisitor.getJsniRefs();
+
+        for (String jsniRefString : jsniRefs) {
+          JsniRef jsniRef = JsniRef.parse(jsniRefString);
+          if (hasLongParam(jsniRef)) {
+            warn(meth, "Passing a long into Java from JSNI (method "
+                + jsniRef.memberName() + ")");
+          }
+          JType jsniRefType = getType(jsniRef);
+          if (containsLong(jsniRefType)) {
+            if (jsniRef.isMethod()) {
+              warn(meth, "Method " + jsniRef.memberName() + " returns type "
+                  + jsniRefType + ", which cannot be processed in JSNI code");
+            } else {
+              warn(meth, "Field " + jsniRef.memberName() + " has type "
+                  + jsniRefType + ", which cannot be processed in JSNI code");
+            }
+          }
+        }
+      }
+    }
+
+    private boolean containsLong(JType type) {
+      if (type != null && type.isArray() != null) {
+        return containsLong(type.isArray().getLeafType());
+      }
+      return type == JPrimitiveType.LONG;
+    }
+
+    /**
+     * Check whether the argument type is long or an array of (arrays of...)
+     * long. If the argument is <code>null</code>, returns <code>false</code>.
+     */
+    private boolean containsLong(TypeBinding type) {
+      if (type instanceof BaseTypeBinding) {
+        BaseTypeBinding btb = (BaseTypeBinding) type;
+        if (btb.id == TypeIds.T_long) {
+          return true;
+        }
+      }
+
+      if (type instanceof ArrayBinding) {
+        ArrayBinding ab = (ArrayBinding) type;
+        if (containsLong(ab.elementsType())) {
+          return true;
+        }
+      }
+
+      return false;
+    }
+
+    private boolean containsLong(final TypeReference returnType,
+        ClassScope scope) {
+      return returnType != null && containsLong(returnType.resolveType(scope));
+    }
+
+    /**
+     * Returns either the type returned if this reference is "read". For a
+     * field, returns the field's type. For a method, returns the method's
+     * return type. If the reference cannot be resolved, returns null.
+     */
+    private JType getType(JsniRef jsniRef) {
+      JClassType type = typeOracle.findType(jsniRef.className());
+      if (type == null) {
+        return null;
+      }
+
+      if (jsniRef.isMethod()) {
+        for (JMethod method : type.getMethods()) {
+          if (paramTypesMatch(method, jsniRef)) {
+            return method.getReturnType();
+          }
+        }
+        // no method matched
+        return null;
+      } else {
+        JField field = type.getField(jsniRef.memberName());
+        if (field != null) {
+          return field.getType();
+        }
+        // field not found
+        return null;
+      }
+    }
+
+    private boolean hasLongParam(JsniRef jsniRef) {
+      if (!jsniRef.isMethod()) {
+        return false;
+      }
+      for (String type : jsniRef.paramTypes()) {
+        if (type.charAt(type.length() - 1) == JniConstants.DESC_LONG) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    private boolean paramTypesMatch(JMethod method, JsniRef jsniRef) {
+      JsniRef methodJsni = JsniRef.parse(Jsni.getJsniSignature(method));
+      return methodJsni.equals(jsniRef);
+    }
+
+    private boolean suppressingWarnings(MethodDeclaration meth, ClassScope scope) {
+      CompilationResult result = scope.referenceCompilationUnit().compilationResult;
+      long[] suppressWarningIrritants;
+      long[] suppressWarningScopePositions; // (start << 32) + end
+      int suppressWarningsCount;
+
+      try {
+        {
+          Field field = CompilationResult.class.getDeclaredField("suppressWarningIrritants");
+          field.setAccessible(true);
+          suppressWarningIrritants = (long[]) field.get(result);
+        }
+        {
+          Field field = CompilationResult.class.getDeclaredField("suppressWarningScopePositions");
+          field.setAccessible(true);
+          suppressWarningScopePositions = (long[]) field.get(result);
+        }
+        {
+          Field field = CompilationResult.class.getDeclaredField("suppressWarningsCount");
+          field.setAccessible(true);
+          suppressWarningsCount = (Integer) field.get(result);
+        }
+      } catch (NoSuchFieldException e) {
+        throw new InternalCompilerException(
+            "Failed to read suppress warnings data from JDT", e);
+      } catch (IllegalAccessException e) {
+        throw new InternalCompilerException(
+            "Failed to read suppress warnings data from JDT", e);
+      }
+
+      for (int i = 0; i < suppressWarningsCount; i++) {
+        if ((suppressWarningIrritants[i] & CompilerOptions.DiscouragedReference) != 0) {
+          long start = suppressWarningScopePositions[i] >> 32;
+          long end = suppressWarningScopePositions[i] & 0xFFFFFFFF;
+          if (meth.bodyStart >= start && meth.bodyStart <= end) {
+            return true;
+          }
+        }
+      }
+
+      return false;
+    }
+
+    private void warn(ASTNode node, String message) {
+      CompilationResult compResult = cud.compilationResult();
+      int[] lineEnds = compResult.getLineSeparatorPositions();
+      int startLine = Util.getLineNumber(node.sourceStart(), lineEnds, 0,
+          lineEnds.length - 1);
+      String fileName = String.valueOf(cud.getFileName());
+      logger.log(TreeLogger.WARN, fileName + "(" + startLine + "): " + message,
+          null);
+    }
+  }
+
+  /**
+   * Checks an entire
+   * {@link org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration}.
+   * 
+   */
+  public static void check(TypeOracle typeOracle,
+      CompilationUnitDeclaration cud, TreeLogger logger) {
+    LongFromJSNIChecker checker = new LongFromJSNIChecker(typeOracle, cud,
+        logger);
+    checker.check();
+  }
+
+  private final CompilationUnitDeclaration cud;
+  private final TreeLogger logger;
+  private final TypeOracle typeOracle;
+
+  private LongFromJSNIChecker(TypeOracle typeOracle,
+      CompilationUnitDeclaration cud, TreeLogger logger) {
+    this.typeOracle = typeOracle;
+    this.cud = cud;
+    this.logger = logger;
+  }
+
+  private void check() {
+    cud.traverse(new CheckingVisitor(), cud.scope);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java b/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java
index 48b2ac4..f54a39c 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java
@@ -617,6 +617,10 @@
     Util.invokeInaccessableMethod(TypeOracle.class, "refresh",
         new Class[] {TreeLogger.class}, oracle, new Object[] {logger});
 
+    for (CompilationUnitDeclaration cud : cudsByFileName.values()) {
+      LongFromJSNIChecker.check(oracle, cud, logger);
+    }
+
     PerfLogger.end();
 
     return oracle;
diff --git a/dev/core/src/com/google/gwt/dev/jdt/WebModeCompilerFrontEnd.java b/dev/core/src/com/google/gwt/dev/jdt/WebModeCompilerFrontEnd.java
index c3403a2..d7efced 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/WebModeCompilerFrontEnd.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/WebModeCompilerFrontEnd.java
@@ -19,6 +19,7 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.jdt.FindDeferredBindingSitesVisitor.DeferredBindingSite;
 import com.google.gwt.dev.util.Empty;
+import com.google.gwt.dev.util.JsniRef;
 import com.google.gwt.dev.util.Util;
 
 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
@@ -27,9 +28,7 @@
 import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
 import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
 
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 
@@ -70,15 +69,32 @@
     return rebindPermOracle;
   }
 
+  @Override
+  protected void doCompilationUnitDeclarationValidation(
+      CompilationUnitDeclaration cud, TreeLogger logger) {
+    /*
+     * Anything that makes it here was already checked by AstCompiler while
+     * building TypeOracle; no need to rerun checks.
+     */
+  }
+
   /**
    * Pull in types referenced only via JSNI.
    */
   protected String[] doFindAdditionalTypesUsingJsni(TreeLogger logger,
       CompilationUnitDeclaration cud) {
-    Set dependentTypeNames = new HashSet();
-    FindJsniRefVisitor v = new FindJsniRefVisitor(dependentTypeNames);
+    FindJsniRefVisitor v = new FindJsniRefVisitor();
     cud.traverse(v, cud.scope);
-    return (String[]) dependentTypeNames.toArray(Empty.STRINGS);
+    Set<String> jsniRefs = v.getJsniRefs();
+    Set<String> dependentTypeNames = new HashSet<String>();
+    for (String jsniRef : jsniRefs) {
+      JsniRef parsed = JsniRef.parse(jsniRef);
+      if (parsed != null) {
+        // If we fail to parse, don't add a class reference.
+        dependentTypeNames.add(parsed.className());
+      }
+    }
+    return dependentTypeNames.toArray(Empty.STRINGS);
   }
 
   /**
@@ -86,20 +102,18 @@
    */
   protected String[] doFindAdditionalTypesUsingRebinds(TreeLogger logger,
       CompilationUnitDeclaration cud) {
-    Set dependentTypeNames = new HashSet();
+    Set<String> dependentTypeNames = new HashSet<String>();
 
     // Find all the deferred binding request types.
     //
-    Map requestedTypes = new HashMap();
-    FindDeferredBindingSitesVisitor v = new FindDeferredBindingSitesVisitor(
-        requestedTypes);
+    FindDeferredBindingSitesVisitor v = new FindDeferredBindingSitesVisitor();
     cud.traverse(v, cud.scope);
+    Map<String, DeferredBindingSite> requestedTypes = v.getSites();
 
     // For each, ask the host for every possible deferred binding answer.
     //
-    for (Iterator iter = requestedTypes.keySet().iterator(); iter.hasNext();) {
-      String reqType = (String) iter.next();
-      DeferredBindingSite site = (DeferredBindingSite) requestedTypes.get(reqType);
+    for (String reqType : requestedTypes.keySet()) {
+      DeferredBindingSite site = requestedTypes.get(reqType);
 
       try {
         String[] resultTypes = rebindPermOracle.getAllPossibleRebindAnswers(
@@ -159,6 +173,6 @@
             "Failed to resolve '" + reqType + "' via deferred binding");
       }
     }
-    return (String[]) dependentTypeNames.toArray(Empty.STRINGS);
+    return dependentTypeNames.toArray(Empty.STRINGS);
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
index 9a15320..acf6514 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.dev.jjs.impl;
 
-import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.jjs.ast.Context;
 import com.google.gwt.dev.jjs.ast.JBinaryOperation;
 import com.google.gwt.dev.jjs.ast.JBinaryOperator;
diff --git a/dev/core/super/com/google/gwt/lang/LongLib.java b/dev/core/super/com/google/gwt/lang/LongLib.java
index be4bab3..a26ad0b 100644
--- a/dev/core/super/com/google/gwt/lang/LongLib.java
+++ b/dev/core/super/com/google/gwt/lang/LongLib.java
@@ -679,6 +679,7 @@
   /**
    * Web mode implementation; the long is already the right object.
    */
+  @SuppressWarnings("restriction")
   private static native double[] typeChange0(long value) /*-{
     return value;
   }-*/;
diff --git a/dev/core/test/com/google/gwt/dev/jdt/LongFromJSNITest.java b/dev/core/test/com/google/gwt/dev/jdt/LongFromJSNITest.java
new file mode 100644
index 0000000..faa5885
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jdt/LongFromJSNITest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jdt;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.dev.util.UnitTestTreeLogger;
+
+import junit.framework.TestCase;
+
+/**
+ * Test access to longs from JSNI.
+ */
+public class LongFromJSNITest extends TestCase {
+  public void testFieldAccess() 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("  $wnd.alert(\"x is: \"+this.@Buggy::x); }-*/;\n");
+    code.append("}\n");
+
+    shouldGenerateWarning(code, 3,
+        "Field x has type long, which cannot be processed in JSNI code");
+  }
+
+  public void testLongArray() throws UnableToCompleteException {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  long[] m() { return new long[] { -1 }; }\n");
+    code.append("  native void jsniMeth() /*-{\n");
+    code.append("    $wnd.alert(this.@Buggy::m()()); }-*/;\n");
+    code.append("}\n");
+
+    shouldGenerateWarning(code, 3,
+        "Method m returns type long[], which cannot be processed in JSNI code");
+  }
+
+  public void testLongParameter() throws UnableToCompleteException {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  native void jsniMeth(long x) /*-{ return; }-*/;\n");
+    code.append("}\n");
+
+    shouldGenerateWarning(code, 2, "JSNI method with a parameter of type long");
+  }
+
+  public void testLongReturn() throws UnableToCompleteException {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  native long jsniMeth() /*-{ return 0; }-*/;\n");
+    code.append("}\n");
+
+    shouldGenerateWarning(code, 2, "JSNI method with return type of long");
+  }
+
+  public void testMethodArgument() throws UnableToCompleteException {
+    StringBuffer code = new StringBuffer();
+    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");
+
+    shouldGenerateWarning(code, 3,
+        "Passing a long into Java from JSNI (method print)");
+  }
+
+  public void testMethodReturn() throws UnableToCompleteException {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  long m() { return -1; }\n");
+    code.append("  native void jsniMeth() /*-{\n");
+    code.append("    $wnd.alert(this.@Buggy::m()()); }-*/;\n");
+    code.append("}\n");
+
+    shouldGenerateWarning(code, 3,
+        "Method m returns type long, which cannot be processed in JSNI code");
+  }
+
+  public void testOverloadedMethodWithNoWarning()
+      throws UnableToCompleteException {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  long m(int x) { return -1; }\n");
+    code.append("  int m(String x) { return -1; }\n");
+    code.append("  native void jsniMeth() /*-{\n");
+    code.append("    $wnd.alert(this.@Buggy::m(Ljava/lang/String;)(\"hello\")); }-*/;\n");
+    code.append("}\n");
+
+    shouldGenerateNoWarning(code);
+  }
+
+  public void testOverloadedMethodWithWarning()
+      throws UnableToCompleteException {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  long m(int x) { return -1; }\n");
+    code.append("  int m(String x) { return -1; }\n");
+    code.append("  native void jsniMeth() /*-{\n");
+    code.append("    $wnd.alert(this.@Buggy::m(I)(10)); }-*/;\n");
+    code.append("}\n");
+
+    shouldGenerateWarning(code, 4,
+        "Method m returns type long, which cannot be processed in JSNI code");
+  }
+
+  public void testSuppressWarnings() throws UnableToCompleteException {
+    {
+      StringBuffer code = new StringBuffer();
+      code.append("class Buggy {\n");
+      code.append("  void print(long x) { }\n");
+      code.append("  @SuppressWarnings(\"restriction\")\n");
+      code.append("  native void jsniMeth() /*-{ this.@Buggy::print(J)(0); }-*/;\n");
+      code.append("}\n");
+
+      shouldGenerateNoWarning(code);
+    }
+
+    {
+      StringBuffer code = new StringBuffer();
+      code.append("@SuppressWarnings(\"restriction\")\n");
+      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");
+
+      shouldGenerateNoWarning(code);
+    }
+  }
+
+  private void shouldGenerateNoWarning(StringBuffer code)
+      throws UnableToCompleteException {
+    shouldGenerateWarning(code, -1, null);
+  }
+
+  private void shouldGenerateWarning(CharSequence code, int line, String message)
+      throws UnableToCompleteException {
+    Type logType = TreeLogger.WARN;
+    UnitTestTreeLogger.Builder b = new UnitTestTreeLogger.Builder();
+    b.setLowestLogLevel(logType);
+    if (message != null) {
+      final String fullMessage = "transient source for Buggy(" + line + "): "
+          + message;
+      b.expect(logType, fullMessage, null);
+    }
+    UnitTestTreeLogger logger = b.createLogger();
+    TypeOracleTestingUtils.buildTypeOracleForCode("Buggy", code, logger);
+    logger.assertCorrectLogEntries();
+  }
+}
diff --git a/user/test/com/google/gwt/dev/jjs/test/HostedTest.java b/user/test/com/google/gwt/dev/jjs/test/HostedTest.java
index 6359842..d5409e5 100644
--- a/user/test/com/google/gwt/dev/jjs/test/HostedTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/HostedTest.java
@@ -148,6 +148,7 @@
     return val;
   }-*/;
 
+  @SuppressWarnings("restriction")
   private static native long passThroughLong(long val) /*-{
     return val;
   }-*/;