/* * 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.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.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.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.ImportReference; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; 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 java.util.Arrays; import java.util.List; 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. * <p> * * 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 (*). * <p> * * 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. * <p> * In addition in the following instances involving longs warning will be emitted to remind uses * that GWT longs are not JavaScript numbers: * <ul> * <li>JSNI methods with a parameter or return type of long or an array whose base type is long. * </li> * <li>Access from JSNI to a field whose type is long or an array whose base type is long.</li> * <li>Access from JSNI to a method with a parameter or return type of long or an array whose base * type is long.</li> * <li>JSNI references to anonymous classes.</li> * </ul> */ 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(JdtUtil.getSuppressedWarnings(meth.annotations)); return true; } @Override public boolean visit(TypeDeclaration typeDeclaration, ClassScope scope) { suppressWarningsStack.push(JdtUtil.getSuppressedWarnings(typeDeclaration.annotations)); return true; } @Override public boolean visit(TypeDeclaration typeDeclaration, CompilationUnitScope scope) { suppressWarningsStack.push(JdtUtil.getSuppressedWarnings(typeDeclaration.annotations)); return true; } @Override public boolean visitValid(TypeDeclaration typeDeclaration, BlockScope scope) { suppressWarningsStack.push(JdtUtil.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 = JdtUtil.getBinaryName(declaringClass) + "$" + originalName.replace('.', '$'); if (typeResolver.resolveType(fullClassName) != null) { jsniRef.setResolvedClassName(JdtUtil.getSourceName(declaringClass) + "." + originalName); 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 currentPackageBinaryClassName = JdtUtil.getBinaryName( CharOperation.charToString(method.binding.declaringClass.qualifiedPackageName()), originalName); if (typeResolver.resolveType(currentPackageBinaryClassName) != null) { jsniRef.setResolvedClassName( JdtUtil.getSourceName( CharOperation.charToString(method.binding.declaringClass.qualifiedPackageName()), originalName)); return; } // 5. Check to see if this name is resolvable as an import on demand. List<String> 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.replace('.', '$'); if (typeResolver.resolveType(fullClassName) != null) { jsniRef.setResolvedClassName(importPackage + "." + originalName); 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<MethodBinding> 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 (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_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_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: * <ul> * <li> %1$s -> full original jsni string </li> * <li> %2$s -> full original jsni classname </li> * <li> %3$s -> full original jsni membername </li> * <li> %4$s -> full original jsni memberspec </li> * <li> %5$s -> full resolved jsni string </li> * <li> %6$s -> full resolved jsni classname </li> * <li> %7$s -> full resolved jsni member with signature </li> * </ul> */ 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<String> 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<MethodBinding> getMatchingMethods(ReferenceBinding clazz, JsniRef jsniRef) { assert jsniRef.isMethod(); List<MethodBinding> foundMethods = Lists.newArrayList(); String methodName = jsniRef.memberName(); if ("new".equals(methodName)) { MethodBinding constructorBinding = getMatchingConstructor(clazz, jsniRef); if (constructorBinding != null) { foundMethods.add(constructorBinding); } } else { Queue<ReferenceBinding> 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 = "<init>".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<ImportReference> cudOriginalImports, Map<MethodDeclaration, JsniMethod> jsniMethods, Map<String, Binding> jsniRefs, TypeResolver typeResolver) { new JsniReferenceResolver(cud, cudOriginalImports, typeResolver, jsniMethods, jsniRefs) .resolve(); } private final CompilationUnitDeclaration cud; private final List<ImportReference> cudImports; private final Map<MethodDeclaration, JsniMethod> jsniMethods; private final Map<String, Binding> jsniRefs; private final Stack<Set<String>> suppressWarningsStack = new Stack<Set<String>>(); private final TypeResolver typeResolver; private JsniReferenceResolver(CompilationUnitDeclaration cud, List<ImportReference> cudImports, TypeResolver typeResolver, Map<MethodDeclaration, JsniMethod> jsniMethods, Map<String, Binding> jsniRefs) { 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 <code>long</code> primitive type. If * the argument is <code>null</code>, returns <code>false</code>. */ 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 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)); } }