Fixes an NPE in JsniChecker; improves test coverage on JsniCollector and JsniChecker.
Review by: bobv
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7033 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 a8a0238..706a49b 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java
@@ -84,7 +84,9 @@
checkDecl(meth, scope);
}
JsniMethod jsniMethod = jsniMethods.get(meth);
- new JsniRefChecker(meth, hasUnsafeLongsAnnotation).check(jsniMethod.function());
+ if (jsniMethod != null) {
+ new JsniRefChecker(meth, hasUnsafeLongsAnnotation).check(jsniMethod.function());
+ }
}
}
@@ -156,6 +158,9 @@
}
FieldBinding target = getField(clazz, jsniRef);
if (target == null) {
+ emitWarning("jsni", "Referencing field '" + jsniRef.className() + "."
+ + jsniRef.memberName()
+ + "': unable to resolve field, expect subsequent failures");
return;
}
if (target.isDeprecated()) {
@@ -177,6 +182,9 @@
assert jsniRef.isMethod();
MethodBinding target = getMethod(clazz, jsniRef);
if (target == null) {
+ emitWarning("jsni", "Referencing method '" + jsniRef.className() + "."
+ + jsniRef.memberSignature()
+ + "': unable to resolve method, expect subsequent failures");
return;
}
if (target.isDeprecated()) {
@@ -262,7 +270,7 @@
}
} else {
emitWarning("jsni", "Referencing class '" + className
- + ": unable to resolve class, expect subsequent failures");
+ + "': unable to resolve class, expect subsequent failures");
}
}
diff --git a/dev/core/test/com/google/gwt/dev/javac/CompilationStateTestBase.java b/dev/core/test/com/google/gwt/dev/javac/CompilationStateTestBase.java
index b04959b..eff8673 100644
--- a/dev/core/test/com/google/gwt/dev/javac/CompilationStateTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/javac/CompilationStateTestBase.java
@@ -17,7 +17,6 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.dev.javac.impl.JavaResourceBase;
-import com.google.gwt.dev.javac.impl.MockJavaResource;
import com.google.gwt.dev.javac.impl.MockResource;
import com.google.gwt.dev.javac.impl.MockResourceOracle;
import com.google.gwt.dev.resource.Resource;
@@ -48,7 +47,7 @@
boolean reallyLog = false;
if (reallyLog) {
AbstractTreeLogger logger = new PrintWriterTreeLogger();
- logger.setMaxDetail(TreeLogger.ALL);
+ logger.setMaxDetail(TreeLogger.WARN);
return logger;
}
return TreeLogger.NULL;
@@ -102,7 +101,7 @@
protected CompilationState state = isolatedBuilder.doBuildFrom(
createTreeLogger(), oracle.getResources());
- protected void addGeneratedUnits(MockJavaResource... sourceFiles) {
+ protected void addGeneratedUnits(MockResource... sourceFiles) {
state.addGeneratedCompilationUnits(createTreeLogger(),
getGeneratedUnits(sourceFiles));
}
diff --git a/dev/core/test/com/google/gwt/dev/javac/JavaCompilationSuite.java b/dev/core/test/com/google/gwt/dev/javac/JavaCompilationSuite.java
index f7fd44b..411ea46 100644
--- a/dev/core/test/com/google/gwt/dev/javac/JavaCompilationSuite.java
+++ b/dev/core/test/com/google/gwt/dev/javac/JavaCompilationSuite.java
@@ -39,6 +39,7 @@
suite.addTestSuite(JdtCompilerTest.class);
suite.addTestSuite(JSORestrictionsTest.class);
suite.addTestSuite(JsniCheckerTest.class);
+ suite.addTestSuite(JsniCollectorTest.class);
suite.addTestSuite(TypeOracleMediatorTest.class);
suite.addTestSuite(CollectClassDataTest.class);
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 6a00bf8..2e38454 100644
--- a/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java
@@ -62,6 +62,29 @@
+ "JSNI references to anonymous classes are illegal");
}
+ public void testArrayBadMember() {
+ StringBuffer code = new StringBuffer();
+ code.append("class Buggy {\n");
+ code.append(" native void jsniMethod() /*-{\n");
+ code.append(" @Buggy[][]::blah;\n");
+ code.append(" }-*/;\n");
+ code.append("}\n");
+ shouldGenerateError(
+ code,
+ 3,
+ "Referencing member 'Buggy[][].blah': 'class' is the only legal reference for array types");
+ }
+
+ public void testArrayClass() {
+ 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 testCyclicReferences() {
{
StringBuffer buggy = new StringBuffer();
@@ -249,6 +272,16 @@
"Type 'long' may not be returned from a JSNI method");
}
+ public void testMalformedJsniRef() {
+ StringBuffer code = new StringBuffer();
+ code.append("class Buggy {\n");
+ code.append(" native void jsniMethod() /*-{\n");
+ code.append(" @Buggy;\n");
+ code.append(" }-*/;\n");
+ code.append("}\n");
+ shouldGenerateError(code, 3, "Expected \":\" in JSNI reference");
+ }
+
public void testMethodArgument() {
StringBuffer code = new StringBuffer();
code.append("class Buggy {\n");
@@ -325,6 +358,29 @@
"Referencing method 'Buggy.m': return type 'long' is not safe to access in JSNI code");
}
+ public void testPrimitiveBadMember() {
+ StringBuffer code = new StringBuffer();
+ code.append("class Buggy {\n");
+ code.append(" native void jsniMethod() /*-{\n");
+ code.append(" @Z::blah;\n");
+ code.append(" }-*/;\n");
+ code.append("}\n");
+ shouldGenerateError(
+ code,
+ 3,
+ "Referencing member 'Z.blah': 'class' is the only legal reference for primitive types");
+ }
+
+ public void testPrimitiveClass() {
+ StringBuffer code = new StringBuffer();
+ code.append("class Buggy {\n");
+ code.append(" native void jsniMethod() /*-{\n");
+ code.append(" @Z::class;\n");
+ code.append(" }-*/;\n");
+ code.append("}\n");
+ shouldGenerateNoWarning(code);
+ }
+
public void testRefInString() {
{
StringBuffer code = new StringBuffer();
@@ -338,6 +394,43 @@
}
}
+ public void testUnresolvedClass() {
+ StringBuffer code = new StringBuffer();
+ code.append("class Buggy {\n");
+ code.append(" native void jsniMethod() /*-{\n");
+ code.append(" @Foo::x;\n");
+ code.append(" }-*/;\n");
+ code.append("}\n");
+ shouldGenerateWarning(code, 3,
+ "Referencing class 'Foo': unable to resolve class, expect subsequent failures");
+ }
+
+ public void testUnresolvedField() {
+ StringBuffer code = new StringBuffer();
+ code.append("class Buggy {\n");
+ code.append(" native void jsniMethod() /*-{\n");
+ code.append(" @Buggy::x;\n");
+ code.append(" }-*/;\n");
+ code.append("}\n");
+ shouldGenerateWarning(
+ code,
+ 3,
+ "Referencing field 'Buggy.x': unable to resolve field, expect subsequent failures");
+ }
+
+ public void testUnresolvedMethod() {
+ StringBuffer code = new StringBuffer();
+ code.append("class Buggy {\n");
+ code.append(" native void jsniMethod() /*-{\n");
+ code.append(" @Buggy::x(Ljava/lang/String);\n");
+ code.append(" }-*/;\n");
+ code.append("}\n");
+ shouldGenerateWarning(
+ code,
+ 3,
+ "Referencing method 'Buggy.x(Ljava/lang/String)': unable to resolve method, expect subsequent failures");
+ }
+
public void testUnsafeAnnotation() {
{
StringBuffer code = new StringBuffer();
diff --git a/dev/core/test/com/google/gwt/dev/javac/JsniCollectorTest.java b/dev/core/test/com/google/gwt/dev/javac/JsniCollectorTest.java
new file mode 100644
index 0000000..fbe0105
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/JsniCollectorTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.javac;
+
+import com.google.gwt.dev.javac.impl.StaticJavaResource;
+import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsExpression;
+import com.google.gwt.dev.js.ast.JsNameRef;
+import com.google.gwt.dev.js.ast.JsVisitor;
+
+import org.eclipse.jdt.core.compiler.CategorizedProblem;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests {@link JsniCollector}.
+ */
+public class JsniCollectorTest extends CompilationStateTestBase {
+
+ /**
+ * TODO: currently JSNI does not parse character position. Turn this on (and
+ * delete it, actually) when it does.
+ */
+ public static final boolean JSNI_PARSES_SOURCE_POSITION = false;
+
+ public void testErrorPosition() {
+ StringBuffer code = new StringBuffer();
+ code.append("class Foo {\n");
+ code.append(" native void m(Object o) /*-{\n");
+ code.append(" o.@Foo::m(Ljava/lang/String);\n");
+ code.append(" }-*/;\n");
+ code.append("}\n");
+ String source = code.toString();
+
+ CategorizedProblem[] problems = getProblems("Foo", source);
+ assertEquals(1, problems.length);
+ CategorizedProblem problem = problems[0];
+ if (JSNI_PARSES_SOURCE_POSITION) {
+ assertEquals(source.indexOf('@'), problem.getSourceStart());
+ }
+ assertEquals(3, problem.getSourceLineNumber());
+ assertTrue(problem.isWarning());
+ assertEquals(
+ "Referencing method 'Foo.m(Ljava/lang/String)': unable to resolve method, expect subsequent failures",
+ problem.getMessage());
+ }
+
+ public void testMalformedJsniRefPosition() {
+ StringBuffer code = new StringBuffer();
+ code.append("class Foo {\n");
+ code.append(" native void m() /*-{\n");
+ code.append(" @Bar;\n");
+ code.append(" }-*/;\n");
+ code.append("}\n");
+ String source = code.toString();
+ CategorizedProblem[] problems = getProblems("Foo", source);
+ assertEquals(1, problems.length);
+ CategorizedProblem problem = problems[0];
+ assertEquals(source.indexOf('@') + "Bar".length(), problem.getSourceStart());
+ assertEquals(3, problem.getSourceLineNumber());
+ assertTrue(problem.isError());
+ assertEquals("Expected \":\" in JSNI reference", problem.getMessage());
+ }
+
+ public void testMalformedJsniRefPositionWithExtraLines() {
+ StringBuffer code = new StringBuffer();
+ code.append("class Foo {\n");
+ code.append(" native\nvoid\nm()\n\n\n/*-{\n\n");
+ code.append(" @Bar;\n");
+ code.append(" }-*/;\n");
+ code.append("}\n");
+ String source = code.toString();
+ CategorizedProblem[] problems = getProblems("Foo", source);
+ assertEquals(1, problems.length);
+ CategorizedProblem problem = problems[0];
+ assertEquals(source.indexOf('@') + "Bar".length(), problem.getSourceStart());
+ assertEquals(9, problem.getSourceLineNumber());
+ assertTrue(problem.isError());
+ assertEquals("Expected \":\" in JSNI reference", problem.getMessage());
+ }
+
+ public void testSourcePosition() {
+ StringBuffer code = new StringBuffer();
+ code.append("class Foo {\n");
+ code.append(" native void m(Object o) /*-{\n");
+ code.append(" o.@Foo::m(Ljava/lang/Object);\n");
+ code.append(" }-*/;\n");
+ code.append("}\n");
+ String source = code.toString();
+
+ List<JsNameRef> foundRefs = findJsniRefs("Foo", source);
+ assertEquals(1, foundRefs.size());
+ JsNameRef ref = foundRefs.get(0);
+ SourceInfo info = ref.getSourceInfo();
+ if (JSNI_PARSES_SOURCE_POSITION) {
+ assertEquals(source.indexOf('@'), info.getStartPos());
+ }
+ assertEquals(3, info.getStartLine());
+ }
+
+ private List<JsNameRef> findJsniRefs(String typeName, final String source) {
+ addGeneratedUnits(new StaticJavaResource(typeName, source));
+ CompilationUnit unit = state.getCompilationUnitMap().get(typeName);
+ assertNotNull(unit);
+ List<JsniMethod> jsniMethods = unit.getJsniMethods();
+ assertEquals(1, jsniMethods.size());
+ JsniMethod jsniMethod = jsniMethods.get(0);
+ final List<JsNameRef> foundRefs = new ArrayList<JsNameRef>();
+ new JsVisitor() {
+ @Override
+ public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
+ if (x.getIdent().startsWith("@")) {
+ foundRefs.add(x);
+ }
+ }
+ }.accept(jsniMethod.function());
+ return foundRefs;
+ }
+
+ private CategorizedProblem[] getProblems(String typeName, String source) {
+ addGeneratedUnits(new StaticJavaResource(typeName, source));
+ CompilationUnit unit = state.getCompilationUnitMap().get(typeName);
+ assertNotNull(unit);
+ CategorizedProblem[] problems = unit.getProblems();
+ return problems;
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/javac/impl/StaticJavaResource.java b/dev/core/test/com/google/gwt/dev/javac/impl/StaticJavaResource.java
index 0ec18b0..0f73da5 100644
--- a/dev/core/test/com/google/gwt/dev/javac/impl/StaticJavaResource.java
+++ b/dev/core/test/com/google/gwt/dev/javac/impl/StaticJavaResource.java
@@ -15,14 +15,12 @@
*/
package com.google.gwt.dev.javac.impl;
-import com.google.gwt.dev.javac.Shared;
-
-public class StaticJavaResource extends MockResource {
+public class StaticJavaResource extends MockJavaResource {
private final CharSequence source;
- public StaticJavaResource(String typeName, CharSequence source) {
- super(Shared.toPath(typeName));
+ public StaticJavaResource(String qualifiedTypeName, CharSequence source) {
+ super(qualifiedTypeName);
this.source = source;
}