Revision: 9653
Author: [email protected]
Date: Fri Jan 28 10:48:44 2011
Log: 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: [email protected]
http://code.google.com/p/google-web-toolkit/source/detail?r=9653

Modified:
 /trunk/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
 /trunk/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java
 /trunk/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java
 /trunk/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java
 /trunk/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
 /trunk/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java
 /trunk/dev/core/test/com/google/gwt/dev/javac/JsniCollectorTest.java
 /trunk/dev/core/test/com/google/gwt/dev/javac/impl/JavaResourceBase.java

=======================================
--- /trunk/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java Thu Jan 27 12:46:16 2011 +++ /trunk/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java Fri Jan 28 10:48:44 2011
@@ -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;
-          }
-        });
-
-        JSORestrictionsChecker.check(jsoState, cud);
+        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;
+              }
+            });
+
         ArtificialRescueChecker.check(cud, builder.isGenerated());
         BinaryTypeReferenceRestrictionsChecker.check(cud);

=======================================
--- /trunk/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java Fri Jul 9 10:56:34 2010 +++ /trunk/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java Fri Jan 28 10:48:44 2011
@@ -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);
             }
           }
         }
@@ -252,6 +263,36 @@
         + "interface that declared methods. The interface (" + intfName
         + ") 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) {
@@ -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;
-  }
-}
+}
=======================================
--- /trunk/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java Fri Jan 28 06:04:41 2011 +++ /trunk/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java Fri Jan 28 10:48:44 2011
@@ -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;
-      }
-      ReferenceBinding clazz = (ReferenceBinding) binding;
-      if (clazz.isDeprecated()) {
+        emitError("Referencing class '" + className
+            + "': unable to resolve class");
+        return null;
+      }
+
+      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() {
=======================================
--- /trunk/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java Tue Dec 14 13:56:04 2010 +++ /trunk/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java Fri Jan 28 10:48:44 2011
@@ -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);
   }
=======================================
--- /trunk/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java Thu Jan 27 12:46:16 2011 +++ /trunk/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java Fri Jan 28 10:48:44 2011
@@ -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);
=======================================
--- /trunk/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java Fri Jan 28 06:04:41 2011 +++ /trunk/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java Fri Jan 28 10:48:44 2011
@@ -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");
   }

@@ -84,6 +84,27 @@
     code.append("}\n");
     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() {
     {
@@ -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");
@@ -214,6 +235,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();
@@ -226,12 +258,186 @@
     shouldGenerateError(code, 4,
"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()[email protected]::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()[email protected]::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()[email protected]::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");
@@ -344,6 +550,17 @@
     shouldGenerateError(code, 3,
"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();
@@ -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'");
   }

   /**
@@ -393,6 +621,41 @@
         4,
"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();
@@ -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() {
=======================================
--- /trunk/dev/core/test/com/google/gwt/dev/javac/JsniCollectorTest.java Wed Dec 1 13:52:36 2010 +++ /trunk/dev/core/test/com/google/gwt/dev/javac/JsniCollectorTest.java Fri Jan 28 10:48:44 2011
@@ -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());
   }

=======================================
--- /trunk/dev/core/test/com/google/gwt/dev/javac/impl/JavaResourceBase.java Wed Sep 1 14:06:53 2010 +++ /trunk/dev/core/test/com/google/gwt/dev/javac/impl/JavaResourceBase.java Fri Jan 28 10:48:44 2011
@@ -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,

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to