Fixed a race condition that occurs when a JSNI ref to a compile time constant is processed before that field has been processed. The fix involves doing a second pass to resolve JSNI refs.
Review by: spoon
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2226 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 e5ba2f5..917a79b 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
@@ -18,6 +18,7 @@
import com.google.gwt.dev.jjs.HasSourceInfo;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.HasEnclosingType;
import com.google.gwt.dev.jjs.ast.JArrayRef;
import com.google.gwt.dev.jjs.ast.JArrayType;
@@ -58,6 +59,7 @@
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JMethodBody;
import com.google.gwt.dev.jjs.ast.JMethodCall;
+import com.google.gwt.dev.jjs.ast.JModVisitor;
import com.google.gwt.dev.jjs.ast.JNewArray;
import com.google.gwt.dev.jjs.ast.JNewInstance;
import com.google.gwt.dev.jjs.ast.JNode;
@@ -85,7 +87,6 @@
import com.google.gwt.dev.jjs.ast.js.JsonObject;
import com.google.gwt.dev.js.ast.JsContext;
import com.google.gwt.dev.js.ast.JsExpression;
-import com.google.gwt.dev.js.ast.JsFunction;
import com.google.gwt.dev.js.ast.JsModVisitor;
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.js.ast.JsProgram;
@@ -217,186 +218,6 @@
*/
private static class JavaASTGenerationVisitor {
- private class JsniRefResolver extends JsModVisitor {
- private final AbstractMethodDeclaration methodDecl;
- private final JsniMethodBody nativeMethodBody;
-
- private JsniRefResolver(AbstractMethodDeclaration methodDecl,
- JsniMethodBody nativeMethodBody) {
- this.methodDecl = methodDecl;
- this.nativeMethodBody = nativeMethodBody;
- }
-
- @Override
- public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
- String ident = x.getIdent();
- if (ident.charAt(0) == '@') {
- processNameRef(x, ctx);
- }
- }
-
- private HasEnclosingType parseJsniRef(SourceInfo info, String ident) {
- JsniRef parsed = JsniRef.parse(ident);
- if (parsed == null) {
- reportJsniError(info, methodDecl,
- "Badly formatted native reference '" + ident + "'");
- return null;
- }
-
- String className = parsed.className();
- JReferenceType type = null;
- if (!className.equals("null")) {
- type = program.getFromTypeMap(className);
- if (type == null) {
- reportJsniError(info, methodDecl,
- "Unresolvable native reference to type '" + className + "'");
- return null;
- }
- }
-
- if (!parsed.isMethod()) {
- // look for a field
- String fieldName = parsed.memberName();
- if (type == null) {
- if (fieldName.equals("nullField")) {
- return program.getNullField();
- }
- } else {
- for (int i = 0; i < type.fields.size(); ++i) {
- JField field = type.fields.get(i);
- if (field.getName().equals(fieldName)) {
- return field;
- }
- }
- }
-
- reportJsniError(info, methodDecl,
- "Unresolvable native reference to field '" + fieldName
- + "' in type '" + className + "'");
- return null;
- } else {
- // look for a method
- String almostMatches = null;
- String methodName = parsed.memberName();
- String jsniSig = parsed.memberSignature();
- if (type == null) {
- if (jsniSig.equals("nullMethod()")) {
- return program.getNullMethod();
- }
- } else {
- for (int i = 0; i < type.methods.size(); ++i) {
- JMethod method = type.methods.get(i);
- if (method.getName().equals(methodName)) {
- String sig = JProgram.getJsniSig(method);
- if (sig.equals(jsniSig)) {
- return method;
- } else if (almostMatches == null) {
- almostMatches = "'" + sig + "'";
- } else {
- almostMatches += ", '" + sig + "'";
- }
- }
- }
- }
-
- if (almostMatches == null) {
- reportJsniError(info, methodDecl,
- "Unresolvable native reference to method '" + methodName
- + "' in type '" + className + "'");
- return null;
- } else {
- reportJsniError(info, methodDecl,
- "Unresolvable native reference to method '" + methodName
- + "' in type '" + className + "' (did you mean "
- + almostMatches + "?)");
- return null;
- }
- }
- }
-
- private void processField(JsNameRef nameRef, SourceInfo info,
- JField field, JsContext<JsExpression> ctx) {
- if (field.getEnclosingType() != null) {
- if (field.isStatic() && nameRef.getQualifier() != null) {
- reportJsniError(info, methodDecl,
- "Cannot make a qualified reference to the static field "
- + field.getName());
- } else if (!field.isStatic() && nameRef.getQualifier() == null) {
- 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()) {
- JLiteral initializer = field.getConstInitializer();
- JType type = initializer.getType();
- if (type instanceof JPrimitiveType
- || type == program.getTypeJavaLangString()) {
- GenerateJavaScriptLiterals generator = new GenerateJavaScriptLiterals(
- jsProgram);
- generator.accept(initializer);
- JsExpression result = generator.peek();
- assert (result != null);
- ctx.replaceMe(result);
- return;
- }
- }
-
- // Normal: create a jsniRef.
- JsniFieldRef fieldRef = new JsniFieldRef(program, info,
- nameRef.getIdent(), field, currentClass);
- nativeMethodBody.jsniFieldRefs.add(fieldRef);
- }
-
- private void processMethod(JsNameRef nameRef, SourceInfo info,
- JMethod method) {
- if (method.getEnclosingType() != null) {
- if (method.isStatic() && nameRef.getQualifier() != null) {
- reportJsniError(info, methodDecl,
- "Cannot make a qualified reference to the static method "
- + method.getName());
- } else if (!method.isStatic() && nameRef.getQualifier() == null) {
- reportJsniError(info, methodDecl,
- "Cannot make an unqualified reference to the instance method "
- + method.getName());
- }
- }
-
- JsniMethodRef methodRef = new JsniMethodRef(program, info,
- nameRef.getIdent(), method);
- nativeMethodBody.jsniMethodRefs.add(methodRef);
- }
-
- private void processNameRef(JsNameRef nameRef, JsContext<JsExpression> ctx) {
- SourceInfo info = nativeMethodBody.getSourceInfo();
- // TODO: make this tighter when we have real source info
- // JSourceInfo info = translateInfo(nameRef.getInfo());
- String ident = nameRef.getIdent();
- HasEnclosingType node = program.jsniMap.get(ident);
- if (node == null) {
- node = parseJsniRef(info, ident);
- if (node == null) {
- return; // already reported error
- }
- program.jsniMap.put(ident, node);
- }
-
- if (node instanceof JField) {
- processField(nameRef, info, (JField) node, ctx);
- } else if (node instanceof JMethod) {
- processMethod(nameRef, info, (JMethod) node);
- } else {
- throw new InternalCompilerException((HasSourceInfo) node,
- "JSNI reference to something other than a field or method?", null);
- }
- }
- }
-
private static InternalCompilerException translateException(JNode node,
Throwable e) {
InternalCompilerException ice;
@@ -426,7 +247,7 @@
private boolean enableAsserts;
- private final JsProgram jsProgram;
+ private final Map<JsniMethodBody, AbstractMethodDeclaration> jsniMethodMap = new HashMap<JsniMethodBody, AbstractMethodDeclaration>();
private final Map<JMethod, Map<String, JLabel>> labelMap = new IdentityHashMap<JMethod, Map<String, JLabel>>();
@@ -435,10 +256,9 @@
private final TypeMap typeMap;
public JavaASTGenerationVisitor(TypeMap typeMap, JProgram program,
- JsProgram jsProgram, boolean enableAsserts) {
+ boolean enableAsserts) {
this.typeMap = typeMap;
this.program = program;
- this.jsProgram = jsProgram;
this.enableAsserts = enableAsserts;
}
@@ -686,6 +506,10 @@
return stmt;
}
+ Map<JsniMethodBody, AbstractMethodDeclaration> getJsniMethodMap() {
+ return jsniMethodMap;
+ }
+
JBooleanLiteral processConstant(BooleanConstant x) {
return program.getLiteralBoolean(x.booleanValue());
}
@@ -1595,14 +1419,8 @@
void processNativeMethod(AbstractMethodDeclaration x,
JsniMethodBody nativeMethodBody) {
-
- JsFunction func = nativeMethodBody.getFunc();
- if (func == null) {
- return;
- }
-
- // resolve jsni refs
- new JsniRefResolver(x, nativeMethodBody).accept(func);
+ // Squirrel away a reference to the JDT node to enable error reporting.
+ jsniMethodMap.put(nativeMethodBody, x);
}
JStatement processStatement(AssertStatement x) {
@@ -2651,17 +2469,232 @@
}
/**
+ * Resolve JSNI refs; replace with compile-time constants where appropriate.
+ */
+ private static class JsniRefGenerationVisitor extends JModVisitor {
+
+ private class JsniRefResolver extends JsModVisitor {
+ private final AbstractMethodDeclaration methodDecl;
+ private final JsniMethodBody nativeMethodBody;
+
+ private JsniRefResolver(AbstractMethodDeclaration methodDecl,
+ JsniMethodBody nativeMethodBody) {
+ this.methodDecl = methodDecl;
+ this.nativeMethodBody = nativeMethodBody;
+ }
+
+ @Override
+ public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
+ String ident = x.getIdent();
+ if (ident.charAt(0) == '@') {
+ processNameRef(x, ctx);
+ }
+ }
+
+ private HasEnclosingType parseJsniRef(SourceInfo info, String ident) {
+ JsniRef parsed = JsniRef.parse(ident);
+ if (parsed == null) {
+ reportJsniError(info, methodDecl,
+ "Badly formatted native reference '" + ident + "'");
+ return null;
+ }
+
+ String className = parsed.className();
+ JReferenceType type = null;
+ if (!className.equals("null")) {
+ type = program.getFromTypeMap(className);
+ if (type == null) {
+ reportJsniError(info, methodDecl,
+ "Unresolvable native reference to type '" + className + "'");
+ return null;
+ }
+ }
+
+ if (!parsed.isMethod()) {
+ // look for a field
+ String fieldName = parsed.memberName();
+ if (type == null) {
+ if (fieldName.equals("nullField")) {
+ return program.getNullField();
+ }
+ } else {
+ for (int i = 0; i < type.fields.size(); ++i) {
+ JField field = type.fields.get(i);
+ if (field.getName().equals(fieldName)) {
+ return field;
+ }
+ }
+ }
+
+ reportJsniError(info, methodDecl,
+ "Unresolvable native reference to field '" + fieldName
+ + "' in type '" + className + "'");
+ return null;
+ } else {
+ // look for a method
+ String almostMatches = null;
+ String methodName = parsed.memberName();
+ String jsniSig = parsed.memberSignature();
+ if (type == null) {
+ if (jsniSig.equals("nullMethod()")) {
+ return program.getNullMethod();
+ }
+ } else {
+ for (int i = 0; i < type.methods.size(); ++i) {
+ JMethod method = type.methods.get(i);
+ if (method.getName().equals(methodName)) {
+ String sig = JProgram.getJsniSig(method);
+ if (sig.equals(jsniSig)) {
+ return method;
+ } else if (almostMatches == null) {
+ almostMatches = "'" + sig + "'";
+ } else {
+ almostMatches += ", '" + sig + "'";
+ }
+ }
+ }
+ }
+
+ if (almostMatches == null) {
+ reportJsniError(info, methodDecl,
+ "Unresolvable native reference to method '" + methodName
+ + "' in type '" + className + "'");
+ return null;
+ } else {
+ reportJsniError(info, methodDecl,
+ "Unresolvable native reference to method '" + methodName
+ + "' in type '" + className + "' (did you mean "
+ + almostMatches + "?)");
+ return null;
+ }
+ }
+ }
+
+ private void processField(JsNameRef nameRef, SourceInfo info,
+ JField field, JsContext<JsExpression> ctx) {
+ if (field.getEnclosingType() != null) {
+ if (field.isStatic() && nameRef.getQualifier() != null) {
+ reportJsniError(info, methodDecl,
+ "Cannot make a qualified reference to the static field "
+ + field.getName());
+ } else if (!field.isStatic() && nameRef.getQualifier() == null) {
+ 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()) {
+ JLiteral initializer = field.getConstInitializer();
+ JType type = initializer.getType();
+ if (type instanceof JPrimitiveType
+ || type == program.getTypeJavaLangString()) {
+ GenerateJavaScriptLiterals generator = new GenerateJavaScriptLiterals(
+ jsProgram);
+ generator.accept(initializer);
+ JsExpression result = generator.peek();
+ assert (result != null);
+ ctx.replaceMe(result);
+ return;
+ }
+ }
+
+ // Normal: create a jsniRef.
+ JsniFieldRef fieldRef = new JsniFieldRef(program, info,
+ nameRef.getIdent(), field, currentClass);
+ nativeMethodBody.jsniFieldRefs.add(fieldRef);
+ }
+
+ private void processMethod(JsNameRef nameRef, SourceInfo info,
+ JMethod method) {
+ if (method.getEnclosingType() != null) {
+ if (method.isStatic() && nameRef.getQualifier() != null) {
+ reportJsniError(info, methodDecl,
+ "Cannot make a qualified reference to the static method "
+ + method.getName());
+ } else if (!method.isStatic() && nameRef.getQualifier() == null) {
+ reportJsniError(info, methodDecl,
+ "Cannot make an unqualified reference to the instance method "
+ + method.getName());
+ }
+ }
+
+ JsniMethodRef methodRef = new JsniMethodRef(program, info,
+ nameRef.getIdent(), method);
+ nativeMethodBody.jsniMethodRefs.add(methodRef);
+ }
+
+ private void processNameRef(JsNameRef nameRef, JsContext<JsExpression> ctx) {
+ SourceInfo info = nativeMethodBody.getSourceInfo();
+ // TODO: make this tighter when we have real source info
+ // JSourceInfo info = translateInfo(nameRef.getInfo());
+ String ident = nameRef.getIdent();
+ HasEnclosingType node = program.jsniMap.get(ident);
+ if (node == null) {
+ node = parseJsniRef(info, ident);
+ if (node == null) {
+ return; // already reported error
+ }
+ program.jsniMap.put(ident, node);
+ }
+
+ if (node instanceof JField) {
+ processField(nameRef, info, (JField) node, ctx);
+ } else if (node instanceof JMethod) {
+ processMethod(nameRef, info, (JMethod) node);
+ } else {
+ throw new InternalCompilerException((HasSourceInfo) node,
+ "JSNI reference to something other than a field or method?", null);
+ }
+ }
+ }
+
+ private JReferenceType currentClass;
+
+ private final Map<JsniMethodBody, AbstractMethodDeclaration> jsniMethodMap;
+
+ private final JsProgram jsProgram;
+
+ private final JProgram program;
+
+ public JsniRefGenerationVisitor(JProgram program, JsProgram jsProgram,
+ Map<JsniMethodBody, AbstractMethodDeclaration> jsniMethodMap) {
+ this.program = program;
+ this.jsProgram = jsProgram;
+ this.jsniMethodMap = jsniMethodMap;
+ }
+
+ public void endVisit(JClassType x, Context ctx) {
+ currentClass = null;
+ }
+
+ @Override
+ public void endVisit(JsniMethodBody x, Context ctx) {
+ new JsniRefResolver(jsniMethodMap.get(x), x).accept(x.getFunc());
+ }
+ }
+
+ /**
* Combines the information from the JDT type nodes and the type map to create
* a JProgram structure.
*/
public static void exec(TypeDeclaration[] types, TypeMap typeMap,
JProgram jprogram, JsProgram jsProgram, boolean enableAsserts) {
+ // Construct the basic AST.
JavaASTGenerationVisitor v = new JavaASTGenerationVisitor(typeMap,
- jprogram, jsProgram, enableAsserts);
+ jprogram, enableAsserts);
for (int i = 0; i < types.length; ++i) {
v.processType(types[i]);
}
Collections.sort(jprogram.getDeclaredTypes(), new HasNameSort());
+
+ // Process JSNI.
+ Map<JsniMethodBody, AbstractMethodDeclaration> jsniMethodMap = v.getJsniMethodMap();
+ new JsniRefGenerationVisitor(jprogram, jsProgram, jsniMethodMap).accept(jprogram);
}
public static void reportJsniError(SourceInfo info,