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,