/* * SonarQube Java * Copyright (C) 2012-2016 SonarSource SA * mailto:contact AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.java.resolve; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import org.sonar.java.ast.api.JavaKeyword; import org.sonar.java.model.AbstractTypedTree; import org.sonar.java.model.expression.ConditionalExpressionTreeImpl; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.ConditionalExpressionTree; import org.sonar.plugins.java.api.tree.IdentifierTree; import org.sonar.plugins.java.api.tree.LambdaExpressionTree; import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; import org.sonar.plugins.java.api.tree.MethodReferenceTree; import org.sonar.plugins.java.api.tree.Tree; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; /** * Routines for name resolution. * <p/> * Lookup by name and then filter by type is performant, because amount of symbols with same name are relatively small. * <p/> * Naming conventions: * env - is the environment where the symbol was mentioned * site - is the type of which symbol is a member * name - is the symbol's name * <p/> * TODO site should be represented by class Type */ public class Resolve { private static final String CONSTRUCTOR_NAME = "<init>"; private final JavaSymbolNotFound symbolNotFound = new JavaSymbolNotFound(); private final BytecodeCompleter bytecodeCompleter; private final TypeSubstitutionSolver typeSubstitutionSolver; private final Types types = new Types(); private final Symbols symbols; public Resolve(Symbols symbols, BytecodeCompleter bytecodeCompleter, ParametrizedTypeCache parametrizedTypeCache) { this.symbols = symbols; this.bytecodeCompleter = bytecodeCompleter; this.typeSubstitutionSolver = new TypeSubstitutionSolver(parametrizedTypeCache, symbols); ParametrizedTypeJavaType.typeSubstitutionSolver = typeSubstitutionSolver; } @Nullable private static JavaSymbol.TypeJavaSymbol superclassSymbol(JavaSymbol.TypeJavaSymbol c) { JavaType supertype = c.getSuperclass(); return supertype == null ? null : supertype.symbol; } public JavaSymbol.TypeJavaSymbol registerClass(JavaSymbol.TypeJavaSymbol classSymbol) { return bytecodeCompleter.registerClass(classSymbol); } public Scope createStarImportScope(JavaSymbol owner) { return new Scope.StarImportScope(owner, bytecodeCompleter); } public Scope createStaticStarImportScope(JavaSymbol owner) { return new Scope.StaticStarImportScope(owner, bytecodeCompleter); } public JavaType resolveTypeSubstitution(JavaType type, JavaType definition) { return typeSubstitutionSolver.applySiteSubstitution(type, definition); } public List<JavaType> resolveTypeSubstitution(List<JavaType> formals, TypeSubstitution substitution) { return typeSubstitutionSolver.applySubstitutionToFormalParameters(formals, substitution); } public JavaType applySubstitution(JavaType type, TypeSubstitution substitution) { return typeSubstitutionSolver.applySubstitution(type, substitution); } public JavaType resolveTypeSubstitutionWithDiamondOperator(ParametrizedTypeJavaType type, JavaType definition) { ParametrizedTypeJavaType result = type; if (definition.isParameterized()) { TypeSubstitution substitution = TypeSubstitutionSolver.substitutionFromSuperType(type, (ParametrizedTypeJavaType) definition); result = (ParametrizedTypeJavaType) typeSubstitutionSolver.applySubstitution(type, substitution); } return typeSubstitutionSolver.erasureSubstitution(result); } public JavaType parametrizedTypeWithErasure(ParametrizedTypeJavaType type) { return typeSubstitutionSolver.erasureSubstitution(type); } /** * Finds field with given name. */ private Resolution findField(Env env, JavaSymbol.TypeJavaSymbol site, String name, JavaSymbol.TypeJavaSymbol c) { Resolution bestSoFar = unresolved(); Resolution resolution = new Resolution(); for (JavaSymbol symbol : c.members().lookup(name)) { if (symbol.kind == JavaSymbol.VAR) { if(isAccessible(env, site, symbol)) { resolution.symbol = symbol; resolution.type = typeSubstitutionSolver.applySiteSubstitution(symbol.type, c.type); return resolution; } else { return Resolution.resolution(new AccessErrorJavaSymbol(symbol, Symbols.unknownType)); } } } if (c.getSuperclass() != null) { resolution = findField(env, site, name, c.getSuperclass().symbol); if (resolution.symbol.kind < bestSoFar.symbol.kind) { resolution.type = typeSubstitutionSolver.applySiteSubstitution(resolution.symbol.type, c.getSuperclass()); bestSoFar = resolution; } } for (JavaType interfaceType : c.getInterfaces()) { resolution = findField(env, site, name, interfaceType.symbol); if (resolution.symbol.kind < bestSoFar.symbol.kind) { bestSoFar = resolution; } } return bestSoFar; } /** * Finds variable or field with given name. */ private Resolution findVar(Env env, String name) { Resolution bestSoFar = unresolved(); Env env1 = env; while (env1.outer != null) { Resolution sym = new Resolution(); for (JavaSymbol symbol : env1.scope.lookup(name)) { if (symbol.kind == JavaSymbol.VAR) { sym.symbol = symbol; } } if (sym.symbol == null) { sym = findField(env1, env1.enclosingClass, name, env1.enclosingClass); } if (sym.symbol.kind < JavaSymbol.ERRONEOUS) { // symbol exists return sym; } else if (sym.symbol.kind < bestSoFar.symbol.kind) { bestSoFar = sym; } env1 = env1.outer; } JavaSymbol symbol = findVarInStaticImport(env, name); if (symbol.kind < JavaSymbol.ERRONEOUS) { // symbol exists return Resolution.resolution(symbol); } else if (symbol.kind < bestSoFar.symbol.kind) { bestSoFar = Resolution.resolution(symbol); } return bestSoFar; } private JavaSymbol findVarInStaticImport(Env env, String name) { JavaSymbol bestSoFar = symbolNotFound; for (JavaSymbol symbol : env.namedImports.lookup(name)) { if ((JavaSymbol.VAR & symbol.kind) != 0) { return symbol; } } for (JavaSymbol symbol : env.staticStarImports.lookup(name)) { if ((JavaSymbol.VAR & symbol.kind) != 0) { return symbol; } } return bestSoFar; } private JavaSymbol findMemberType(Env env, JavaSymbol.TypeJavaSymbol site, String name, JavaSymbol.TypeJavaSymbol c) { JavaSymbol bestSoFar = symbolNotFound; for (JavaSymbol symbol : c.members().lookup(name)) { if (symbol.kind == JavaSymbol.TYP) { return isAccessible(env, site, symbol) ? symbol : new AccessErrorJavaSymbol(symbol, Symbols.unknownType); } } if (c.getSuperclass() != null) { JavaSymbol symbol = findMemberType(env, site, name, c.getSuperclass().symbol); if (symbol.kind < bestSoFar.kind) { bestSoFar = symbol; } } if (c.getInterfaces() == null) { // Invariant to check that interfaces are not set only when we are looking into the symbol we are currently completing. // required for generics Preconditions.checkState(c.completing, "interfaces of a symbol not currently completing are not set."); Preconditions.checkState(c == site); } else { for (JavaType interfaceType : c.getInterfaces()) { JavaSymbol symbol = findMemberType(env, site, name, interfaceType.symbol); if (symbol.kind < bestSoFar.kind) { bestSoFar = symbol; } } } return bestSoFar; } /** * Finds type with given name. */ private JavaSymbol findType(Env env, String name) { JavaSymbol bestSoFar = symbolNotFound; for (Env env1 = env; env1 != null; env1 = env1.outer) { for (JavaSymbol symbol : env1.scope.lookup(name)) { if (symbol.kind == JavaSymbol.TYP) { return symbol; } } if (env1.outer != null) { JavaSymbol symbol = findMemberType(env1, env1.enclosingClass, name, env1.enclosingClass); if (symbol.kind < JavaSymbol.ERRONEOUS) { // symbol exists return symbol; } else if (symbol.kind < bestSoFar.kind) { bestSoFar = symbol; } } } //checks predefined types JavaSymbol predefinedSymbol = findMemberType(env, symbols.predefClass, name, symbols.predefClass); if (predefinedSymbol.kind < bestSoFar.kind) { return predefinedSymbol; } //JLS8 6.4.1 Shadowing rules //named imports for (JavaSymbol symbol : env.namedImports.lookup(name)) { if (symbol.kind == JavaSymbol.TYP) { return symbol; } } //package types JavaSymbol sym = findIdentInPackage(env.packge, name, JavaSymbol.TYP); if (sym.kind < bestSoFar.kind) { return sym; } //on demand imports for (JavaSymbol symbol : env.starImports.lookup(name)) { if (symbol.kind == JavaSymbol.TYP) { return symbol; } } //java.lang JavaSymbol.PackageJavaSymbol javaLang = bytecodeCompleter.enterPackage("java.lang"); for (JavaSymbol symbol : javaLang.completedMembers().lookup(name)) { if (symbol.kind == JavaSymbol.TYP) { return symbol; } } return bestSoFar; } /** * @param kind subset of {@link JavaSymbol#VAR}, {@link JavaSymbol#TYP}, {@link JavaSymbol#PCK} */ public Resolution findIdent(Env env, String name, int kind) { Resolution bestSoFar = unresolved(); if ((kind & JavaSymbol.VAR) != 0) { Resolution res = findVar(env, name); if (res.symbol.kind < JavaSymbol.ERRONEOUS) { // symbol exists return res; } else if (res.symbol.kind < bestSoFar.symbol.kind) { bestSoFar = res; } } if ((kind & JavaSymbol.TYP) != 0) { Resolution res = new Resolution(); res.symbol = findType(env, name); if (res.symbol.kind < JavaSymbol.ERRONEOUS) { // symbol exists return res; } else if (res.symbol.kind < bestSoFar.symbol.kind) { bestSoFar = res; } } if ((kind & JavaSymbol.PCK) != 0) { Resolution res = new Resolution(); res.symbol = findIdentInPackage(symbols.defaultPackage, name, JavaSymbol.PCK); if (res.symbol.kind < JavaSymbol.ERRONEOUS) { // symbol exists return res; } else if (res.symbol.kind < bestSoFar.symbol.kind) { bestSoFar = res; } } return bestSoFar; } /** * @param kind subset of {@link JavaSymbol#TYP}, {@link JavaSymbol#PCK} */ public JavaSymbol findIdentInPackage(JavaSymbol site, String name, int kind) { String fullname = bytecodeCompleter.formFullName(name, site); JavaSymbol bestSoFar = symbolNotFound; //Try to find a type matching the name. if ((kind & JavaSymbol.TYP) != 0) { JavaSymbol sym = bytecodeCompleter.loadClass(fullname); if (sym.kind < bestSoFar.kind) { bestSoFar = sym; } } //We did not find the class so identifier must be a package. if ((kind & JavaSymbol.PCK) != 0 && bestSoFar.kind >= symbolNotFound.kind) { bestSoFar = bytecodeCompleter.enterPackage(fullname); } return bestSoFar; } /** * @param kind subset of {@link JavaSymbol#VAR}, {@link JavaSymbol#TYP} */ public Resolution findIdentInType(Env env, JavaSymbol.TypeJavaSymbol site, String name, int kind) { Resolution bestSoFar = unresolved(); Resolution resolution; JavaSymbol symbol; if ((kind & JavaSymbol.VAR) != 0) { resolution = findField(env, site, name, site); if (resolution.symbol.kind < JavaSymbol.ERRONEOUS) { // symbol exists return resolution; } else if (resolution.symbol.kind < bestSoFar.symbol.kind) { bestSoFar = resolution; } } if ((kind & JavaSymbol.TYP) != 0) { symbol = findMemberType(env, site, name, site); if (symbol.kind < JavaSymbol.ERRONEOUS) { // symbol exists return Resolution.resolution(symbol); } else if (symbol.kind < bestSoFar.symbol.kind) { bestSoFar = Resolution.resolution(symbol); } } return bestSoFar; } /** * Finds method matching given name and types of arguments. */ public Resolution findMethod(Env env, String name, List<JavaType> argTypes, List<JavaType> typeParamTypes) { Resolution bestSoFar = unresolved(); Env env1 = env; while (env1.outer != null) { Resolution res = findMethod(env1, env1.enclosingClass.getType(), name, argTypes, typeParamTypes); if (res.symbol.kind < JavaSymbol.ERRONEOUS) { // symbol exists return res; } else if (res.symbol.kind < bestSoFar.symbol.kind) { bestSoFar = res; } env1 = env1.outer; } Resolution res = findMethodInStaticImport(env, name, argTypes, typeParamTypes); if (res.symbol.kind < JavaSymbol.ERRONEOUS) { // symbol exists return res; } else if (res.symbol.kind < bestSoFar.symbol.kind) { bestSoFar = res; } return bestSoFar; } private Resolution findMethodInStaticImport(Env env, String name, List<JavaType> argTypes, List<JavaType> typeParamTypes) { Resolution bestSoFar = unresolved(); JavaType enclosingType = env.enclosingClass.getType(); bestSoFar = lookupInScope(env, enclosingType, enclosingType, name, argTypes, typeParamTypes, false, env.namedImports, bestSoFar); if (bestSoFar.symbol.kind < JavaSymbol.ERRONEOUS) { // symbol exists return bestSoFar; } bestSoFar = lookupInScope(env, enclosingType, enclosingType, name, argTypes, typeParamTypes, false, env.staticStarImports, bestSoFar); if (bestSoFar.symbol.kind < JavaSymbol.ERRONEOUS) { // symbol exists return bestSoFar; } bestSoFar = lookupInScope(env, enclosingType, enclosingType, name, argTypes, typeParamTypes, true, env.namedImports, bestSoFar); if (bestSoFar.symbol.kind < JavaSymbol.ERRONEOUS) { // symbol exists return bestSoFar; } bestSoFar = lookupInScope(env, enclosingType, enclosingType, name, argTypes, typeParamTypes, true, env.staticStarImports, bestSoFar); return bestSoFar; } public Resolution findMethod(Env env, JavaType site, String name, List<JavaType> argTypes) { return findMethod(env, site, site, name, argTypes, ImmutableList.<JavaType>of(), false); } public Resolution findMethod(Env env, JavaType site, String name, List<JavaType> argTypes, List<JavaType> typeParams) { return findMethod(env, site, site, name, argTypes, typeParams, false); } private Resolution findMethod(Env env, JavaType callSite, JavaType site, String name, List<JavaType> argTypes, List<JavaType> typeParams) { return findMethod(env, callSite, site, name, argTypes, typeParams, false); } private Resolution findConstructor(Env env, JavaType site, List<JavaType> argTypes, List<JavaType> typeParams, boolean autoboxing) { List<JavaType> newArgTypes = argTypes; JavaSymbol owner = site.symbol.owner(); if (!owner.isPackageSymbol() && !site.symbol.isStatic()) { // JLS8 - 8.8.1 & 8.8.9 : constructors of inner class have an implicit first arg of its directly enclosing class type newArgTypes = ImmutableList.<JavaType>builder().add(owner.enclosingClass().type).addAll(argTypes).build(); } return findMethod(env, site, site, CONSTRUCTOR_NAME, newArgTypes, typeParams, autoboxing); } private Resolution findMethod(Env env, JavaType callSite, JavaType site, String name, List<JavaType> argTypes, List<JavaType> typeParams, boolean autoboxing) { JavaType superclass = site.getSymbol().getSuperclass(); Resolution bestSoFar = unresolved(); // handle constructors if ("this".equals(name)) { return findConstructor(env, site, argTypes, typeParams, autoboxing); } else if ("super".equals(name)) { if (superclass == null) { return bestSoFar; } return findConstructor(env, superclass, argTypes, typeParams, autoboxing); } bestSoFar = lookupInScope(env, callSite, site, name, argTypes, typeParams, autoboxing, site.getSymbol().members(), bestSoFar); //look in supertypes for more specialized method (overloading). if (superclass != null) { Resolution method = findMethod(env, callSite, superclass, name, argTypes, typeParams); method.type = typeSubstitutionSolver.applySiteSubstitution(method.type, site, superclass); Resolution best = selectBest(env, superclass, callSite, argTypes, typeParams, method.symbol, bestSoFar, autoboxing); if (best.symbol == method.symbol) { bestSoFar = method; } } for (JavaType interfaceType : site.getSymbol().getInterfaces()) { Resolution method = findMethod(env, callSite, interfaceType, name, argTypes, typeParams); method.type = typeSubstitutionSolver.applySiteSubstitution(method.type, site, interfaceType); Resolution best = selectBest(env, interfaceType, callSite, argTypes, typeParams, method.symbol, bestSoFar, autoboxing); if (best.symbol == method.symbol) { bestSoFar = method; } } if(bestSoFar.symbol.kind >= JavaSymbol.ERRONEOUS && !autoboxing) { bestSoFar = findMethod(env, callSite, site, name, argTypes, typeParams, true); } return bestSoFar; } private Resolution lookupInScope(Env env, JavaType callSite, JavaType site, String name, List<JavaType> argTypes, List<JavaType> typeParams, boolean autoboxing, Scope scope, Resolution bestFound) { Resolution bestSoFar = bestFound; // look in site members for (JavaSymbol symbol : scope.lookup(name)) { if (symbol.kind == JavaSymbol.MTH) { Resolution best = selectBest(env, site, callSite, argTypes, typeParams, symbol, bestSoFar, autoboxing); if (best.symbol == symbol) { bestSoFar = best; } } } return bestSoFar; } /** * @param candidate candidate * @param bestSoFar previously found best match */ private Resolution selectBest(Env env, JavaType defSite, JavaType callSite, List<JavaType> argTypes, List<JavaType> typeParams, JavaSymbol candidate, Resolution bestSoFar, boolean autoboxing) { JavaSymbol.TypeJavaSymbol siteSymbol = callSite.symbol; // TODO get rid of null check if (candidate.kind >= JavaSymbol.ERRONEOUS || !isInheritedIn(candidate, siteSymbol) || candidate.type == null) { return bestSoFar; } JavaSymbol.MethodJavaSymbol methodJavaSymbol = (JavaSymbol.MethodJavaSymbol) candidate; if(!hasCompatibleArity(methodJavaSymbol.parameterTypes().size(), argTypes.size(), methodJavaSymbol.isVarArgs())) { return bestSoFar; } TypeSubstitution substitution = typeSubstitutionSolver.getTypeSubstitution(methodJavaSymbol, callSite, typeParams, argTypes); if (substitution == null) { return bestSoFar; } List<JavaType> formals = ((MethodJavaType) methodJavaSymbol.type).argTypes; formals = typeSubstitutionSolver.applySiteSubstitutionToFormalParameters(formals, callSite); if(defSite != callSite) { formals = typeSubstitutionSolver.applySiteSubstitutionToFormalParameters(formals, defSite); } formals = typeSubstitutionSolver.applySubstitutionToFormalParameters(formals, substitution); if (!isArgumentsAcceptable(env, argTypes, formals, methodJavaSymbol.isVarArgs(), autoboxing)) { return bestSoFar; } // TODO ambiguity, errors, ... if (!isAccessible(env, siteSymbol, candidate)) { Resolution resolution = new Resolution(new AccessErrorJavaSymbol(candidate, Symbols.unknownType)); resolution.type = Symbols.unknownType; return resolution; } JavaSymbol mostSpecific = selectMostSpecific(env, candidate, bestSoFar.symbol, argTypes, substitution, callSite); if (mostSpecific.isKind(JavaSymbol.AMBIGUOUS)) { // same signature, we keep the first symbol found (overrides the other one). return bestSoFar; } Resolution resolution = new Resolution(mostSpecific); JavaSymbol.MethodJavaSymbol mostSpecificMethod = (JavaSymbol.MethodJavaSymbol) mostSpecific; List<JavaType> thrownTypes = ((MethodJavaType) mostSpecific.type).thrown; JavaType returnType = ((MethodJavaType) mostSpecificMethod.type).resultType; if(applicableWithUncheckedConversion(mostSpecificMethod, defSite, typeParams) && !mostSpecificMethod.isConstructor()) { returnType = returnType.erasure(); thrownTypes = erasure(thrownTypes); } else { returnType = typeSubstitutionSolver.getReturnType(returnType, defSite, callSite, substitution, mostSpecificMethod); } resolution.type = new MethodJavaType(formals, returnType, thrownTypes, defSite.symbol); return resolution; } private static List<JavaType> erasure(List<JavaType> types) { List<JavaType> erasedTypes = new ArrayList<>(types.size()); for (JavaType type : types) { erasedTypes.add(type.erasure()); } return erasedTypes; } private static boolean applicableWithUncheckedConversion(JavaSymbol.MethodJavaSymbol candidate, JavaType callSite, List<JavaType> typeParams) { return !candidate.isStatic() && isRawTypeOfParametrizedType(callSite) && typeParams.isEmpty(); } private static boolean isRawTypeOfParametrizedType(JavaType site) { return !site.isParameterized() && !site.symbol.typeVariableTypes.isEmpty(); } private static boolean hasCompatibleArity(int formalArgSize, int argSize, boolean isVarArgs) { if(isVarArgs) { return argSize - formalArgSize >= -1; } return formalArgSize == argSize; } /** * @param argTypes types of arguments * @param formals types of formal parameters of method */ private boolean isArgumentsAcceptable(Env env, List<JavaType> argTypes, List<JavaType> formals, boolean isVarArgs, boolean autoboxing) { int argsSize = argTypes.size(); int formalsSize = formals.size(); int nbArgToCheck = argsSize - formalsSize; if (isVarArgs) { // check at least last parameter for varargs compatibility nbArgToCheck++; } for (int i = 1; i <= nbArgToCheck; i++) { ArrayJavaType lastFormal = (ArrayJavaType) formals.get(formalsSize - 1); JavaType argType = argTypes.get(argsSize - i); // check type of element of array or if we invoke with an array that it is a compatible array type if (!isAcceptableType(env, argType, lastFormal.elementType, autoboxing) && (nbArgToCheck != 1 || !isAcceptableType(env, argType, lastFormal, autoboxing))) { return false; } } for (int i = 0; i < argsSize - nbArgToCheck; i++) { JavaType arg = argTypes.get(i); JavaType formal = formals.get(i); if (!isAcceptableType(env, arg, formal, autoboxing)) { return false; } } return true; } private boolean isAcceptableType(Env env, JavaType arg, JavaType formal, boolean autoboxing) { if(arg.isTagged(JavaType.DEFERRED)) { return isAcceptableDeferredType(env, (DeferredType) arg, formal); } if(formal.isTagged(JavaType.TYPEVAR) && !arg.isTagged(JavaType.TYPEVAR)) { return subtypeOfTypeVar(arg, (TypeVariableJavaType) formal); } if (formal.isArray() && arg.isArray()) { return isAcceptableType(env, ((ArrayJavaType) arg).elementType(), ((ArrayJavaType) formal).elementType(), autoboxing); } if (arg.isParameterized() || formal.isParameterized() || isWilcardType(arg) || isWilcardType(formal)) { return callWithRawType(arg, formal) || types.isSubtype(arg, formal) || isAcceptableByAutoboxing(arg, formal.erasure()); } // fall back to behavior based on erasure return types.isSubtype(arg.erasure(), formal.erasure()) || (autoboxing && isAcceptableByAutoboxing(arg, formal.erasure())); } private boolean isAcceptableDeferredType(Env env, DeferredType arg, JavaType formal) { AbstractTypedTree tree = arg.tree(); List<JavaType> samMethodArgs = findSamMethodArgs(formal); if (tree.is(Tree.Kind.METHOD_REFERENCE)) { return validMethodReference(env, (MethodReferenceTree) tree, samMethodArgs); } // we accept all deferred type as we will resolve this later, but reject lambdas with incorrect arity return !tree.is(Tree.Kind.LAMBDA_EXPRESSION) || ((LambdaExpressionTree) tree).parameters().size() == samMethodArgs.size(); } private boolean validMethodReference(Env env, MethodReferenceTree tree, List<JavaType> samMethodArgs) { if (isArrayConstructor(tree)) { return true; } Resolution resolution = findMethodReference(env, samMethodArgs, tree); return !resolution.symbol.isUnknown(); } Resolution findMethodReference(Env env, List<JavaType> samMethodArgs, MethodReferenceTree methodRefTree) { Tree expression = methodRefTree.expression(); JavaType expressionType = (JavaType) ((AbstractTypedTree) expression).symbolType(); String methodName = getMethodReferenceMethodName(methodRefTree.method().name()); Resolution resolution = findMethod(env, expressionType, methodName, samMethodArgs); // JLS ยง15.13.1 if (secondSearchRequired(expression, expressionType, resolution.symbol, samMethodArgs)) { resolution = findMethod(env, expressionType, methodName, samMethodArgs.stream().skip(1).collect(Collectors.toList())); } return resolution; } private static String getMethodReferenceMethodName(String methodName) { return JavaKeyword.NEW.getValue().equals(methodName) ? CONSTRUCTOR_NAME : methodName; } private static boolean isArrayConstructor(MethodReferenceTree tree) { JavaType expressionType = (JavaType) ((AbstractTypedTree) tree.expression()).symbolType(); String methodName = tree.method().name(); return expressionType.isArray() && JavaKeyword.NEW.getValue().equals(methodName); } private static boolean secondSearchRequired(Tree expression, JavaType expressionType, JavaSymbol symbol, List<JavaType> samMethodArgs) { return isMethodRefOnType(expression) && firstParamSubtypeOfRefType(expressionType, samMethodArgs) && (symbol.isUnknown() || !symbol.isStatic()); } private static boolean isMethodRefOnType(Tree expression) { if (expression.is(Tree.Kind.MEMBER_SELECT)) { return ((MemberSelectExpressionTree) expression).identifier().symbol().isTypeSymbol(); } else if (expression.is(Tree.Kind.IDENTIFIER)) { return ((IdentifierTree) expression).symbol().isTypeSymbol(); } return false; } private static boolean firstParamSubtypeOfRefType(JavaType expressionType, List<JavaType> samMethodArgs) { return samMethodArgs.isEmpty() || samMethodArgs.get(0).isSubtypeOf(expressionType.erasure()); } private boolean callWithRawType(JavaType arg, JavaType formal) { return formal.isParameterized() && !arg.isParameterized() && types.isSubtype(arg, formal.erasure()); } private static boolean subtypeOfTypeVar(JavaType arg, TypeVariableJavaType formal) { for (JavaType bound : formal.bounds()) { if ((bound.isTagged(JavaType.TYPEVAR) && !subtypeOfTypeVar(arg, (TypeVariableJavaType) bound)) || !arg.isSubtypeOf(bound)) { return false; } } return true; } private static boolean isWilcardType(JavaType type) { return type.isTagged(JavaType.WILDCARD); } private boolean isAcceptableByAutoboxing(JavaType expressionType, JavaType formalType) { if (expressionType.isPrimitive()) { return types.isSubtype(symbols.boxedTypes.get(expressionType), formalType); } else { JavaType unboxedType = symbols.boxedTypes.inverse().get(expressionType); if (unboxedType != null) { return types.isSubtype(unboxedType, formalType); } } return false; } /** * JLS7 15.12.2.5. Choosing the Most Specific Method */ private JavaSymbol selectMostSpecific(Env env, JavaSymbol m1, JavaSymbol m2, List<JavaType> argTypes, TypeSubstitution m1Substitution, JavaType callSite) { // FIXME get rig of null check if (m2.type == null || !m2.isKind(JavaSymbol.MTH)) { return m1; } TypeSubstitution m2Substitution = null; if (((JavaSymbol.MethodJavaSymbol) m2).isParametrized()) { m2Substitution = typeSubstitutionSolver.getTypeSubstitution((JavaSymbol.MethodJavaSymbol) m2, callSite, ImmutableList.of(), argTypes); } if (m2Substitution == null) { m2Substitution = new TypeSubstitution(); } boolean m1SignatureMoreSpecific = isSignatureMoreSpecific(env, m1, m2, argTypes, m1Substitution, m2Substitution); boolean m2SignatureMoreSpecific = isSignatureMoreSpecific(env, m2, m1, argTypes, m1Substitution, m2Substitution); if (m1SignatureMoreSpecific && m2SignatureMoreSpecific) { return new AmbiguityErrorJavaSymbol(); } else if (m1SignatureMoreSpecific) { return m1; } else if (m2SignatureMoreSpecific) { return m2; } return new AmbiguityErrorJavaSymbol(); } /** * @return true, if signature of m1 is more specific than signature of m2 */ private boolean isSignatureMoreSpecific(Env env, JavaSymbol m1, JavaSymbol m2, List<JavaType> argTypes, TypeSubstitution m1Substitution, TypeSubstitution m2Substitution) { List<JavaType> m1ArgTypes = ((MethodJavaType) m1.type).argTypes; List<JavaType> m2ArgTypes = ((MethodJavaType) m2.type).argTypes; JavaSymbol.MethodJavaSymbol methodJavaSymbol = (JavaSymbol.MethodJavaSymbol) m1; boolean m1VarArity = methodJavaSymbol.isVarArgs(); boolean m2VarArity = ((JavaSymbol.MethodJavaSymbol) m2).isVarArgs(); if (m1VarArity != m2VarArity) { // last arg is an array boolean lastArgIsArray = !argTypes.isEmpty() && argTypes.get(argTypes.size() -1).isArray() && (argTypes.size() == m2ArgTypes.size() || argTypes.size() == m1ArgTypes.size()); // general case : prefer strict arity invocation over varArity, so if m2 is variadic, m1 is most specific, but not if last arg of invocation is an array return lastArgIsArray ^ m2VarArity; } if (m1VarArity) { m1ArgTypes = expandVarArgsToFitSize(m1ArgTypes, m2ArgTypes.size()); } if(!hasCompatibleArity(m1ArgTypes.size(), m2ArgTypes.size(), m2VarArity)) { return false; } m1ArgTypes = typeSubstitutionSolver.applySubstitutionToFormalParameters(m1ArgTypes, m1Substitution); m2ArgTypes = typeSubstitutionSolver.applySubstitutionToFormalParameters(m2ArgTypes, m2Substitution); return isArgumentsAcceptable(env, m1ArgTypes, m2ArgTypes, m2VarArity, false); } private static List<JavaType> expandVarArgsToFitSize(List<JavaType> m1ArgTypes, int size) { List<JavaType> newArgTypes = new ArrayList<>(m1ArgTypes); int m1ArgTypesSize = newArgTypes.size(); int m1ArgTypesLast = m1ArgTypesSize - 1; Type lastElementType = ((Type.ArrayType) newArgTypes.get(m1ArgTypesLast)).elementType(); // replace last element type from GivenType[] to GivenType newArgTypes.set(m1ArgTypesLast, (JavaType) lastElementType); // if m1ArgTypes smaller than size pad it with lastElementType for (int i = m1ArgTypesSize; i < size - 1; i++) { if (i < newArgTypes.size()) { newArgTypes.set(i, (JavaType) lastElementType); } else { newArgTypes.add((JavaType) lastElementType); } } return newArgTypes; } /** * Is class accessible in given environment? */ @VisibleForTesting static boolean isAccessible(Env env, JavaSymbol.TypeJavaSymbol c) { final boolean result; switch (c.flags() & Flags.ACCESS_FLAGS) { case Flags.PRIVATE: result = sameOutermostClass(env.enclosingClass, c.owner()); break; case 0: result = env.packge == c.packge(); break; case Flags.PUBLIC: result = true; break; case Flags.PROTECTED: result = env.packge == c.packge() || isInnerSubClass(env.enclosingClass, c.owner()); break; default: throw new IllegalStateException(); } // TODO check accessibility of enclosing type: isAccessible(env, c.type.getEnclosingType()) return result; } /** * Is given class a subclass of given base class, or an inner class of a subclass? */ private static boolean isInnerSubClass(JavaSymbol.TypeJavaSymbol c, JavaSymbol base) { while (c != null && isSubClass(c, base)) { c = c.owner().enclosingClass(); } return c != null; } /** * Is given class a subclass of given base class? */ @VisibleForTesting static boolean isSubClass(@Nullable JavaSymbol.TypeJavaSymbol c, JavaSymbol base) { // TODO get rid of null check if (c == null) { return false; } // TODO see Javac if (c == base) { // same class return true; } else if ((base.flags() & Flags.INTERFACE) != 0) { // check if class implements base for (JavaType interfaceType : c.getInterfaces()) { if (isSubClass(interfaceType.symbol, base)) { return true; } } // check if superclass implements base return isSubClass(superclassSymbol(c), base); } else { // check if class extends base or its superclass extends base return isSubClass(superclassSymbol(c), base); } } /** * Is symbol accessible as a member of given class in given environment? * <p/> * Symbol is accessible only if not overridden by another symbol. If overridden, then strictly speaking it is not a member. */ private static boolean isAccessible(Env env, JavaSymbol.TypeJavaSymbol site, JavaSymbol symbol) { switch (symbol.flags() & Flags.ACCESS_FLAGS) { case Flags.PRIVATE: //if enclosing class is null, we are checking accessibility for imports so we return false. // no check of overriding, because private members cannot be overridden return env.enclosingClass != null && sameOutermostClass(env.enclosingClass, symbol.owner()) && isInheritedIn(symbol, site); case 0: return (env.packge == symbol.packge()) && isAccessible(env, site) && isInheritedIn(symbol, site) && notOverriddenIn(site, symbol); case Flags.PUBLIC: return isAccessible(env, site) && notOverriddenIn(site, symbol); case Flags.PROTECTED: return ((env.packge == symbol.packge()) || isProtectedAccessible(symbol, env.enclosingClass, site)) && isAccessible(env, site) && notOverriddenIn(site, symbol); default: throw new IllegalStateException(); } } static boolean sameOutermostClass(JavaSymbol s1, JavaSymbol s2) { return s1.outermostClass() == s2.outermostClass(); } private static boolean notOverriddenIn(JavaSymbol.TypeJavaSymbol site, JavaSymbol symbol) { // TODO see Javac return true; } /** * Is symbol inherited in given class? */ @VisibleForTesting static boolean isInheritedIn(JavaSymbol symbol, JavaSymbol.TypeJavaSymbol clazz) { switch (symbol.flags() & Flags.ACCESS_FLAGS) { case Flags.PUBLIC: return true; case Flags.PRIVATE: return symbol.owner() == clazz; case Flags.PROTECTED: // TODO see Javac return true; case 0: // TODO see Javac JavaSymbol.PackageJavaSymbol thisPackage = symbol.packge(); for (JavaSymbol.TypeJavaSymbol sup = clazz; sup != null && sup != symbol.owner(); sup = superclassSymbol(sup)) { if (sup.packge() != thisPackage) { return false; } } return true; default: throw new IllegalStateException(); } } private static boolean isProtectedAccessible(JavaSymbol symbol, JavaSymbol.TypeJavaSymbol c, JavaSymbol.TypeJavaSymbol site) { // TODO see Javac return true; } Type leastUpperBound(Set<Type> refTypes) { return typeSubstitutionSolver.leastUpperBound(refTypes); } Resolution unresolved() { Resolution resolution = new Resolution(symbolNotFound); resolution.type = Symbols.unknownType; return resolution; } public JavaType conditionalExpressionType(ConditionalExpressionTree tree, JavaType trueType, JavaType falseType) { if (trueType.isTagged(JavaType.DEFERRED)) { return falseType.isTagged(JavaType.DEFERRED) ? symbols.deferedType((ConditionalExpressionTreeImpl) tree) : falseType; } if (falseType.isTagged(JavaType.DEFERRED)) { return trueType; } if (trueType == falseType) { return trueType; } if (trueType.isTagged(JavaType.BOT)) { return falseType.isPrimitive() ? falseType.primitiveWrapperType() : falseType; } if (falseType.isTagged(JavaType.BOT)) { return trueType.isPrimitive() ? trueType.primitiveWrapperType() : trueType; } JavaType secondOperand = getPrimitive(trueType); JavaType thirdOperand = getPrimitive(falseType); if (secondOperand != null && thirdOperand != null && isNumericalConditionalExpression(secondOperand, thirdOperand)) { // If operand is a constant int that can fits a narrow type it should be narrowed. We always narrow to approximate things properly for // method resolution. if ((secondOperand.tag < thirdOperand.tag || secondOperand.isTagged(JavaType.INT)) && !thirdOperand.isTagged(JavaType.INT)) { return thirdOperand; } else { return secondOperand; } } return (JavaType) leastUpperBound(Sets.<Type>newHashSet(trueType, falseType)); } private static boolean isNumericalConditionalExpression(JavaType secondOperand, JavaType thirdOperand) { return secondOperand.isNumerical() && thirdOperand.isNumerical(); } @CheckForNull private static JavaType getPrimitive(JavaType primitiveOrWrapper) { if(primitiveOrWrapper.isPrimitiveWrapper()) { return primitiveOrWrapper.primitiveType(); } return primitiveOrWrapper.isPrimitive() ? primitiveOrWrapper : null; } public List<JavaType> findSamMethodArgs(Type type) { return findSamMethodArgsRecursively(type).orElse(new ArrayList<>()); } private Optional<List<JavaType>> findSamMethodArgsRecursively(@Nullable Type type) { if (type == null) { return Optional.empty(); } return getSamMethod((JavaType) type) .map(m -> ((MethodJavaType) m.type).argTypes) .map(samTypes -> applySamSubstitution(type, samTypes)); } private List<JavaType> applySamSubstitution(Type type, List<JavaType> samTypes) { List<JavaType> argTypes = typeSubstitutionSolver.applySiteSubstitutionToFormalParameters(samTypes, (JavaType) type); return argTypes.stream().map(argType -> { if (argType.isTagged(JavaType.WILDCARD)) { // JLS8 9.9 Function types : this is approximated for ? extends X types (cf JLS) return ((WildCardType) argType).bound; } return argType; }).collect(Collectors.toList()); } public Optional<JavaSymbol.MethodJavaSymbol> getSamMethod(JavaType lambdaType) { for (Symbol member : lambdaType.symbol().memberSymbols()) { if (isAbstractMethod(member)) { JavaSymbol.MethodJavaSymbol methodJavaSymbol = (JavaSymbol.MethodJavaSymbol) member; boolean isObjectMethod = isObjectMethod(methodJavaSymbol); if(!isObjectMethod) { return Optional.of(methodJavaSymbol); } } } for (ClassJavaType type : lambdaType.symbol.superTypes()) { Optional<JavaSymbol.MethodJavaSymbol> samMethod = getSamMethod(type); if (samMethod.isPresent()) { return samMethod; } } return Optional.empty(); } private boolean isObjectMethod(JavaSymbol.MethodJavaSymbol methodJavaSymbol) { JavaSymbol.MethodJavaSymbol overriddenSymbol = methodJavaSymbol.overriddenSymbol(); boolean isObjectMethod = false; while (overriddenSymbol != null && !isObjectMethod) { isObjectMethod = overriddenSymbol.owner.type == symbols.objectType; overriddenSymbol = overriddenSymbol.overriddenSymbol(); } return isObjectMethod; } private static boolean isAbstractMethod(Symbol member) { return member.isMethodSymbol() && member.isAbstract(); } /** * Resolution holds the symbol resolved and its type in this context. * This is required to handle type substitution for generics. */ static class Resolution { private JavaSymbol symbol; private JavaType type; private Resolution(JavaSymbol symbol) { this.symbol = symbol; } Resolution() { } static Resolution resolution(JavaSymbol symbol) { return new Resolution(symbol); } JavaSymbol symbol() { return symbol; } public JavaType type() { if (type == null) { if(symbol.isKind(JavaSymbol.MTH)) { return ((MethodJavaType)symbol.type).resultType; } if(symbol.isUnknown() || symbol.isKind(JavaSymbol.PCK)) { return Symbols.unknownType; } return symbol.type; } return type; } } static class Env { /** * The next enclosing environment. */ Env next; /** * The environment enclosing the current class. */ @Nullable Env outer; JavaSymbol.PackageJavaSymbol packge; @Nullable JavaSymbol.TypeJavaSymbol enclosingClass; Scope scope; Scope namedImports; Scope starImports; Scope staticStarImports; public Env dup() { Env env = new Env(); env.next = this; env.outer = this.outer; env.packge = this.packge; env.enclosingClass = this.enclosingClass; env.scope = this.scope; env.namedImports = this.namedImports; env.starImports = this.starImports; env.staticStarImports = this.staticStarImports; return env; } } public static class JavaSymbolNotFound extends JavaSymbol { public JavaSymbolNotFound() { super(JavaSymbol.ABSENT, 0, null, Symbols.unknownSymbol); } @Override public boolean isUnknown() { return true; } } public static class AmbiguityErrorJavaSymbol extends JavaSymbol { public AmbiguityErrorJavaSymbol() { super(JavaSymbol.AMBIGUOUS, 0, null, null); } } public static class AccessErrorJavaSymbol extends JavaSymbol { /** * The invalid symbol found during resolution. */ JavaSymbol symbol; public AccessErrorJavaSymbol(JavaSymbol symbol, JavaType type) { super(JavaSymbol.ERRONEOUS, 0, null, null); this.symbol = symbol; this.type = type; } } }