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