All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.dev.javac.JsniReferenceResolver Maven / Gradle / Ivy

/*
 * 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.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.SourceInfo;
import com.google.gwt.dev.js.ast.JsBinaryOperation;
import com.google.gwt.dev.js.ast.JsContext;
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.util.InstalledHelpInfo;
import com.google.gwt.dev.util.JsniRef;
import com.google.gwt.dev.util.collect.Stack;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
import com.google.gwt.thirdparty.guava.common.collect.Lists;

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.ImportReference;
import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
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.ArrayBinding;
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;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.NestedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.SyntheticArgumentBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.lookup.UnresolvedReferenceBinding;
import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;

import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

/**
 * Resolves JSNI references to fields and methods and gives a informative errors if the references
 * cannot be resolved.
 * 

* * JSNI references consist of two parts @ClassDescriptor::memberDescriptor. Class descriptors * are source names and memberDescriptors are either a field name or a method name with a signature * specification. Signature specification a full signature or a wildcard (*). *

* * The result of resolution will be a modified JSNI AST where all the resolved references will * carry the fully qualified class name and the full member reference, along with a mapping from * jsni references to JDT binding. *

* In addition in the following instances involving longs warning will be emitted to remind uses * that GWT longs are not JavaScript numbers: *

    *
  • JSNI methods with a parameter or return type of long or an array whose base type is long. *
  • *
  • Access from JSNI to a field whose type is long or an array whose base type is long.
  • *
  • Access from JSNI to a method with a parameter or return type of long or an array whose base * type is long.
  • *
  • JSNI references to anonymous classes.
  • *
*/ public class JsniReferenceResolver { /** * A call-back interface to resolve types. */ public interface TypeResolver { /** * @param sourceOrBinaryName Either source or binary names are allowed in JSNI. */ ReferenceBinding resolveType(String sourceOrBinaryName); } private class JsniDeclChecker extends SafeASTVisitor implements ClassFileConstants { @Override public void endVisit(MethodDeclaration method, ClassScope scope) { if (method.isNative()) { boolean hasUnsafeLongsAnnotation = hasUnsafeLongsAnnotation(method, scope); if (!hasUnsafeLongsAnnotation) { checkDecl(method, scope); } JsniMethod jsniMethod = jsniMethods.get(method); if (jsniMethod != null) { new JsniReferenceResolverVisitor(method, hasUnsafeLongsAnnotation).resolve( jsniMethod.function()); } } suppressWarningsStack.pop(); } @Override public void endVisit(TypeDeclaration typeDeclaration, ClassScope scope) { suppressWarningsStack.pop(); } @Override public void endVisit(TypeDeclaration typeDeclaration, CompilationUnitScope scope) { suppressWarningsStack.pop(); } @Override public void endVisitValid(TypeDeclaration typeDeclaration, BlockScope scope) { suppressWarningsStack.pop(); } @Override public boolean visit(MethodDeclaration meth, ClassScope scope) { suppressWarningsStack.push(getSuppressedWarnings(meth.annotations)); return true; } @Override public boolean visit(TypeDeclaration typeDeclaration, ClassScope scope) { suppressWarningsStack.push(getSuppressedWarnings(typeDeclaration.annotations)); return true; } @Override public boolean visit(TypeDeclaration typeDeclaration, CompilationUnitScope scope) { suppressWarningsStack.push(getSuppressedWarnings(typeDeclaration.annotations)); return true; } @Override public boolean visitValid(TypeDeclaration typeDeclaration, BlockScope scope) { suppressWarningsStack.push(getSuppressedWarnings(typeDeclaration.annotations)); return true; } private void checkDecl(MethodDeclaration meth, ClassScope scope) { TypeReference returnType = meth.returnType; if (containsLong(returnType, scope)) { longAccessError(meth, "Type '" + typeString(returnType) + "' may not be returned from a JSNI method"); } if (meth.arguments == null) { return; } for (Argument arg : meth.arguments) { if (containsLong(arg.type, scope)) { longAccessError(arg, "Parameter '" + String.valueOf(arg.name) + "': type '" + typeString(arg.type) + "' is not safe to access in JSNI code"); } } } private boolean containsLong(final TypeReference type, ClassScope scope) { return type != null && JsniReferenceResolver.this.containsLong(type.resolveType(scope)); } private String typeString(TypeReference type) { return type.toString(); } } private class JsniReferenceResolverVisitor extends JsModVisitor { private final boolean hasUnsafeLongsAnnotation; private final MethodDeclaration method; public JsniReferenceResolverVisitor(MethodDeclaration method, boolean hasUnsafeLongsAnnotation) { this.method = method; this.hasUnsafeLongsAnnotation = hasUnsafeLongsAnnotation; } public void resolve(JsFunction function) { this.accept(function); } @Override public void endVisit(JsBinaryOperation x, JsContext ctx) { // Look for the patern a.f = o.@C::m(); if (!x.getOperator().isAssignment()) { return; } if (!(x.getArg1() instanceof JsNameRef) || !(x.getArg2() instanceof JsNameRef)) { return; } JsNameRef lhs = (JsNameRef) x.getArg1(); JsNameRef rhs = (JsNameRef) x.getArg2(); if (!rhs.isJsniReference() || !(jsniRefs.get(rhs.getIdent()) instanceof MethodBinding)) { // Not a reference to a JSNI method. return; } if (rhs.getQualifier() == null) { // Unqualified JSNI reference is OK. return; } if (lhs.getQualifier() == null) { // Assignment to unqualified variable is OK. return; } // Here we have a qualified JSNI method reference assigned to a field. JsniRef jsniRef = JsniRef.parse(rhs.getIdent()); emitWarning("unsafe", WARN_NOT_CAPTURING_QUALIFIER, x.getSourceInfo(), jsniRef, rhs.getQualifier().toSource()); } @Override public void endVisit(JsNameRef x, JsContext ctx) { String ident = x.getIdent(); if (!x.isJsniReference()) { // Not a jsni reference. return; } JsniRef jsniRef = JsniRef.parse(ident); if (jsniRef == null) { emitError(ERR_MALFORMED_JSNI_IDENTIFIER, x.getSourceInfo(), null, ident); return; } Binding binding = resolveReference(x.getSourceInfo(), jsniRef, x.getQualifier() != null, ctx.isLvalue()); assert !x.isResolved(); if (!ident.equals(jsniRef.getResolvedReference())) { // Replace by the resolved reference (consisting of the fully qualified classname and the // method description including actual signature) so that dispatch everywhere is consistent // with the one resolved here. ident = jsniRef.getResolvedReference(); JsNameRef newRef = new JsNameRef(x.getSourceInfo(), ident); newRef.setQualifier(x.getQualifier()); ctx.replaceMe(newRef); } if (binding != null) { jsniRefs.put(ident, binding); } } private void resolveClassReference(JsniRef jsniRef) { // Precedence rules as of JLS 6.4.1. // 1. Enclosing type. // 2. Visible type in same compilation unit. // 3. Named import. // 4. Same package. // 5. Import on demand. String originalName = jsniRef.className(); String importedClassName = originalName; if (importedClassName.contains(".")) { // Only retain up the first dot to support innerclasses. E.g. import c.g.A and reference // @A.I::f. importedClassName = importedClassName.substring(0,importedClassName.indexOf(".")); } // 1 & 2. Check to see if this name refers to the enclosing class or is directly accessible // from it. ReferenceBinding declaringClass = method.binding.declaringClass; while (declaringClass != null) { String declaringClassName = JdtUtil.getSourceName(declaringClass); if (declaringClassName.equals(importedClassName) || declaringClassName.endsWith("." + importedClassName)) { // Referring to declaring class name using unqualified name. jsniRef.setResolvedClassName(declaringClassName + originalName.substring(importedClassName.length())); return; } String fullClassName = declaringClassName + "." + originalName; if (typeResolver.resolveType(fullClassName) != null) { jsniRef.setResolvedClassName(fullClassName); return; } declaringClass = declaringClass.enclosingTypeAt(1); } // 3. Check to see if this name is one of the named imports. for (ImportReference importReference : cudImports) { String nameFromImport = JdtUtil.asDottedString(importReference.getImportName()); if (!importReference.isStatic() && importReference.trailingStarPosition == 0 && nameFromImport.endsWith("." + importedClassName)) { jsniRef.setResolvedClassName( nameFromImport + originalName.substring(importedClassName.length())); return; } } // 4. Check to see if this name is resolvable from the current package. String currentPackageClassName = String.valueOf(method.binding.declaringClass.qualifiedPackageName()); currentPackageClassName += (currentPackageClassName.isEmpty() ? "" : ".") + originalName; if (typeResolver.resolveType(currentPackageClassName) != null) { jsniRef.setResolvedClassName(currentPackageClassName); return; } // 5. Check to see if this name is resolvable as an import on demand. List importPackages = Lists.newArrayList("java.lang"); for (ImportReference importReference : cudImports) { if (importReference.isStatic() || importReference.trailingStarPosition == 0) { continue; } importPackages.add(JdtUtil.asDottedString(importReference.getImportName())); } for (String importPackage : importPackages) { String fullClassName = importPackage + "." + originalName; if (typeResolver.resolveType(fullClassName) != null) { jsniRef.setResolvedClassName(fullClassName); return; } } // Otherwise leave it as it is. // TODO(rluble): Maybe we should leave it null here. jsniRef.setResolvedClassName(jsniRef.className()); } private boolean isEnclosingClass(TypeBinding clazz, TypeBinding maybeEnclosingClass) { for (TypeBinding currentClass = clazz; currentClass != null; currentClass = currentClass.enclosingType()) { if (currentClass == maybeEnclosingClass) { return true; } } return false; } private FieldBinding checkAndResolveFieldRef(SourceInfo errorInfo, ReferenceBinding clazz, JsniRef jsniRef, boolean hasQualifier, boolean isLvalue) { assert jsniRef.isField(); FieldBinding target = getField(clazz, jsniRef); if (target == null) { emitError(ERR_UNABLE_TO_RESOLVE_FIELD, errorInfo, jsniRef); return null; } resolveJsniRef(jsniRef, target); if (target.isDeprecated() && !isEnclosingClass(method.binding.declaringClass, target.declaringClass)) { emitWarning("deprecation", WARN_DEPRECATED_FIELD, errorInfo, jsniRef); } if (isLvalue && target.constant() != Constant.NotAConstant) { emitError(ERR_ILLEGAL_ASSIGNMENT_TO_COMPILE_TIME_CONSTANT, errorInfo, jsniRef); } if (target.isStatic() && hasQualifier) { emitError(ERR_UNNECESSARY_QUALIFIER_STATIC_FIELD, errorInfo, jsniRef); } else if (!target.isStatic() && !hasQualifier) { emitError(ERR_MISSING_QUALIFIER_INSTANCE_FIELD, errorInfo, jsniRef); } if (hasUnsafeLongsAnnotation) { return target; } if (containsLong(target.type)) { emitError(ERR_UNSAFE_FIELD_ACCESS, errorInfo, jsniRef, typeString(target.type)); } return target; } private MethodBinding checkAndResolveMethodRef(SourceInfo errorInfo, ReferenceBinding clazz, JsniRef jsniRef, boolean hasQualifier, boolean isLvalue) { assert jsniRef.isMethod(); List targets = getMatchingMethods(clazz, jsniRef); if (targets.size() > 1) { emitError(ERR_AMBIGUOUS_WILDCARD_MATCH, errorInfo, jsniRef, JdtUtil.formatBinding(targets.get(0)), JdtUtil.formatBinding(targets.get(1))); return null; } else if (targets.isEmpty()) { emitError(ERR_UNABLE_TO_RESOLVE_METHOD, errorInfo, jsniRef); return null; } MethodBinding target = targets.get(0); resolveJsniRef(jsniRef, target); if (target.isDeprecated() && !isEnclosingClass(method.binding.declaringClass, target.declaringClass)) { emitWarning("deprecation", WARN_DEPRECATED_METHOD, errorInfo, jsniRef); } if (isLvalue) { emitError(ERR_ILLEGAL_ASSIGNMENT_TO_METHOD, errorInfo, jsniRef); } boolean needsQualifer = !target.isStatic() && !target.isConstructor(); if (!needsQualifer && hasQualifier) { emitError(ERR_UNNECESSARY_QUALIFIER_STATIC_METHOD, errorInfo, jsniRef); } else if (needsQualifer && !hasQualifier) { emitError(ERR_MISSING_QUALIFIER_INSTANCE_METHOD, errorInfo, jsniRef); } if (!target.isStatic() && JSORestrictionsChecker.isJso(clazz)) { emitError(ERR_REFERENCE_TO_JSO_INSTANCE_METHOD, errorInfo, jsniRef); } if (checkerState.isJsoInterface(clazz)) { String implementor = checkerState.getJsoImplementor(clazz); emitError(ERR_REFERENCE_TO_JSO_INTERFACE_METHOD, errorInfo, jsniRef, implementor); } if (hasUnsafeLongsAnnotation) { return target; } if (containsLong(target.returnType)) { emitError(ERR_ILLEGAL_RETURN_TYPE, errorInfo, jsniRef, typeString(target.returnType)); } if (target.parameters != null) { int i = 0; for (TypeBinding paramType : target.parameters) { ++i; if (containsLong(paramType)) { // It would be nice to print the parameter name, but how to find it? emitError(ERR_ILLEGAL_PARAMETER, errorInfo, jsniRef, i, typeString(paramType)); } } } return target; } private Binding resolveReference(SourceInfo errorInfo, JsniRef jsniRef, boolean hasQualifier, boolean isLvalue) { resolveClassReference(jsniRef); String className = jsniRef.getResolvedClassName(); boolean isPrimitive; ReferenceBinding clazz; TypeBinding binding = method.scope.getBaseType(className.toCharArray()); if (binding != null) { isPrimitive = true; clazz = null; } else { isPrimitive = false; binding = clazz = findClass(className); } if (binding != null && binding.isAnonymousType()) { // There seems that there is no way to write a JSNI reference to an anonymous class as // it will require to accept a source name of the form A.1 where one of the identifier parts // consists only of digits and therefore is not a valid identifier. // This error case is left here in case names of that form start appearing from the JSNI // parser. emitError(ERR_ILLEGAL_ANONYMOUS_INNER_CLASS, errorInfo, jsniRef); return null; } else if (binding == null) { emitError(ERR_UNABLE_TO_RESOLVE_CLASS, errorInfo, jsniRef); return null; } if (clazz != null && clazz.isDeprecated() && !isEnclosingClass(method.binding.declaringClass, clazz)) { emitWarning("deprecation", WARN_DEPRECATED_CLASS, errorInfo, jsniRef); } if (jsniRef.isField() && "class".equals(jsniRef.memberName())) { if (isLvalue) { emitError(ERR_ILLEGAL_ASSIGNMENT_TO_CLASS_LITERAL, errorInfo, jsniRef); return null; } // Reference to the class itself. jsniRef.setResolvedClassName(JdtUtil.getSourceName(binding)); jsniRef.setResolvedMemberWithSignature(jsniRef.memberSignature()); if (jsniRef.isArray()) { ArrayBinding arrayBinding = method.scope.createArrayType(binding, jsniRef.getDimensions()); return arrayBinding; } else { return binding; } } if (jsniRef.isArray() || isPrimitive) { emitError(ERR_ILLEGAL_ARRAY_OR_PRIMITIVE_REFERENCE, errorInfo, jsniRef); return null; } assert clazz != null; if (jsniRef.isMethod()) { return checkAndResolveMethodRef(errorInfo, clazz, jsniRef, hasQualifier, isLvalue); } else { return checkAndResolveFieldRef(errorInfo, clazz, jsniRef, hasQualifier, isLvalue); } } private static final String WARN_NOT_CAPTURING_QUALIFIER = "Instance method reference '%2$s.%3$s' loses its instance ('%8$s') when assigned; " + "to remove this warning either assign to a local variable or construct " + "the proper closure using an anonymous function or by calling " + "Function.prototype.bind"; private static final String ERR_ILLEGAL_ARRAY_OR_PRIMITIVE_REFERENCE = "Referencing member '%2$s.%4$s': 'class' is " + "the only legal reference for arrays and primitive types"; private static final String ERR_ILLEGAL_ASSIGNMENT_TO_CLASS_LITERAL = "Illegal assignment to class literal '%2$s.%3$s'"; private static final String ERR_UNABLE_TO_RESOLVE_CLASS = "Referencing class '%2$s': unable to resolve class"; private static final String ERR_ILLEGAL_ANONYMOUS_INNER_CLASS = "Referencing class '%2$s': JSNI references to anonymous classes are illegal"; private static final String ERR_ILLEGAL_FIELD_ACCESS_ON_NULL = "Referencing field '%2$s.%3$s': 'nullField' is the only legal field reference for 'null'"; private static final String ERR_ILLEGAL_METHOD_ACCESS_ON_NULL = "Referencing method '%2$s.%4$s': 'nullMethod()' is the only legal method for 'null'"; private static final String ERR_ILLEGAL_PARAMETER = "Parameter %8$d of method '%2$s.%3$s': type '%9$s' may not be passed out of JSNI code"; private static final String ERR_ILLEGAL_RETURN_TYPE = "Referencing method '%2$s.%3$s': return type '%8$s' is not safe to access in JSNI code"; private static final String ERR_REFERENCE_TO_JSO_INTERFACE_METHOD = "Referencing interface method '%2$s.%4$s': implemented by '%8$s';" + " references to instance methods in overlay types are illegal;" + " use a stronger type or a Java trampoline method"; private static final String ERR_REFERENCE_TO_JSO_INSTANCE_METHOD = "Referencing method '%2$s.%4$s': " + "references to instance methods in overlay types are illegal"; private static final String ERR_MISSING_QUALIFIER_INSTANCE_METHOD = "Missing qualifier on instance method '%2$s.%3$s'"; private static final String ERR_UNNECESSARY_QUALIFIER_STATIC_METHOD = "Unnecessary qualifier on static method '%2$s.%3$s'"; private static final String ERR_MISSING_QUALIFIER_INSTANCE_FIELD = "Missing qualifier on instance field '%2$s.%3$s'"; private static final String ERR_UNNECESSARY_QUALIFIER_STATIC_FIELD = "Unnecessary qualifier on static field '%2$s.%3$s'"; private static final String ERR_ILLEGAL_ASSIGNMENT_TO_METHOD = "Illegal assignment to method '%2$s.%3$s'"; private static final String ERR_UNABLE_TO_RESOLVE_METHOD = "Referencing method '%2$s.%4$s': unable to resolve method in class '%6$s'"; private static final String ERR_UNABLE_TO_RESOLVE_FIELD = "Referencing field '%2$s.%4$s': unable to resolve field in class '%6$s'"; private static final String ERR_AMBIGUOUS_WILDCARD_MATCH = "Referencing method '%2$s.%4$s': ambiguous wildcard match; " + "both '%8$s' and '%9$s' match"; private static final String ERR_UNSAFE_FIELD_ACCESS = "Referencing field '%2$s.%3$s': type '%8$s' is not safe to access in JSNI code"; private static final String ERR_ILLEGAL_ASSIGNMENT_TO_COMPILE_TIME_CONSTANT = "Illegal assignment to compile-time constant '%2$s.%3$s'"; private static final String ERR_MALFORMED_JSNI_IDENTIFIER = "Malformed JSNI identifier '%8$s'"; private static final String WARN_DEPRECATED_CLASS = "Referencing deprecated class '%2$s'"; private static final String WARN_DEPRECATED_METHOD = "Referencing method '%2$s.%3$s': method '%6$s.%7$s' is deprecated"; private static final String WARN_DEPRECATED_FIELD = "Referencing field '%2$s.%3$s': field '%6$s.%7$s' is deprecated"; /** * Formats messages for {@link #emitError} and {@link #emitWarning}, substituting as follows: *
    *
  • %1$s -> full original jsni string
  • *
  • %2$s -> full original jsni classname
  • *
  • %3$s -> full original jsni membername
  • *
  • %4$s -> full original jsni memberspec
  • *
  • %5$s -> full resolved jsni string
  • *
  • %6$s -> full resolved jsni classname
  • *
  • %7$s -> full resolved jsni member with signature
  • *
*/ private String formatMessage(String msg, JsniRef jsniRef, Object... extraPars) { Object[] formatParameters = new Object[extraPars.length + 7]; if (jsniRef != null) { formatParameters[0] = jsniRef.toString(); formatParameters[1] = jsniRef.fullClassName(); formatParameters[2] = jsniRef.memberName(); formatParameters[3] = jsniRef.memberSignature(); formatParameters[4] = jsniRef.getResolvedReference(); formatParameters[5] = jsniRef.getFullResolvedClassName(); formatParameters[6] = jsniRef.getResolvedMemberSignature(); } for (int i = 0; i < extraPars.length; i++) { formatParameters[i + 7] = extraPars[i]; } return String.format(msg, formatParameters); } private void emitError(String msg, SourceInfo errorInfo, JsniRef jsniRef, Object... extraPars) { JsniMethodCollector.reportJsniError(errorInfo, method, formatMessage(msg, jsniRef, extraPars)); } private void emitWarning(String category, String msg, SourceInfo errorInfo, JsniRef jsniRef, Object... extraPars) { for (Set suppressWarnings : suppressWarningsStack) { if (suppressWarnings.contains(category) || suppressWarnings.contains("all")) { return; } } JsniMethodCollector .reportJsniWarning(errorInfo, method, formatMessage(msg, jsniRef, extraPars)); } private ReferenceBinding findClass(String className) { ReferenceBinding binding = typeResolver.resolveType(className); assert !(binding instanceof ProblemReferenceBinding); assert !(binding instanceof UnresolvedReferenceBinding); return binding; } private FieldBinding getField(ReferenceBinding clazz, JsniRef jsniRef) { assert jsniRef.isField(); return clazz.getField(jsniRef.memberName().toCharArray(), false); } private MethodBinding getMatchingConstructor(ReferenceBinding clazz, JsniRef jsniRef) { for (MethodBinding constructorBinding : clazz.getMethods(INIT_CTOR_CHARS)) { StringBuilder methodSig = new StringBuilder(); if (clazz instanceof NestedTypeBinding) { // Match synthetic args for enclosing instances. NestedTypeBinding nestedBinding = (NestedTypeBinding) clazz; if (nestedBinding.enclosingInstances != null) { for (int i = 0; i < nestedBinding.enclosingInstances.length; ++i) { SyntheticArgumentBinding arg = nestedBinding.enclosingInstances[i]; methodSig.append(arg.type.signature()); } } } if (constructorBinding.parameters != null) { for (TypeBinding binding : constructorBinding.parameters) { methodSig.append(binding.signature()); } } if (methodSig.toString().equals(jsniRef.paramTypesString())) { return constructorBinding; } } return null; } /** * Returns true if {@code method}, is a method in {@code jsniClassQualifier} (to support current * private method access} or is a visible method in the super class per Java import rules. */ private boolean isMethodVisibleToJsniRef(ReferenceBinding jsniClassQualifier, MethodBinding targetMethod) { return // All methods are visible (regardless of access) as long as the JSNI class reference is // explicit. jsniClassQualifier == targetMethod.declaringClass || // All public superclass methods are visible. targetMethod.isPublic() // Protected and package private are visible from the same package. || !targetMethod.isPrivate() && method.binding.declaringClass.getPackage() == targetMethod.declaringClass.getPackage() // Protected super class methods are visible from any subclass. || targetMethod.isProtected() && targetMethod.declaringClass.isSuperclassOf(method.binding.declaringClass); } private List getMatchingMethods(ReferenceBinding clazz, JsniRef jsniRef) { assert jsniRef.isMethod(); List foundMethods = Lists.newArrayList(); String methodName = jsniRef.memberName(); if ("new".equals(methodName)) { MethodBinding constructorBinding = getMatchingConstructor(clazz, jsniRef); if (constructorBinding != null) { foundMethods.add(constructorBinding); } } else { Queue work = Lists.newLinkedList(); work.add(clazz); while (!work.isEmpty()) { ReferenceBinding currentClass = work.remove(); NEXT_METHOD: for (MethodBinding findMethod : currentClass.getMethods(methodName.toCharArray())) { if (!isMethodVisibleToJsniRef(clazz, findMethod)) { continue; } if (!paramTypesMatch(findMethod, jsniRef)) { continue; } for (MethodBinding alreadyFound : foundMethods) { // Only collect methods with different signatures (same signatures are overloads // hence they are ok. if (paramTypesMatch(alreadyFound, findMethod)) { break NEXT_METHOD; } } foundMethods.add(findMethod); } ReferenceBinding[] superInterfaces = currentClass.superInterfaces(); if (superInterfaces != null) { work.addAll(Arrays.asList(superInterfaces)); } ReferenceBinding superclass = currentClass.superclass(); if (superclass != null) { work.add(superclass); } } } return foundMethods; } private boolean paramTypesMatch(MethodBinding method, JsniRef jsniRef) { if (jsniRef.matchesAnyOverload()) { return true; } StringBuilder methodSig = new StringBuilder(); if (method.parameters != null) { for (TypeBinding binding : method.parameters) { methodSig.append(binding.signature()); } } return methodSig.toString().equals(jsniRef.paramTypesString()); } private boolean paramTypesMatch(MethodBinding thisMethod, MethodBinding thatMethod) { int thisParameterCount = thisMethod.parameters == null ? 0 : thisMethod.parameters.length; int thatParameterCount = thatMethod.parameters == null ? 0 : thatMethod.parameters.length; if (thisParameterCount != thatParameterCount) { return false; } for (int i = 0; i < thisParameterCount; i++) { TypeBinding thisBinding = thisMethod.parameters[i]; TypeBinding thatBinding = thatMethod.parameters[i]; if (!new String(thisBinding.signature()).equals(new String(thatBinding.signature()))) { return false; } } return true; } private String typeString(TypeBinding type) { return String.valueOf(type.shortReadableName()); } } private static final char[] INIT_CTOR_CHARS = "".toCharArray(); private static final char[][] UNSAFE_LONG_ANNOTATION_CHARS = CharOperation.splitOn( '.', UnsafeNativeLong.class.getName().toCharArray()); /** * Resolve JSNI references in an entire * {@link org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration}. * */ public static void resolve(CompilationUnitDeclaration cud, List cudOriginalImports, CheckerState checkerState, Map jsniMethods, Map jsniRefs, TypeResolver typeResolver) { new JsniReferenceResolver(cud, cudOriginalImports, checkerState, typeResolver, jsniMethods, jsniRefs).resolve(); } Set getSuppressedWarnings(Annotation[] annotations) { if (annotations == null) { return ImmutableSet.of(); } for (Annotation a : annotations) { if (!SuppressWarnings.class.getName().equals( CharOperation.toString(((ReferenceBinding) a.resolvedType).compoundName))) { continue; } for (MemberValuePair pair : a.memberValuePairs()) { if (!String.valueOf(pair.name).equals("value")) { continue; } Expression valueExpr = pair.value; if (valueExpr instanceof StringLiteral) { // @SuppressWarnings("Foo") return ImmutableSet.of(((StringLiteral) valueExpr).constant.stringValue().toLowerCase( Locale.ENGLISH)); } else if (valueExpr instanceof ArrayInitializer) { // @SuppressWarnings({ "Foo", "Bar"}) ArrayInitializer ai = (ArrayInitializer) valueExpr; ImmutableSet.Builder valuesSetBuilder = ImmutableSet.builder(); for (int i = 0, j = ai.expressions.length; i < j; i++) { if ((ai.expressions[i]) instanceof StringLiteral) { StringLiteral expression = (StringLiteral) ai.expressions[i]; valuesSetBuilder.add(expression.constant.stringValue().toLowerCase(Locale.ENGLISH)); } else { suppressionAnnotationWarning(a, "Unable to analyze SuppressWarnings annotation, " + ai.expressions[i].toString() + " not a string constant."); } } return valuesSetBuilder.build(); } else { suppressionAnnotationWarning(a, "Unable to analyze SuppressWarnings annotation, " + valueExpr.toString() + " not a string constant."); } } } return ImmutableSet.of(); } private final CheckerState checkerState; private final CompilationUnitDeclaration cud; private final List cudImports; private final Map jsniMethods; private final Map jsniRefs; private final Stack> suppressWarningsStack = new Stack>(); private final TypeResolver typeResolver; private JsniReferenceResolver(CompilationUnitDeclaration cud, List cudImports, CheckerState checkerState, TypeResolver typeResolver, Map jsniMethods, Map jsniRefs) { this.checkerState = checkerState; this.cud = cud; this.cudImports = cudImports; this.typeResolver = typeResolver; this.jsniMethods = jsniMethods; this.jsniRefs = jsniRefs; } private void resolve() { // First resolve the declarations. cud.traverse(new JsniDeclChecker(), cud.scope); } /** * Check whether the argument type is the long primitive type. If * the argument is null, returns false. */ private boolean containsLong(TypeBinding type) { if (!(type instanceof BaseTypeBinding)) { return false; } BaseTypeBinding btb = (BaseTypeBinding) type; if (btb.id == TypeIds.T_long) { return true; } return false; } private boolean hasUnsafeLongsAnnotation(MethodDeclaration meth, ClassScope scope) { if (meth.annotations == null) { return false; } for (Annotation annot : meth.annotations) { if (isUnsafeLongAnnotation(annot, scope)) { return true; } } return false; } private boolean isUnsafeLongAnnotation(Annotation annot, ClassScope scope) { if (annot.type == null) { return false; } TypeBinding resolved = annot.type.resolveType(scope); if (resolved == null || !(resolved instanceof ReferenceBinding)) { return false; } ReferenceBinding rb = (ReferenceBinding) resolved; if (CharOperation.equals(rb.compoundName, UNSAFE_LONG_ANNOTATION_CHARS)) { return true; } return false; } private void longAccessError(ASTNode node, String message) { GWTProblem.recordError(node, cud, message, new InstalledHelpInfo( "longJsniRestriction.html")); } private void suppressionAnnotationWarning(ASTNode node, String message) { GWTProblem.recordProblem(node, cud.compilationResult(), message, null, ProblemSeverities.Warning); } private static void resolveJsniRef(JsniRef jsniRef, FieldBinding fieldBinding) { if (fieldBinding == null) { return; } jsniRef.setResolvedClassName(JdtUtil.getSourceName(fieldBinding.declaringClass)); jsniRef.setResolvedMemberWithSignature(new String(fieldBinding.name)); } private static void resolveJsniRef(JsniRef jsniRef, MethodBinding methodBinding) { if (methodBinding == null) { return; } ReferenceBinding declaringClassBinding = methodBinding.declaringClass; jsniRef.setResolvedClassName(JdtUtil.getSourceName(declaringClassBinding)); jsniRef.setResolvedMemberWithSignature(JdtUtil.formatMethodSignature(methodBinding)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy