Improvements to JsniChecker.

- JsniChecker now implements ALL of the JSNI checks that were being done in GenerateJavaAST.
- JsniCheckerTest tests them all.
- JsniChecker reports the resolved refs it collected.
- Tighter type on jsni method map.

http://gwt-code-reviews.appspot.com/1333801/show

Review by: robertvawter@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9653 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java b/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
index 5f5c9c3..8926cc7 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
@@ -31,8 +31,9 @@
 import org.apache.commons.collections.map.ReferenceIdentityMap;
 import org.apache.commons.collections.map.ReferenceMap;
 import org.eclipse.jdt.core.compiler.CharOperation;
-import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+import org.eclipse.jdt.internal.compiler.lookup.Binding;
 import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
 
 import java.util.ArrayList;
@@ -44,8 +45,8 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.Map.Entry;
+import java.util.Set;
 
 /**
  * Manages a centralized cache for compiled units.
@@ -61,22 +62,25 @@
 
       public void process(CompilationUnitBuilder builder,
           CompilationUnitDeclaration cud, List<CompiledClass> compiledClasses) {
-        Map<AbstractMethodDeclaration, JsniMethod> jsniMethods = JsniCollector.collectJsniMethods(
+        Map<MethodDeclaration, JsniMethod> jsniMethods = JsniCollector.collectJsniMethods(
             cud, builder.getSource(), jsProgram);
 
+        JSORestrictionsChecker.check(jsoState, cud);
+
         // JSNI check + collect dependencies.
         final Set<String> jsniDeps = new HashSet<String>();
-        JsniChecker.check(cud, jsniMethods, new JsniChecker.TypeResolver() {
-          public ReferenceBinding resolveType(String typeName) {
-            ReferenceBinding resolveType = compiler.resolveType(typeName);
-            if (resolveType != null) {
-              jsniDeps.add(String.valueOf(resolveType.qualifiedSourceName()));
-            }
-            return resolveType;
-          }
-        });
+        Map<String, Binding> jsniRefs = new HashMap<String, Binding>();
+        JsniChecker.check(cud, jsoState, jsniMethods, jsniRefs,
+            new JsniChecker.TypeResolver() {
+              public ReferenceBinding resolveType(String typeName) {
+                ReferenceBinding resolveType = compiler.resolveType(typeName);
+                if (resolveType != null) {
+                  jsniDeps.add(String.valueOf(resolveType.qualifiedSourceName()));
+                }
+                return resolveType;
+              }
+            });
 
-        JSORestrictionsChecker.check(jsoState, cud);
         ArtificialRescueChecker.check(cud, builder.isGenerated());
         BinaryTypeReferenceRestrictionsChecker.check(cud);
 
diff --git a/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java b/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java
index 9833fbb..37350b8 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java
@@ -32,6 +32,7 @@
 import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
 import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
 import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
+import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
 import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
 
 import java.util.HashMap;
@@ -65,17 +66,27 @@
     private final Map<String, String> interfacesToJsoImpls = new HashMap<String, String>();
 
     public void addJsoInterface(TypeDeclaration jsoType,
-        CompilationUnitDeclaration cud, String interfaceName) {
-      String alreadyImplementor = interfacesToJsoImpls.get(interfaceName);
+        CompilationUnitDeclaration cud, ReferenceBinding interf) {
+      String intfName = CharOperation.toString(interf.compoundName);
+      String alreadyImplementor = interfacesToJsoImpls.get(intfName);
       String myName = CharOperation.toString(jsoType.binding.compoundName);
       if (alreadyImplementor == null) {
-        interfacesToJsoImpls.put(interfaceName, myName);
+        interfacesToJsoImpls.put(intfName, myName);
       } else {
-        String msg = errAlreadyImplemented(interfaceName, alreadyImplementor,
-            myName);
+        String msg = errAlreadyImplemented(intfName, alreadyImplementor, myName);
         errorOn(jsoType, cud, msg);
       }
     }
+
+    public String getJsoImplementor(ReferenceBinding binding) {
+      String name = CharOperation.toString(binding.compoundName);
+      return interfacesToJsoImpls.get(name);
+    }
+
+    public boolean isJsoInterface(ReferenceBinding binding) {
+      String name = CharOperation.toString(binding.compoundName);
+      return interfacesToJsoImpls.containsKey(name);
+    }
   }
 
   private class JSORestrictionsVisitor extends SafeASTVisitor implements
@@ -183,15 +194,16 @@
     }
 
     private boolean checkType(TypeDeclaration type) {
-      if (!isJsoSubclass(type.binding)) {
+      SourceTypeBinding binding = type.binding;
+      if (!isJsoSubclass(binding)) {
         return false;
       }
 
-      if (type.enclosingType != null && !type.binding.isStatic()) {
+      if (type.enclosingType != null && !binding.isStatic()) {
         errorOn(type, ERR_IS_NONSTATIC_NESTED);
       }
 
-      ReferenceBinding[] interfaces = type.binding.superInterfaces();
+      ReferenceBinding[] interfaces = binding.superInterfaces();
       if (interfaces != null) {
         for (ReferenceBinding interf : interfaces) {
           if (interf.methods() == null) {
@@ -200,11 +212,10 @@
 
           if (interf.methods().length > 0) {
             // See if any of my superTypes implement it.
-            ReferenceBinding superclass = type.binding.superclass();
+            ReferenceBinding superclass = binding.superclass();
             if (superclass == null
                 || !superclass.implementsInterface(interf, true)) {
-              String intfName = CharOperation.toString(interf.compoundName);
-              state.addJsoInterface(type, cud, intfName);
+              state.addJsoInterface(type, cud, interf);
             }
           }
         }
@@ -253,6 +264,36 @@
         + ") is implemented by both (" + impl1 + ") and (" + impl2 + ")";
   }
 
+  /**
+   * Returns {@code true} if {@code typeBinding} is {@code JavaScriptObject} or
+   * any subtype.
+   */
+  static boolean isJso(TypeBinding typeBinding) {
+    if (!(typeBinding instanceof ReferenceBinding)) {
+      return false;
+    }
+    ReferenceBinding binding = (ReferenceBinding) typeBinding;
+    while (binding != null) {
+      if (JSO_CLASS.equals(String.valueOf(binding.constantPoolName()))) {
+        return true;
+      }
+      binding = binding.superclass();
+    }
+    return false;
+  }
+
+  /**
+   * Returns {@code true} if {@code typeBinding} is a subtype of
+   * {@code JavaScriptObject}, but not {@code JavaScriptObject} itself.
+   */
+  static boolean isJsoSubclass(TypeBinding typeBinding) {
+    if (!(typeBinding instanceof ReferenceBinding)) {
+      return false;
+    }
+    ReferenceBinding binding = (ReferenceBinding) typeBinding;
+    return isJso(binding.superclass());
+  }
+
   private static void errorOn(ASTNode node, CompilationUnitDeclaration cud,
       String error) {
     GWTProblem.recordError(node, cud, error, new InstalledHelpInfo(
@@ -276,18 +317,4 @@
   private void errorOn(ASTNode node, String error) {
     errorOn(node, cud, error);
   }
-
-  private boolean isJsoSubclass(TypeBinding typeBinding) {
-    if (!(typeBinding instanceof ReferenceBinding)) {
-      return false;
-    }
-    ReferenceBinding binding = (ReferenceBinding) typeBinding;
-    while (binding.superclass() != null) {
-      if (JSO_CLASS.equals(String.valueOf(binding.superclass().constantPoolName()))) {
-        return true;
-      }
-      binding = binding.superclass();
-    }
-    return false;
-  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java b/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java
index c2add7d..d637fc3 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java
@@ -16,6 +16,7 @@
 package com.google.gwt.dev.javac;
 
 import com.google.gwt.core.client.UnsafeNativeLong;
+import com.google.gwt.dev.javac.JSORestrictionsChecker.CheckerState;
 import com.google.gwt.dev.jdt.SafeASTVisitor;
 import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.jjs.SourceInfo;
@@ -30,7 +31,6 @@
 
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.ast.ASTNode;
-import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.Annotation;
 import org.eclipse.jdt.internal.compiler.ast.Argument;
 import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
@@ -42,7 +42,9 @@
 import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.TypeReference;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.impl.Constant;
 import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.Binding;
 import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
 import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
 import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
@@ -194,57 +196,94 @@
         if (jsniRef == null) {
           emitError("Malformed JSNI identifier '" + ident + "'");
         } else {
-          checkRef(jsniRef);
+          Binding binding = checkRef(jsniRef, x.getQualifier() != null,
+              ctx.isLvalue());
+          if (binding != null) {
+            jsniRefs.put(ident, binding);
+          }
         }
       }
       this.errorInfo = null;
     }
 
-    private void checkFieldRef(ReferenceBinding clazz, JsniRef jsniRef) {
+    private FieldBinding checkFieldRef(ReferenceBinding clazz, JsniRef jsniRef,
+        boolean hasQualifier, boolean isLvalue) {
       assert jsniRef.isField();
-      if ("class".equals(jsniRef.memberName())) {
-        return;
-      }
       FieldBinding target = getField(clazz, jsniRef);
       if (target == null) {
-        emitError("JSNI Referencing field '" + jsniRef.className() + "."
-            + jsniRef.memberName()
-            + "': unable to resolve field, expect subsequent failures");
-        return;
+        emitError("Referencing field '" + jsniRef.className() + "."
+            + jsniRef.memberName() + "': unable to resolve field");
+        return null;
       }
       if (target.isDeprecated()) {
         emitWarning("deprecation",
             "Referencing deprecated field '" + jsniRef.className() + "."
                 + jsniRef.memberName() + "'");
       }
+      if (isLvalue && target.constant() != Constant.NotAConstant) {
+        emitError("Illegal assignment to compile-time constant '"
+            + jsniRef.className() + "." + jsniRef.memberName() + "'");
+      }
+      if (target.isStatic() && hasQualifier) {
+        emitError("Unnecessary qualifier on static field '"
+            + jsniRef.className() + "." + jsniRef.memberName() + "'");
+      } else if (!target.isStatic() && !hasQualifier) {
+        emitError("Missing qualifier on instance field '" + jsniRef.className()
+            + "." + jsniRef.memberName() + "'");
+      }
 
       if (hasUnsafeLongsAnnotation) {
-        return;
+        return target;
       }
       if (containsLong(target.type)) {
         emitError("Referencing field '" + jsniRef.className() + "."
             + jsniRef.memberName() + "': type '" + typeString(target.type)
             + "' is not safe to access in JSNI code");
       }
+      return target;
     }
 
-    private void checkMethodRef(ReferenceBinding clazz, JsniRef jsniRef) {
+    private MethodBinding checkMethodRef(ReferenceBinding clazz,
+        JsniRef jsniRef, boolean hasQualifier, boolean isLvalue) {
       assert jsniRef.isMethod();
       MethodBinding target = getMethod(clazz, jsniRef);
       if (target == null) {
-        emitError("JSNI Referencing method '" + jsniRef.className() + "."
-            + jsniRef.memberSignature()
-            + "': unable to resolve method, expect subsequent failures");
-        return;
+        emitError("Referencing method '" + jsniRef.className() + "."
+            + jsniRef.memberSignature() + "': unable to resolve method");
+        return null;
       }
       if (target.isDeprecated()) {
         emitWarning("deprecation",
             "Referencing deprecated method '" + jsniRef.className() + "."
                 + jsniRef.memberName() + "'");
       }
+      if (isLvalue) {
+        emitError("Illegal assignment to method '" + jsniRef.className() + "."
+            + jsniRef.memberName() + "'");
+      }
+      boolean needsQualifer = !target.isStatic() && !target.isConstructor();
+      if (!needsQualifer && hasQualifier) {
+        emitError("Unnecessary qualifier on static method '"
+            + jsniRef.className() + "." + jsniRef.memberName() + "'");
+      } else if (needsQualifer && !hasQualifier) {
+        emitError("Missing qualifier on instance method '"
+            + jsniRef.className() + "." + jsniRef.memberName() + "'");
+      }
+      if (!target.isStatic() && JSORestrictionsChecker.isJso(clazz)) {
+        emitError("Referencing method '" + jsniRef.className() + "."
+            + jsniRef.memberSignature()
+            + "': references to instance methods in overlay types are illegal");
+      }
+      if (checkerState.isJsoInterface(clazz)) {
+        String implementor = checkerState.getJsoImplementor(clazz);
+        emitError("Referencing interface method '" + jsniRef.className() + "."
+            + jsniRef.memberSignature() + "': implemented by '" + implementor
+            + "'; references to instance methods in overlay types are illegal"
+            + "; use a stronger type or a Java trampoline method");
+      }
 
       if (hasUnsafeLongsAnnotation) {
-        return;
+        return target;
       }
       if (containsLong(target.returnType)) {
         emitError("Referencing method '" + jsniRef.className() + "."
@@ -266,26 +305,47 @@
           }
         }
       }
+      return target;
     }
 
-    private void checkRef(JsniRef jsniRef) {
+    private Binding checkRef(JsniRef jsniRef, boolean hasQualifier,
+        boolean isLvalue) {
       String className = jsniRef.className();
       if ("null".equals(className)) {
-        return;
+        if (jsniRef.isField()) {
+          if (!"nullField".equals(jsniRef.memberName())) {
+            emitError("Referencing field '" + jsniRef.className() + "."
+                + jsniRef.memberName()
+                + "': 'nullField' is the only legal field reference for 'null'");
+          }
+        } else {
+          if (!"nullMethod()".equals(jsniRef.memberSignature())) {
+            emitError("Referencing method '" + jsniRef.className() + "."
+                + jsniRef.memberSignature()
+                + "': 'nullMethod()' is the only legal method for 'null'");
+          }
+          return null;
+        }
+        return null;
       }
 
       boolean isArray = false;
+      int dims = 0;
       while (className.endsWith("[]")) {
+        ++dims;
         isArray = true;
         className = className.substring(0, className.length() - 2);
       }
 
-      boolean isPrimitive = false;
+      boolean isPrimitive;
+      ReferenceBinding clazz;
       TypeBinding binding = method.scope.getBaseType(className.toCharArray());
       if (binding != null) {
         isPrimitive = true;
+        clazz = null;
       } else {
-        binding = findClass(className);
+        isPrimitive = false;
+        binding = clazz = findClass(className);
       }
 
       // TODO(deprecation): remove this support eventually.
@@ -302,40 +362,49 @@
                 + String.valueOf(binding.sourceName()) + "' instead");
       }
 
-      if (isArray || isPrimitive) {
-        if (!jsniRef.isField() || !jsniRef.memberName().equals("class")) {
-          emitError("Referencing member '" + jsniRef.className() + "."
-              + jsniRef.memberName()
-              + "': 'class' is the only legal reference for "
-              + (isArray ? "array" : "primitive") + " types");
-          return;
-        }
-      }
-
-      if (isPrimitive) {
-        return;
-      }
-
-      if (looksLikeAnonymousClass(jsniRef)
+      if ((binding == null && looksLikeAnonymousClass(jsniRef))
           || (binding != null && binding.isAnonymousType())) {
         emitError("Referencing class '" + className
-            + ": JSNI references to anonymous classes are illegal");
-        return;
+            + "': JSNI references to anonymous classes are illegal");
+        return null;
       } else if (binding == null) {
-        emitError("JSNI Referencing class '" + className
-            + "': unable to resolve class, expect subsequent failures");
-        return;
+        emitError("Referencing class '" + className
+            + "': unable to resolve class");
+        return null;
       }
-      ReferenceBinding clazz = (ReferenceBinding) binding;
-      if (clazz.isDeprecated()) {
+
+      if (clazz != null && clazz.isDeprecated()) {
         emitWarning("deprecation", "Referencing deprecated class '" + className
             + "'");
       }
 
+      if (jsniRef.isField() && "class".equals(jsniRef.memberName())) {
+        if (isLvalue) {
+          emitError("Illegal assignment to class literal '"
+              + jsniRef.className() + ".class'");
+          return null;
+        }
+        // Reference to the class itself.
+        if (isArray) {
+          return method.scope.createArrayType(binding, dims);
+        } else {
+          return binding;
+        }
+      }
+
+      if (isArray || isPrimitive) {
+        emitError("Referencing member '" + jsniRef.className() + "."
+            + jsniRef.memberName()
+            + "': 'class' is the only legal reference for "
+            + (isArray ? "array" : "primitive") + " types");
+        return null;
+      }
+
+      assert clazz != null;
       if (jsniRef.isMethod()) {
-        checkMethodRef(clazz, jsniRef);
+        return checkMethodRef(clazz, jsniRef, hasQualifier, isLvalue);
       } else {
-        checkFieldRef(clazz, jsniRef);
+        return checkFieldRef(clazz, jsniRef, hasQualifier, isLvalue);
       }
     }
 
@@ -485,9 +554,10 @@
    * 
    */
   public static void check(CompilationUnitDeclaration cud,
-      Map<AbstractMethodDeclaration, JsniMethod> jsniMethods,
-      TypeResolver typeResolver) {
-    new JsniChecker(cud, typeResolver, jsniMethods).check();
+      CheckerState checkerState,
+      Map<MethodDeclaration, JsniMethod> jsniMethods,
+      Map<String, Binding> jsniRefs, TypeResolver typeResolver) {
+    new JsniChecker(cud, checkerState, typeResolver, jsniMethods, jsniRefs).check();
   }
 
   static Set<String> getSuppressedWarnings(Annotation[] annotations) {
@@ -523,17 +593,22 @@
     return Sets.create();
   }
 
+  private final CheckerState checkerState;
   private final CompilationUnitDeclaration cud;
-  private final Map<AbstractMethodDeclaration, JsniMethod> jsniMethods;
+  private final Map<MethodDeclaration, JsniMethod> jsniMethods;
+  private final Map<String, Binding> jsniRefs;
   private final Stack<Set<String>> suppressWarningsStack = new Stack<Set<String>>();
   private final TypeResolver typeResolver;
 
   private JsniChecker(CompilationUnitDeclaration cud,
-      TypeResolver typeResolver,
-      Map<AbstractMethodDeclaration, JsniMethod> jsniMethods) {
+      CheckerState checkerState, TypeResolver typeResolver,
+      Map<MethodDeclaration, JsniMethod> jsniMethods,
+      Map<String, Binding> jsniRefs) {
+    this.checkerState = checkerState;
     this.cud = cud;
     this.typeResolver = typeResolver;
     this.jsniMethods = jsniMethods;
+    this.jsniRefs = jsniRefs;
   }
 
   private void check() {
diff --git a/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java b/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java
index 9572448..9decb5a 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java
@@ -20,7 +20,6 @@
 import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.jjs.SourceOrigin;
-import com.google.gwt.dev.jjs.ast.JAnnotation;
 import com.google.gwt.dev.js.JsParser;
 import com.google.gwt.dev.js.JsParserException;
 import com.google.gwt.dev.js.JsParserException.SourceDetail;
@@ -131,7 +130,6 @@
         return false;
       }
       for (Annotation a : method.annotations) {
-        JAnnotation annotation;
         ReferenceBinding binding = (ReferenceBinding) a.resolvedType;
         String name = CharOperation.toString(binding.compoundName);
         if (name.equals(GwtScriptOnly.class.getName())) {
@@ -141,12 +139,12 @@
       return false;
     }
 
-    private final Map<AbstractMethodDeclaration, JsniMethod> jsniMethods;
+    private final Map<MethodDeclaration, JsniMethod> jsniMethods;
     private final JsProgram jsProgram;
     private final String source;
 
     public Visitor(String source, JsProgram program,
-        Map<AbstractMethodDeclaration, JsniMethod> jsniMethods) {
+        Map<MethodDeclaration, JsniMethod> jsniMethods) {
       this.jsProgram = program;
       this.jsniMethods = jsniMethods;
       this.source = source;
@@ -159,14 +157,13 @@
 
     @Override
     protected void processMethod(TypeDeclaration typeDecl,
-        AbstractMethodDeclaration method, String enclosingType,
-        String loc) {
+        AbstractMethodDeclaration method, String enclosingType, String loc) {
       JsFunction jsFunction = parseJsniFunction(method, source, enclosingType,
           loc, jsProgram);
       if (jsFunction != null) {
         String jsniSignature = getJsniSignature(enclosingType, method);
-        jsniMethods.put(method, new JsniMethodImpl(jsniSignature,
-            jsFunction, isScriptOnly(method)));
+        jsniMethods.put((MethodDeclaration) method, new JsniMethodImpl(
+            jsniSignature, jsFunction, isScriptOnly(method)));
       }
     }
   }
@@ -175,10 +172,10 @@
 
   public static final String JSNI_BLOCK_START = "/*-{";
 
-  public static Map<AbstractMethodDeclaration, JsniMethod> collectJsniMethods(
+  public static Map<MethodDeclaration, JsniMethod> collectJsniMethods(
       final CompilationUnitDeclaration cud, final String source,
       final JsProgram program) {
-    Map<AbstractMethodDeclaration, JsniMethod> jsniMethods = new IdentityHashMap<AbstractMethodDeclaration, JsniMethod>();
+    Map<MethodDeclaration, JsniMethod> jsniMethods = new IdentityHashMap<MethodDeclaration, JsniMethod>();
     new Visitor(source, program, jsniMethods).collect(cud);
     return IdentityMaps.normalizeUnmodifiable(jsniMethods);
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
index e797417..38a03fd 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
@@ -2985,31 +2985,12 @@
 
       private void processField(JsNameRef nameRef, SourceInfo info,
           JField field, JsContext<JsExpression> ctx) {
-        if (field.getEnclosingType() != null) {
-          if (field.isStatic() && nameRef.getQualifier() != null) {
-            JsniCollector.reportJsniError(info, methodDecl,
-                "Cannot make a qualified reference to the static field "
-                    + field.getName());
-          } else if (!field.isStatic() && nameRef.getQualifier() == null) {
-            JsniCollector.reportJsniError(info, methodDecl,
-                "Cannot make an unqualified reference to the instance field "
-                    + field.getName());
-          }
-        }
-
         /*
          * We must replace any compile-time constants with the constant value of
          * the field.
          */
         if (field.isCompileTimeConstant()) {
-          if (ctx.isLvalue()) {
-            JsniCollector.reportJsniError(
-                info,
-                methodDecl,
-                "Cannot change the value of compile-time constant "
-                    + field.getName());
-          }
-
+          assert !ctx.isLvalue();
           JLiteral initializer = field.getConstInitializer();
           JType type = initializer.getType();
           if (type instanceof JPrimitiveType || program.isJavaLangString(type)) {
@@ -3031,43 +3012,7 @@
 
       private void processMethod(JsNameRef nameRef, SourceInfo info,
           JMethod method, JsContext<JsExpression> ctx) {
-        JDeclaredType enclosingType = method.getEnclosingType();
-        if (enclosingType != null) {
-          JClassType jsoImplType = program.typeOracle.getSingleJsoImpl(enclosingType);
-          if (jsoImplType != null) {
-            JsniCollector.reportJsniError(info, methodDecl,
-                "Illegal reference to method '" + method.getName()
-                    + "' in type '" + enclosingType.getName()
-                    + "', which is implemented by an overlay type '"
-                    + jsoImplType.getName()
-                    + "'. Use a stronger type in the JSNI "
-                    + "identifier or a Java trampoline method.");
-          } else if (!method.needsVtable() && nameRef.getQualifier() != null) {
-            JsniCollector.reportJsniError(info, methodDecl,
-                "Cannot make a qualified reference to the static method "
-                    + method.getName());
-          } else if (method.needsVtable() && nameRef.getQualifier() == null
-              && !(method instanceof JConstructor)) {
-            JsniCollector.reportJsniError(info, methodDecl,
-                "Cannot make an unqualified reference to the instance method "
-                    + method.getName());
-          } else if (!method.isStatic()
-              && program.isJavaScriptObject(enclosingType)) {
-            JsniCollector.reportJsniError(
-                info,
-                methodDecl,
-                "Illegal reference to instance method '"
-                    + method.getName()
-                    + "' in type '"
-                    + enclosingType.getName()
-                    + "', which is an overlay type; only static references to overlay types are allowed from JSNI");
-          }
-        }
-        if (ctx.isLvalue()) {
-          JsniCollector.reportJsniError(info, methodDecl,
-              "Cannot reassign the Java method " + method.getName());
-        }
-
+        assert !ctx.isLvalue();
         JsniMethodRef methodRef = new JsniMethodRef(info, nameRef.getIdent(),
             method, program.getJavaScriptObject());
         nativeMethodBody.addJsniRef(methodRef);
diff --git a/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java b/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java
index b4006c0..004a667 100644
--- a/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java
@@ -36,7 +36,7 @@
     code.append("  }-*/;\n");
     code.append("}\n");
 
-    shouldGenerateError(code, 8, "Referencing class \'Buggy$1: "
+    shouldGenerateError(code, 8, "Referencing class 'Buggy$1': "
         + "JSNI references to anonymous classes are illegal");
   }
 
@@ -58,7 +58,7 @@
     code.append("  }-*/;\n");
     code.append("}\n");
 
-    shouldGenerateError(code, 10, "Referencing class \'Buggy$1.A: "
+    shouldGenerateError(code, 10, "Referencing class 'Buggy$1.A': "
         + "JSNI references to anonymous classes are illegal");
   }
 
@@ -85,6 +85,27 @@
     shouldGenerateNoWarning(code);
   }
 
+  public void testClass() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    @Buggy::class;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateNoWarning(code);
+  }
+
+  public void testClassAssignment() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    @Buggy::class = null;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateError(code, 3,
+        "Illegal assignment to class literal 'Buggy.class'");
+  }
+
   public void testCyclicReferences() {
     {
       StringBuffer buggy = new StringBuffer();
@@ -134,7 +155,7 @@
   public void testDeprecationField() {
     StringBuffer code = new StringBuffer();
     code.append("class Buggy {\n");
-    code.append("  @Deprecated int bar;\n");
+    code.append("  @Deprecated static int bar;\n");
     code.append("  native void jsniMethod() /*-{\n");
     code.append("    @Buggy::bar;\n");
     code.append("  }-*/;\n");
@@ -146,7 +167,7 @@
   public void testDeprecationMethod() {
     StringBuffer code = new StringBuffer();
     code.append("class Buggy {\n");
-    code.append("  @Deprecated void foo(){}\n");
+    code.append("  @Deprecated static void foo(){}\n");
     code.append("  native void jsniMethod() /*-{\n");
     code.append("    @Buggy::foo();\n");
     code.append("  }-*/;\n");
@@ -158,11 +179,11 @@
   public void testDeprecationSuppression() {
     StringBuffer code = new StringBuffer();
     code.append("@Deprecated class D {\n");
-    code.append("  int bar;\n");
+    code.append("  static int bar;\n");
     code.append("}\n");
     code.append("class Buggy {\n");
-    code.append("  @Deprecated void foo(){}\n");
-    code.append("  @Deprecated int bar;\n");
+    code.append("  @Deprecated static void foo(){}\n");
+    code.append("  @Deprecated static int bar;\n");
     code.append("  @SuppressWarnings(\"deprecation\")\n");
     code.append("  native void jsniMethod1() /*-{\n");
     code.append("    @Buggy::foo();\n");
@@ -181,12 +202,12 @@
     // Check inherited suppress warnings.
     code = new StringBuffer();
     code.append("@Deprecated class D {\n");
-    code.append("  int bar;\n");
+    code.append("  static int bar;\n");
     code.append("}\n");
     code.append("@SuppressWarnings(\"deprecation\")\n");
     code.append("class Buggy {\n");
-    code.append("  @Deprecated void foo(){}\n");
-    code.append("  @Deprecated int bar;\n");
+    code.append("  @Deprecated static void foo(){}\n");
+    code.append("  @Deprecated static int bar;\n");
     code.append("  native void jsniMethod1() /*-{\n");
     code.append("    @Buggy::foo();\n");
     code.append("    @Buggy::bar;\n");
@@ -204,7 +225,7 @@
   public void testDeprecationType() {
     StringBuffer code = new StringBuffer();
     code.append("@Deprecated class D {\n");
-    code.append("  int bar;\n");
+    code.append("  static int bar;\n");
     code.append("}\n");
     code.append("class Buggy {\n");
     code.append("  native void jsniMethod() /*-{\n");
@@ -215,6 +236,17 @@
     shouldGenerateWarning(code, 6, "Referencing deprecated class 'D'");
   }
 
+  public void testField() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  int foo = 3;\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    this.@Buggy::foo;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateNoWarning(code);
+  }
+
   public void testFieldAccess() {
     StringBuffer code = new StringBuffer();
     code.append("class Buggy {\n");
@@ -227,11 +259,185 @@
         "Referencing field 'Buggy.x': type 'long' is not safe to access in JSNI code");
   }
 
+  public void testFieldAssignment() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  int foo = 3;\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    this.@Buggy::foo = 4;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateNoWarning(code);
+  }
+
+  public void testFieldAssignmentStatic() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  static int foo = 3;\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    @Buggy::foo = 4;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateNoWarning(code);
+  }
+
+  public void testFieldConstant() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  static final int foo = 3;\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    @Buggy::foo;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateNoWarning(code);
+  }
+
+  public void testFieldConstantAssignment() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  static final int foo = 3;\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    @Buggy::foo = 4;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateError(code, 4,
+        "Illegal assignment to compile-time constant 'Buggy.foo'");
+
+    code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  static final String foo = \"asdf\";\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    @Buggy::foo = null;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateError(code, 4,
+        "Illegal assignment to compile-time constant 'Buggy.foo'");
+
+    // Not a compile-time constant.
+    code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  static final Object foo = new Object();\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    @Buggy::foo = null;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateNoWarning(code);
+  }
+
+  public void testJsoStaticMethod() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  native void jsniMeth(Object o) /*-{\n");
+    code.append("    @com.google.gwt.core.client.JavaScriptObject::createObject()();\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateNoWarning(code);
+  }
+
+  public void testJsoInstanceMethod() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  native void jsniMeth(Object o) /*-{\n");
+    code.append("    new Object().@com.google.gwt.core.client.JavaScriptObject::toString()();\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateError(
+        code,
+        3,
+        "Referencing method 'com.google.gwt.core.client.JavaScriptObject.toString()': references to instance methods in overlay types are illegal");
+  }
+
+  public void testJsoInterfaceMethod() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  interface IFoo {\n");
+    code.append("    void foo();\n");
+    code.append("  }\n");
+    code.append("  static final class Foo extends com.google.gwt.core.client.JavaScriptObject implements IFoo{\n");
+    code.append("    protected Foo() { };\n");
+    code.append("    public void foo() { };\n");
+    code.append("  }\n");
+    code.append("  native void jsniMeth(Object o) /*-{\n");
+    code.append("    new Object().@Buggy.IFoo::foo()();\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateError(
+        code,
+        10,
+        "Referencing interface method 'Buggy.IFoo.foo()': implemented by 'Buggy$Foo'; references to instance methods in overlay types are illegal; use a stronger type or a Java trampoline method");
+  }
+
+  public void testJsoSubclassInstanceMethod() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  static final class Foo extends com.google.gwt.core.client.JavaScriptObject {\n");
+    code.append("    protected Foo() { };\n");
+    code.append("    void foo() { };\n");
+    code.append("  }\n");
+    code.append("  native void jsniMeth(Object o) /*-{\n");
+    code.append("    new Object().@Buggy.Foo::foo()();\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateError(
+        code,
+        7,
+        "Referencing method 'Buggy.Foo.foo()': references to instance methods in overlay types are illegal");
+  }
+
+  public void testJsoSubclassStaticMethod() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  static final class Foo extends com.google.gwt.core.client.JavaScriptObject {\n");
+    code.append("    protected Foo() { };\n");
+    code.append("    static void foo() { };\n");
+    code.append("  }\n");
+    code.append("  native void jsniMeth(Object o) /*-{\n");
+    code.append("    @Buggy.Foo::foo()();\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateNoWarning(code);
+  }
+
+  public void testFieldStatic() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  static int foo = 3;\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    @Buggy::foo;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateNoWarning(code);
+  }
+
+  public void testFieldStaticQualified() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  static int foo = 3;\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    this.@Buggy::foo;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateError(code, 4,
+        "Unnecessary qualifier on static field 'Buggy.foo'");
+  }
+
+  public void testFieldUnqualified() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  int foo = 3;\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    @Buggy::foo;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateError(code, 4,
+        "Missing qualifier on instance field 'Buggy.foo'");
+  }
+
   public void testInnerClass() {
     StringBuffer code = new StringBuffer();
     code.append("public class Buggy {\n");
     code.append("  static class Inner {\n");
-    code.append("    long x = 3;\n");
+    code.append("    static long x = 3;\n");
     code.append("  }\n");
     code.append("  native void jsniMeth() /*-{\n");
     code.append("    $wnd.alert(@Buggy.Inner::x);\n");
@@ -246,7 +452,7 @@
     StringBuffer code = new StringBuffer();
     code.append("public class Buggy {\n");
     code.append("  static class Inner {\n");
-    code.append("    long x = 3;\n");
+    code.append("    static long x = 3;\n");
     code.append("  }\n");
     code.append("  native void jsniMeth() /*-{\n");
     code.append("    $wnd.alert(@Buggy$Inner::x);\n");
@@ -270,8 +476,8 @@
     code.append("}\n");
 
     // Cannot resolve, missing synthetic enclosing instance.
-    shouldGenerateError(code, 7, "JSNI Referencing method 'Buggy.Inner.new(Z)': "
-        + "unable to resolve method, expect subsequent failures");
+    shouldGenerateError(code, 7, "Referencing method 'Buggy.Inner.new(Z)': "
+        + "unable to resolve method");
 
     code = new StringBuffer();
     code.append("public class Buggy {\n");
@@ -345,6 +551,17 @@
         "Expected \":\" in JSNI reference\n>     @Buggy;\n" + "> ----------^");
   }
 
+  public void testMethod() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  void foo() { }\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    this.@Buggy::foo()();\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateNoWarning(code);
+  }
+
   public void testMethodArgument() {
     StringBuffer code = new StringBuffer();
     code.append("class Buggy {\n");
@@ -355,7 +572,18 @@
     shouldGenerateError(
         code,
         3,
-        "Parameter 1 of method \'Buggy.print\': type 'long' may not be passed out of JSNI code");
+        "Parameter 1 of method 'Buggy.print': type 'long' may not be passed out of JSNI code");
+  }
+
+  public void testMethodAssignment() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  void foo() { }\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    this.@Buggy::foo() = null;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateError(code, 4, "Illegal assignment to method 'Buggy.foo'");
   }
 
   /**
@@ -394,6 +622,41 @@
         "Referencing method 'Buggy.m': return type 'long' is not safe to access in JSNI code");
   }
 
+  public void testMethodStatic() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  static void foo() { }\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    @Buggy::foo()();\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateNoWarning(code);
+  }
+
+  public void testMethodStaticQualified() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  static void foo() { }\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    this.@Buggy::foo()();\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateError(code, 4,
+        "Unnecessary qualifier on static method 'Buggy.foo'");
+  }
+
+  public void testMethodUnqualified() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  void foo() { }\n");
+    code.append("  native void jsniMethod() /*-{\n");
+    code.append("    @Buggy::foo()();\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateError(code, 4,
+        "Missing qualifier on instance method 'Buggy.foo'");
+  }
+
   public void testNew() {
     StringBuffer code = new StringBuffer();
     code.append("class Buggy {\n");
@@ -420,8 +683,18 @@
     code.append("    return @null::nullField;\n");
     code.append("  }-*/;\n");
     code.append("}\n");
-
     shouldGenerateNoWarning(code);
+
+    code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  static native Object main() /*-{\n");
+    code.append("    return @null::foo;\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateError(
+        code,
+        3,
+        "Referencing field 'null.foo': 'nullField' is the only legal field reference for 'null'");
   }
 
   public void testNullMethod() {
@@ -431,8 +704,18 @@
     code.append("    return @null::nullMethod()();\n");
     code.append("  }-*/;\n");
     code.append("}\n");
-
     shouldGenerateNoWarning(code);
+
+    code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  static native Object main() /*-{\n");
+    code.append("    return @null::foo()();\n");
+    code.append("  }-*/;\n");
+    code.append("}\n");
+    shouldGenerateError(
+        code,
+        3,
+        "Referencing method 'null.foo()': 'nullMethod()' is the only legal method for 'null'");
   }
 
   public void testOverloadedMethodWithNoWarning() {
@@ -517,7 +800,7 @@
     code.append("  }-*/;\n");
     code.append("}\n");
     shouldGenerateError(code, 3,
-        "JSNI Referencing class 'Foo': unable to resolve class, expect subsequent failures");
+        "Referencing class 'Foo': unable to resolve class");
   }
 
   public void testUnresolvedField() {
@@ -527,10 +810,8 @@
     code.append("    @Buggy::x;\n");
     code.append("  }-*/;\n");
     code.append("}\n");
-    shouldGenerateError(
-        code,
-        3,
-        "JSNI Referencing field 'Buggy.x': unable to resolve field, expect subsequent failures");
+    shouldGenerateError(code, 3,
+        "Referencing field 'Buggy.x': unable to resolve field");
   }
 
   public void testUnresolvedMethod() {
@@ -540,10 +821,8 @@
     code.append("    @Buggy::x(Ljava/lang/String);\n");
     code.append("  }-*/;\n");
     code.append("}\n");
-    shouldGenerateError(
-        code,
-        3,
-        "JSNI Referencing method 'Buggy.x(Ljava/lang/String)': unable to resolve method, expect subsequent failures");
+    shouldGenerateError(code, 3,
+        "Referencing method 'Buggy.x(Ljava/lang/String)': unable to resolve method");
   }
 
   public void testUnsafeAnnotation() {
diff --git a/dev/core/test/com/google/gwt/dev/javac/JsniCollectorTest.java b/dev/core/test/com/google/gwt/dev/javac/JsniCollectorTest.java
index 37621d4..33d8d61 100644
--- a/dev/core/test/com/google/gwt/dev/javac/JsniCollectorTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/JsniCollectorTest.java
@@ -56,7 +56,7 @@
     assertEquals(3, problem.getSourceLineNumber());
     assertTrue(problem.isError());
     assertEquals(
-        "JSNI Referencing method 'Foo.m(Ljava/lang/String)': unable to resolve method, expect subsequent failures",
+        "Referencing method 'Foo.m(Ljava/lang/String)': unable to resolve method",
         problem.getMessage());
   }
 
diff --git a/dev/core/test/com/google/gwt/dev/javac/impl/JavaResourceBase.java b/dev/core/test/com/google/gwt/dev/javac/impl/JavaResourceBase.java
index ee0efe6..18d7465 100644
--- a/dev/core/test/com/google/gwt/dev/javac/impl/JavaResourceBase.java
+++ b/dev/core/test/com/google/gwt/dev/javac/impl/JavaResourceBase.java
@@ -232,7 +232,9 @@
       StringBuffer code = new StringBuffer();
       code.append("package com.google.gwt.core.client;\n");
       code.append("public class JavaScriptObject {\n");
+      code.append("  public static native JavaScriptObject createObject() /*-{ return {}; }-*/;\n");
       code.append("  protected JavaScriptObject() { }\n");
+      code.append("  public final String toString() { return \"JavaScriptObject\"; }\n");
       code.append("}\n");
       return code;
     }
@@ -396,7 +398,7 @@
   };
 
   public static MockJavaResource[] getStandardResources() {
-    return new MockJavaResource[] {
+    return new MockJavaResource[]{
         ANNOTATION, BYTE, CHARACTER, CLASS, CLASS_NOT_FOUND_EXCEPTION,
         COLLECTION, DOUBLE, ENUM, EXCEPTION, ERROR, FLOAT, INTEGER,
         IS_SERIALIZABLE, JAVASCRIPTOBJECT, LONG, MAP, NO_CLASS_DEF_FOUND_ERROR,