/* * 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.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import org.sonar.java.ast.api.JavaKeyword; import org.sonar.java.model.AbstractTypedTree; import org.sonar.java.model.declaration.VariableTreeImpl; import org.sonar.java.model.expression.ConditionalExpressionTreeImpl; import org.sonar.java.model.expression.IdentifierTreeImpl; import org.sonar.java.model.expression.LambdaExpressionTreeImpl; import org.sonar.java.model.expression.MethodInvocationTreeImpl; import org.sonar.java.model.expression.MethodReferenceTreeImpl; import org.sonar.java.model.expression.NewClassTreeImpl; import org.sonar.java.model.expression.ParenthesizedTreeImpl; import org.sonar.java.model.expression.TypeArgumentListTreeImpl; import org.sonar.java.resolve.Resolve.Resolution; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.AnnotationTree; import org.sonar.plugins.java.api.tree.Arguments; import org.sonar.plugins.java.api.tree.ArrayAccessExpressionTree; import org.sonar.plugins.java.api.tree.ArrayDimensionTree; import org.sonar.plugins.java.api.tree.ArrayTypeTree; import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; import org.sonar.plugins.java.api.tree.BaseTreeVisitor; import org.sonar.plugins.java.api.tree.BinaryExpressionTree; import org.sonar.plugins.java.api.tree.BreakStatementTree; import org.sonar.plugins.java.api.tree.ClassTree; import org.sonar.plugins.java.api.tree.ConditionalExpressionTree; import org.sonar.plugins.java.api.tree.ContinueStatementTree; import org.sonar.plugins.java.api.tree.EnumConstantTree; import org.sonar.plugins.java.api.tree.ExpressionStatementTree; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.IdentifierTree; import org.sonar.plugins.java.api.tree.ImportTree; import org.sonar.plugins.java.api.tree.InstanceOfTree; import org.sonar.plugins.java.api.tree.LabeledStatementTree; import org.sonar.plugins.java.api.tree.LambdaExpressionTree; import org.sonar.plugins.java.api.tree.LiteralTree; import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; import org.sonar.plugins.java.api.tree.MethodInvocationTree; import org.sonar.plugins.java.api.tree.MethodReferenceTree; import org.sonar.plugins.java.api.tree.MethodTree; import org.sonar.plugins.java.api.tree.NewArrayTree; import org.sonar.plugins.java.api.tree.NewClassTree; import org.sonar.plugins.java.api.tree.ParameterizedTypeTree; import org.sonar.plugins.java.api.tree.ParenthesizedTree; import org.sonar.plugins.java.api.tree.PrimitiveTypeTree; import org.sonar.plugins.java.api.tree.ReturnStatementTree; import org.sonar.plugins.java.api.tree.ThrowStatementTree; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.TypeArguments; import org.sonar.plugins.java.api.tree.TypeCastTree; import org.sonar.plugins.java.api.tree.TypeParameterTree; import org.sonar.plugins.java.api.tree.TypeTree; import org.sonar.plugins.java.api.tree.UnaryExpressionTree; import org.sonar.plugins.java.api.tree.UnionTypeTree; import org.sonar.plugins.java.api.tree.VariableTree; import org.sonar.plugins.java.api.tree.WildcardTree; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * Computes types and references of Identifier and MemberSelectExpression. */ public class TypeAndReferenceSolver extends BaseTreeVisitor { private final Map<Tree.Kind, JavaType> typesOfLiterals = Maps.newEnumMap(Tree.Kind.class); private final SemanticModel semanticModel; private final Symbols symbols; private final Resolve resolve; private final ParametrizedTypeCache parametrizedTypeCache; private final Map<Tree, JavaType> types = Maps.newHashMap(); Resolve.Env env; public TypeAndReferenceSolver(SemanticModel semanticModel, Symbols symbols, Resolve resolve, ParametrizedTypeCache parametrizedTypeCache) { this.semanticModel = semanticModel; this.symbols = symbols; this.resolve = resolve; this.parametrizedTypeCache = parametrizedTypeCache; typesOfLiterals.put(Tree.Kind.BOOLEAN_LITERAL, symbols.booleanType); typesOfLiterals.put(Tree.Kind.NULL_LITERAL, symbols.nullType); typesOfLiterals.put(Tree.Kind.CHAR_LITERAL, symbols.charType); typesOfLiterals.put(Tree.Kind.STRING_LITERAL, symbols.stringType); typesOfLiterals.put(Tree.Kind.FLOAT_LITERAL, symbols.floatType); typesOfLiterals.put(Tree.Kind.DOUBLE_LITERAL, symbols.doubleType); typesOfLiterals.put(Tree.Kind.LONG_LITERAL, symbols.longType); typesOfLiterals.put(Tree.Kind.INT_LITERAL, symbols.intType); } @Override public void visitMethod(MethodTree tree) { //skip return type, and throw clauses : visited in second pass. scan(tree.modifiers()); completeMetadata((JavaSymbol.MethodJavaSymbol) tree.symbol(), tree.modifiers().annotations()); scan(tree.typeParameters()); // revisits the parameters to resolve their annotations. scan(tree.parameters()); scan(tree.defaultValue()); scan(tree.block()); } @Override public void visitClass(ClassTree tree) { //skip superclass and interfaces : visited in second pass. scan(tree.modifiers()); completeMetadata((JavaSymbol) tree.symbol(), tree.modifiers().annotations()); scan(tree.typeParameters()); scan(tree.members()); } private static void completeMetadata(JavaSymbol symbol, List<AnnotationTree> annotations) { for (AnnotationTree tree : annotations) { AnnotationInstanceResolve annotationInstance = new AnnotationInstanceResolve((JavaSymbol.TypeJavaSymbol) tree.symbolType().symbol()); symbol.metadata().addAnnotation(annotationInstance); Arguments arguments = tree.arguments(); if (arguments.size() > 1 || (!arguments.isEmpty() && arguments.get(0).is(Tree.Kind.ASSIGNMENT))) { for (ExpressionTree expressionTree : arguments) { AssignmentExpressionTree aet = (AssignmentExpressionTree) expressionTree; // TODO: Store more precise value than the expression (real value in case of literal, symbol for enums, array of values, solve constants?) annotationInstance.addValue(new AnnotationValueResolve(((IdentifierTree) aet.variable()).name(), aet.expression())); } } else { // Constant addConstantValue(tree, annotationInstance); } } } private static void addConstantValue(AnnotationTree tree, AnnotationInstanceResolve annotationInstance) { Collection<Symbol> scopeSymbols = tree.annotationType().symbolType().symbol().memberSymbols(); for (ExpressionTree expressionTree : tree.arguments()) { String name = ""; for (Symbol scopeSymbol : scopeSymbols) { if(scopeSymbol.isMethodSymbol()) { name = scopeSymbol.name(); break; } } annotationInstance.addValue(new AnnotationValueResolve(name, expressionTree)); } } @Override public void visitImport(ImportTree tree) { //Noop, imports are not expression } @Override public void visitLabeledStatement(LabeledStatementTree tree) { //Ignore label (dedicated visitor) scan(tree.statement()); } @Override public void visitBreakStatement(BreakStatementTree tree) { //Ignore break (dedicated visitor) } @Override public void visitContinueStatement(ContinueStatementTree tree) { //Ignore continue (dedicated visitor) } @Override public void visitMethodInvocation(MethodInvocationTree tree) { MethodInvocationTreeImpl mit = (MethodInvocationTreeImpl) tree; Resolve.Env methodEnv = semanticModel.getEnv(tree); if(mit.isTypeSet() && mit.symbol().isMethodSymbol()) { TypeSubstitution typeSubstitution = inferedSubstitution(mit); List<JavaType> argTypes = getParameterTypes(tree.arguments()); JavaSymbol.MethodJavaSymbol methodSymbol = (JavaSymbol.MethodJavaSymbol) mit.symbol(); List<JavaType> formals = methodSymbol.parameterTypes().stream().map(t -> (JavaType) t).collect(Collectors.toList()); List<JavaType> inferedArgTypes = resolve.resolveTypeSubstitution(formals, typeSubstitution); int size = inferedArgTypes.size(); IntStream.range(0, argTypes.size()).forEach( i -> { JavaType arg = argTypes.get(i); Type formal = inferedArgTypes.get(Math.min(i, size - 1)); if (formal != arg) { AbstractTypedTree argTree = (AbstractTypedTree) mit.arguments().get(i); argTree.setInferedType(formal); argTree.accept(this); } } ); List<JavaType> typeParamTypes = getParameterTypes(tree.typeArguments()); inferReturnTypeFromInferedArgs(tree, methodEnv, argTypes, typeParamTypes, (JavaType) mit.symbolType(), typeSubstitution); return; } scan(tree.arguments()); scan(tree.typeArguments()); List<JavaType> argTypes = getParameterTypes(tree.arguments()); List<JavaType> typeParamTypes = getParameterTypes(tree.typeArguments()); Resolve.Resolution resolution = resolveMethodSymbol(tree.methodSelect(), methodEnv, argTypes, typeParamTypes); JavaSymbol symbol; JavaType returnType; if(resolution == null) { returnType = symbols.deferedType(mit); symbol = Symbols.unknownSymbol; } else { symbol = resolution.symbol(); returnType = resolution.type(); if(symbol.isMethodSymbol()) { MethodJavaType methodType = (MethodJavaType) resolution.type(); returnType = methodType.resultType; } } mit.setSymbol(symbol); if(returnType != null && returnType.isTagged(JavaType.DEFERRED)) { ((DeferredType) returnType).setTree(mit); } registerType(tree, returnType); if(resolution != null) { inferArgumentTypes(argTypes, resolution); inferReturnTypeFromInferedArgs(tree, methodEnv, argTypes, typeParamTypes, returnType, new TypeSubstitution()); } } private void inferReturnTypeFromInferedArgs(MethodInvocationTree tree, Resolve.Env methodEnv, List<JavaType> argTypes, List<JavaType> typeParamTypes, JavaType returnType, TypeSubstitution typeSubstitution) { List<JavaType> parameterTypes = getParameterTypes(tree.arguments()); Resolution resolution = null; Tree methodSelect = tree.methodSelect(); if(!parameterTypes.equals(argTypes)) { IdentifierTree identifier; if (methodSelect.is(Tree.Kind.MEMBER_SELECT)) { MemberSelectExpressionTree mset = (MemberSelectExpressionTree) methodSelect; JavaType type = getType(mset.expression()); if(type.isTagged(JavaType.DEFERRED)) { throw new IllegalStateException("type of arg should not be defered anymore ??"); } identifier = mset.identifier(); resolution = resolve.findMethod(methodEnv, type, identifier.name(), parameterTypes, typeParamTypes); } else if (methodSelect.is(Tree.Kind.IDENTIFIER)) { identifier = (IdentifierTree) methodSelect; resolution = resolve.findMethod(methodEnv, identifier.name(), parameterTypes, typeParamTypes); } if(resolution != null && returnType != resolution.type() && resolution.symbol().isMethodSymbol()) { MethodJavaType methodType = (MethodJavaType) resolution.type(); if(!methodType.resultType.isTagged(JavaType.DEFERRED)) { registerType(tree, resolve.applySubstitution(methodType.resultType, typeSubstitution)); } } } else { registerType(tree, resolve.applySubstitution(returnType, typeSubstitution)); } } private static TypeSubstitution inferedSubstitution(MethodInvocationTreeImpl mit) { JavaSymbol.MethodJavaSymbol methodSymbol = (JavaSymbol.MethodJavaSymbol) mit.symbol(); JavaType methodReturnedType = (JavaType) mit.symbolType(); TypeSubstitution typeSubstitution = new TypeSubstitution(); if(methodReturnedType.isTagged(JavaType.PARAMETERIZED)) { JavaType resultType = ((MethodJavaType) methodSymbol.type).resultType; if(resultType.isTagged(JavaType.PARAMETERIZED)) { typeSubstitution =((ParametrizedTypeJavaType) resultType).typeSubstitution.combine(((ParametrizedTypeJavaType) methodReturnedType).typeSubstitution); } else if(resultType.isTagged(JavaType.TYPEVAR)) { typeSubstitution.add((TypeVariableJavaType) resultType, methodReturnedType); } } return typeSubstitution; } private void setInferedType(Type infered, DeferredType deferredType) { AbstractTypedTree inferedExpression = deferredType.tree(); Type newType = infered; if (inferedExpression.is(Tree.Kind.NEW_CLASS)) { Type newClassType = ((NewClassTree) inferedExpression).identifier().symbolType(); if(((JavaType) newClassType).isParameterized()) { newType = resolve.resolveTypeSubstitutionWithDiamondOperator((ParametrizedTypeJavaType) newClassType, (JavaType) infered); } } inferedExpression.setInferedType(newType); inferedExpression.accept(this); } private static List<JavaType> getParameterTypes(@Nullable List<? extends Tree> args) { if(args == null) { return new ArrayList<>(); } return args.stream().map(e -> ((AbstractTypedTree) e).isTypeSet() ? (JavaType) ((AbstractTypedTree) e).symbolType() : Symbols.unknownType).collect(Collectors.toList()); } @CheckForNull private Resolve.Resolution resolveMethodSymbol(Tree methodSelect, Resolve.Env methodEnv, List<JavaType> argTypes, List<JavaType> typeParamTypes) { Resolve.Resolution resolution; IdentifierTree identifier; if (methodSelect.is(Tree.Kind.MEMBER_SELECT)) { MemberSelectExpressionTree mset = (MemberSelectExpressionTree) methodSelect; resolveAs(mset.expression(), JavaSymbol.TYP | JavaSymbol.VAR); JavaType type = getType(mset.expression()); if(type.isTagged(JavaType.DEFERRED)) { return null; } identifier = mset.identifier(); resolution = resolve.findMethod(methodEnv, type, identifier.name(), argTypes, typeParamTypes); } else if (methodSelect.is(Tree.Kind.IDENTIFIER)) { identifier = (IdentifierTree) methodSelect; resolution = resolve.findMethod(methodEnv, identifier.name(), argTypes, typeParamTypes); } else { throw new IllegalStateException("Method select in method invocation is not of the expected type " + methodSelect); } registerType(identifier, resolution.type()); associateReference(identifier, resolution.symbol()); return resolution; } private void resolveAs(@Nullable Tree tree, int kind) { if (tree == null) { return; } if (env == null) { resolveAs(tree, kind, semanticModel.getEnv(tree)); } else { resolveAs(tree, kind, env); } } public JavaSymbol resolveAs(Tree tree, int kind, Resolve.Env resolveEnv) { return resolveAs(tree, kind, resolveEnv, true); } public JavaSymbol resolveAs(Tree tree, int kind, Resolve.Env resolveEnv, boolean associateReference) { if (tree.is(Tree.Kind.IDENTIFIER, Tree.Kind.MEMBER_SELECT)) { JavaSymbol resolvedSymbol; IdentifierTree identifierTree; if (tree.is(Tree.Kind.MEMBER_SELECT)) { MemberSelectExpressionTree mse = (MemberSelectExpressionTree) tree; if (JavaKeyword.CLASS.getValue().equals(mse.identifier().name())) { // resolve type of expression xxx.class return resolveClassType(tree, resolveEnv, mse); } identifierTree = mse.identifier(); Resolve.Resolution res = getSymbolOfMemberSelectExpression(mse, kind, resolveEnv); resolvedSymbol = res.symbol(); JavaType resolvedType = resolve.resolveTypeSubstitution(res.type(), getType(mse.expression())); registerType(identifierTree, resolvedType); registerType(tree, resolvedType); } else { identifierTree = (IdentifierTree) tree; Resolve.Resolution resolution = resolve.findIdent(resolveEnv, identifierTree.name(), kind); resolvedSymbol = resolution.symbol(); registerType(tree, resolution.type()); } if(associateReference) { associateReference(identifierTree, resolvedSymbol); } return resolvedSymbol; } tree.accept(this); JavaType type = getType(tree); if (tree.is(Tree.Kind.INFERED_TYPE)) { type = symbols.deferedType((AbstractTypedTree) tree); registerType(tree, type); } if (type == null) { throw new IllegalStateException("Type not resolved " + tree); } return type.symbol; } private JavaSymbol resolveClassType(Tree tree, Resolve.Env resolveEnv, MemberSelectExpressionTree mse) { resolveAs(mse.expression(), JavaSymbol.TYP, resolveEnv); // member select ending with .class JavaType expressionType = getType(mse.expression()); if (expressionType.isPrimitive()) { expressionType = expressionType.primitiveWrapperType(); } TypeSubstitution typeSubstitution = new TypeSubstitution(); typeSubstitution.add(symbols.classType.getSymbol().typeVariableTypes.get(0), expressionType); JavaType parametrizedClassType = parametrizedTypeCache.getParametrizedTypeType(symbols.classType.symbol, typeSubstitution); registerType(tree, parametrizedClassType); return parametrizedClassType.symbol; } private Resolve.Resolution getSymbolOfMemberSelectExpression(MemberSelectExpressionTree mse, int kind, Resolve.Env resolveEnv) { int expressionKind = JavaSymbol.TYP; if ((kind & JavaSymbol.VAR) != 0) { expressionKind |= JavaSymbol.VAR; } if ((kind & JavaSymbol.TYP) != 0) { expressionKind |= JavaSymbol.PCK; } //TODO: resolveAs result is only used here, should probably be refactored JavaSymbol site = resolveAs(mse.expression(), expressionKind, resolveEnv); if (site.kind == JavaSymbol.VAR) { return resolve.findIdentInType(resolveEnv, getType(mse.expression()).symbol, mse.identifier().name(), JavaSymbol.VAR); } if (site.kind == JavaSymbol.TYP) { return resolve.findIdentInType(resolveEnv, (JavaSymbol.TypeJavaSymbol) site, mse.identifier().name(), kind); } if (site.kind == JavaSymbol.PCK) { return Resolve.Resolution.resolution(resolve.findIdentInPackage(site, mse.identifier().name(), kind)); } return resolve.unresolved(); } private void resolveAs(List<? extends Tree> trees, int kind) { for (Tree tree : trees) { resolveAs(tree, kind); } } @Override public void visitTypeParameter(TypeParameterTree typeParameter) { //Type parameters have been handled in first and second pass. } @Override public void visitTypeArguments(TypeArgumentListTreeImpl trees) { resolveAs((List<? extends Tree>) trees, JavaSymbol.TYP); } @Override public void visitInstanceOf(InstanceOfTree tree) { resolveAs(tree.expression(), JavaSymbol.VAR); resolveAs(tree.type(), JavaSymbol.TYP); registerType(tree, symbols.booleanType); } @Override public void visitParameterizedType(ParameterizedTypeTree tree) { resolveAs(tree.type(), JavaSymbol.TYP); resolveAs((List<Tree>) tree.typeArguments(), JavaSymbol.TYP); registerType(tree, parametrizedTypeWithTypeArguments(getType(tree.type()).getSymbol(), tree.typeArguments())); } private JavaType parametrizedTypeWithTypeArguments(JavaSymbol.TypeJavaSymbol symbol, TypeArguments typeArguments) { TypeSubstitution typeSubstitution = new TypeSubstitution(); if (typeArguments.size() <= symbol.typeVariableTypes.size()) { for (int i = 0; i < typeArguments.size(); i++) { typeSubstitution.add(symbol.typeVariableTypes.get(i), getType(typeArguments.get(i))); } } return parametrizedTypeCache.getParametrizedTypeType(symbol, typeSubstitution); } @Override public void visitWildcard(WildcardTree tree) { if (tree.is(Tree.Kind.UNBOUNDED_WILDCARD)) { registerType(tree, symbols.unboundedWildcard); } else { resolveAs(tree.bound(), JavaSymbol.TYP); JavaType bound = getType(tree.bound()); WildCardType.BoundType boundType = tree.is(Tree.Kind.SUPER_WILDCARD) ? WildCardType.BoundType.SUPER : WildCardType.BoundType.EXTENDS; registerType(tree, parametrizedTypeCache.getWildcardType(bound, boundType)); } } @Override public void visitConditionalExpression(ConditionalExpressionTree tree) { if(((ConditionalExpressionTreeImpl) tree).isTypeSet()) { JavaType trueType = getType(tree.trueExpression()); if(trueType.isTagged(JavaType.DEFERRED)) { setInferedType(tree.symbolType(), (DeferredType) trueType); } JavaType falseType = getType(tree.falseExpression()); if(falseType.isTagged(JavaType.DEFERRED)) { setInferedType(tree.symbolType(), (DeferredType) falseType); } } else { resolveAs(tree.condition(), JavaSymbol.VAR); resolveAs(tree.trueExpression(), JavaSymbol.VAR); resolveAs(tree.falseExpression(), JavaSymbol.VAR); registerType(tree, resolve.conditionalExpressionType(tree, (JavaType) tree.trueExpression().symbolType(),(JavaType) tree.falseExpression().symbolType())); } } @Override public void visitThrowStatement(ThrowStatementTree tree) { resolveAs(tree.expression(), JavaSymbol.VAR); } @Override public void visitLambdaExpression(LambdaExpressionTree tree) { LambdaExpressionTreeImpl lambdaExpressionTree = (LambdaExpressionTreeImpl) tree; if (lambdaExpressionTree.isTypeSet()) { // type should be tied to a SAM interface JavaType lambdaType = (JavaType) lambdaExpressionTree.symbolType(); List<JavaType> samMethodArgs = resolve.findSamMethodArgs(lambdaType); for (int i = 0; i < samMethodArgs.size(); i++) { VariableTree param = lambdaExpressionTree.parameters().get(i); if (param.type().is(Tree.Kind.INFERED_TYPE)) { JavaType inferedType = samMethodArgs.get(i); if(inferedType.isTagged(JavaType.WILDCARD)) { // JLS8 18.5.3 inferedType = ((WildCardType) inferedType).bound; } ((AbstractTypedTree) param.type()).setInferedType(inferedType); ((JavaSymbol.VariableJavaSymbol) param.symbol()).type = inferedType; } } super.visitLambdaExpression(tree); if(lambdaType.isUnknown() || lambdaType.isTagged(JavaType.DEFERRED)) { return; } refineLambdaType(lambdaExpressionTree, lambdaType); } else { registerType(tree, symbols.deferedType(lambdaExpressionTree)); } } private void refineLambdaType(LambdaExpressionTreeImpl lambdaExpressionTree, JavaType lambdaType) { Optional<JavaSymbol.MethodJavaSymbol> samMethod = resolve.getSamMethod(lambdaType); if (!samMethod.isPresent()) { return; } JavaType samReturnType = (JavaType) samMethod.get().returnType().type(); JavaType capturedReturnType = resolve.resolveTypeSubstitution(samReturnType, lambdaType); if (capturedReturnType.is("void") || !lambdaType.isParameterized()) { return; } JavaType refinedReturnType = capturedReturnType; if (lambdaExpressionTree.body().is(Tree.Kind.BLOCK)) { LambdaBlockReturnVisitor lambdaBlockReturnVisitor = new LambdaBlockReturnVisitor(); lambdaExpressionTree.body().accept(lambdaBlockReturnVisitor); if(!lambdaBlockReturnVisitor.types.isEmpty()) { refinedReturnType = (JavaType) resolve.leastUpperBound(lambdaBlockReturnVisitor.types); } } else { refinedReturnType = (JavaType) ((AbstractTypedTree) lambdaExpressionTree.body()).symbolType(); } refineType(lambdaExpressionTree, lambdaType, capturedReturnType, refinedReturnType); } private void refineType(AbstractTypedTree expression, JavaType expressionType, JavaType capturedReturnType, JavaType refinedReturnType) { if (refinedReturnType != capturedReturnType) { // found a lambda return type different from the one infered : update infered type if (expressionType.isTagged(JavaType.PARAMETERIZED)) { TypeSubstitution typeSubstitution = ((ParametrizedTypeJavaType) expressionType).typeSubstitution; typeSubstitution.substitutionEntries().stream() .filter(e -> e.getValue() == capturedReturnType) .findFirst() .ifPresent(e -> { TypeSubstitution refinedSubstitution = new TypeSubstitution(typeSubstitution).add(e.getKey(), refinedReturnType); JavaType refinedType = parametrizedTypeCache.getParametrizedTypeType(expressionType.symbol, refinedSubstitution); if(refinedReturnType instanceof DeferredType) { setInferedType(refinedType, (DeferredType) refinedReturnType); } else { expression.setType(refinedType); } }); } else { expression.setType(refinedReturnType); } } } @Override public void visitReturnStatement(ReturnStatementTree tree) { super.visitReturnStatement(tree); ExpressionTree expression = tree.expression(); if (expression != null && ((JavaType) expression.symbolType()).isTagged(JavaType.DEFERRED)) { // get owner of return (method or lambda) Tree parent = tree.parent(); while (!parent.is(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)) { parent = parent.parent(); if(parent == null) { throw new IllegalStateException("Return statement was unexpected here"); } } Type infered; if(parent.is(Tree.Kind.METHOD)) { infered = ((MethodTree) parent).returnType().symbolType(); } else { infered = ((LambdaExpressionTree) parent).symbolType(); } setInferedType(infered, (DeferredType) expression.symbolType()); } } @Override public void visitNewArray(NewArrayTree tree) { resolveAs(tree.type(), JavaSymbol.TYP); scan(tree.dimensions()); resolveAs((List<? extends Tree>) tree.initializers(), JavaSymbol.VAR); JavaType type = getType(tree.type()); int dimensions = tree.dimensions().size(); // TODO why? type = new ArrayJavaType(type, symbols.arrayClass); for (int i = 1; i < dimensions; i++) { type = new ArrayJavaType(type, symbols.arrayClass); } registerType(tree, type); } @Override public void visitParenthesized(ParenthesizedTree tree) { if(((ParenthesizedTreeImpl) tree).isTypeSet()) { JavaType expType = getType(tree.expression()); if(expType.isTagged(JavaType.DEFERRED)) { setInferedType(tree.symbolType(), (DeferredType) expType); } } else { resolveAs(tree.expression(), JavaSymbol.VAR); JavaType parenthesizedExpressionType = getType(tree.expression()); if(parenthesizedExpressionType.isTagged(JavaType.DEFERRED)) { parenthesizedExpressionType = symbols.deferedType((AbstractTypedTree) tree); } registerType(tree, parenthesizedExpressionType); } } @Override public void visitArrayAccessExpression(ArrayAccessExpressionTree tree) { resolveAs(tree.expression(), JavaSymbol.VAR); scan(tree.dimension()); JavaType type = getType(tree.expression()); if (type != null && type.tag == JavaType.ARRAY) { registerType(tree, ((ArrayJavaType) type).elementType); } else { registerType(tree, Symbols.unknownType); } } @Override public void visitArrayDimension(ArrayDimensionTree tree) { resolveAs(tree.expression(), JavaSymbol.VAR); registerType(tree, getType(tree.expression())); } @Override public void visitBinaryExpression(BinaryExpressionTree tree) { super.visitBinaryExpression(tree); JavaType left = getType(tree.leftOperand()); JavaType right = getType(tree.rightOperand()); // TODO avoid nulls if (left == null || right == null) { registerType(tree, Symbols.unknownType); return; } if("+".equals(tree.operatorToken().text()) && (left == symbols.stringType || right == symbols.stringType)) { registerType(tree, symbols.stringType); return; } JavaSymbol symbol = resolve.findMethod(semanticModel.getEnv(tree), symbols.predefClass.type, tree.operatorToken().text(), ImmutableList.of(left, right)).symbol(); if (symbol.kind != JavaSymbol.MTH) { // not found registerType(tree, Symbols.unknownType); return; } registerType(tree, ((MethodJavaType) symbol.type).resultType); } @Override public void visitNewClass(NewClassTree tree) { NewClassTreeImpl newClassTreeImpl = (NewClassTreeImpl) tree; if (newClassTreeImpl.isTypeSet()) { return; } List<JavaType> typeArgumentsTypes = ImmutableList.of(); if (tree.typeArguments() != null) { resolveAs((List<Tree>) tree.typeArguments(), JavaSymbol.TYP); typeArgumentsTypes = tree.typeArguments().stream().map(this::getType).collect(Collectors.toList()); } resolveAs((List<ExpressionTree>) tree.arguments(), JavaSymbol.VAR); List<JavaType> parameterTypes = getParameterTypes(tree.arguments()); Resolve.Env newClassEnv = semanticModel.getEnv(tree); ExpressionTree enclosingExpression = tree.enclosingExpression(); TypeTree typeTree = tree.identifier(); IdentifierTree constructorIdentifier = newClassTreeImpl.getConstructorIdentifier(); JavaType identifierType = resolveIdentifierType(newClassEnv, enclosingExpression, typeTree, constructorIdentifier.name()); JavaSymbol.TypeJavaSymbol constructorIdentifierSymbol = (JavaSymbol.TypeJavaSymbol) identifierType.symbol(); parameterTypes = addImplicitOuterClassParameter(parameterTypes, constructorIdentifierSymbol); Resolution resolution = resolveConstructorSymbol(constructorIdentifier, identifierType, newClassEnv, parameterTypes, typeArgumentsTypes); ClassTree classBody = tree.classBody(); JavaType constructedType = identifierType; if (classBody != null) { ClassJavaType anonymousClassType = (ClassJavaType) classBody.symbol().type(); if (identifierType.getSymbol().isInterface()) { anonymousClassType.interfaces = ImmutableList.of(identifierType); anonymousClassType.supertype = symbols.objectType; } else { anonymousClassType.supertype = identifierType; anonymousClassType.interfaces = ImmutableList.of(); } anonymousClassType.symbol.members.enter(new JavaSymbol.VariableJavaSymbol(Flags.FINAL, "super", anonymousClassType.supertype, anonymousClassType.symbol)); scan(classBody); constructedType = anonymousClassType; } else if (resolution.symbol().isMethodSymbol()) { constructedType = ((MethodJavaType) resolution.type()).resultType; if (constructedType.isTagged(JavaType.DEFERRED)) { Tree parent = newClassTreeImpl.parent(); if (parent.is(Tree.Kind.MEMBER_SELECT)) { constructedType = resolve.parametrizedTypeWithErasure((ParametrizedTypeJavaType) identifierType); } else { // will be resolved by type inference ((DeferredType) constructedType).setTree(newClassTreeImpl); } } } registerType(tree, constructedType); } private JavaType resolveIdentifierType(Resolve.Env newClassEnv, @Nullable ExpressionTree enclosingExpression, TypeTree typeTree, String typeName) { if (enclosingExpression != null) { resolveAs(enclosingExpression, JavaSymbol.VAR); Resolution idType = resolve.findIdentInType(newClassEnv, (JavaSymbol.TypeJavaSymbol) enclosingExpression.symbolType().symbol(), typeName, JavaSymbol.TYP); JavaType type = idType.type(); if (typeTree.is(Tree.Kind.PARAMETERIZED_TYPE)) { TypeArguments typeArguments = ((ParameterizedTypeTree) typeTree).typeArguments(); scan(typeArguments); type = parametrizedTypeWithTypeArguments(type.symbol, typeArguments); } registerType(typeTree, type); } else { resolveAs(typeTree, JavaSymbol.TYP, newClassEnv, false); } return (JavaType) typeTree.symbolType(); } private static List<JavaType> addImplicitOuterClassParameter(List<JavaType> parameterTypes, JavaSymbol.TypeJavaSymbol constructorIdentifierSymbol) { List<JavaType> result = parameterTypes; JavaSymbol owner = constructorIdentifierSymbol.owner(); if (!owner.isPackageSymbol() && !constructorIdentifierSymbol.isStatic()) { result = ImmutableList.<JavaType>builder().add(owner.enclosingClass().type).addAll(parameterTypes).build(); } return result; } @Override public void visitExpressionStatement(ExpressionStatementTree tree) { super.visitExpressionStatement(tree); ExpressionTree expression = tree.expression(); if (((JavaType) expression.symbolType()).isTagged(JavaType.DEFERRED) && expression.is(Tree.Kind.NEW_CLASS)) { JavaType parametrizedTypeWithObject = resolve.parametrizedTypeWithErasure((ParametrizedTypeJavaType) getType(((NewClassTree) expression).identifier())); setInferedType(parametrizedTypeWithObject, (DeferredType) expression.symbolType()); } } private Resolve.Resolution resolveConstructorSymbol(IdentifierTree identifier, Type type, Resolve.Env methodEnv, List<JavaType> argTypes) { return resolveConstructorSymbol(identifier, type, methodEnv, argTypes, ImmutableList.of()); } private Resolve.Resolution resolveConstructorSymbol(IdentifierTree identifier, Type type, Resolve.Env methodEnv, List<JavaType> argTypes, List<JavaType> typeArgumentsTypes) { Resolve.Resolution resolution = resolve.findMethod(methodEnv, (JavaType) type, "<init>", argTypes, typeArgumentsTypes); JavaSymbol symbol = resolution.symbol(); inferArgumentTypes(argTypes, resolution); associateReference(identifier, symbol); return resolution; } private void inferArgumentTypes(List<JavaType> argTypes, Resolve.Resolution resolution) { Type formal = Symbols.unknownType; for (int i = 0; i < argTypes.size(); i++) { JavaType arg = argTypes.get(i); if (resolution.symbol().isMethodSymbol()) { List<JavaType> resolvedFormals = ((MethodJavaType) resolution.type()).argTypes; int size = resolvedFormals.size(); formal = resolvedFormals.get((i < size) ? i : (size - 1)); } if (arg.isTagged(JavaType.DEFERRED)) { setInferedType(formal, (DeferredType) arg); } } } @Override public void visitPrimitiveType(PrimitiveTypeTree tree) { Resolve.Env primitiveEnv = env; if (env == null) { primitiveEnv = semanticModel.getEnv(tree); } registerType(tree, resolve.findIdent(primitiveEnv, tree.keyword().text(), JavaSymbol.TYP).type()); } @Override public void visitVariable(VariableTree tree) { scan(tree.modifiers()); completeMetadata(((VariableTreeImpl) tree).getSymbol(), tree.modifiers().annotations()); //skip type, it has been resolved in second pass ExpressionTree initializer = tree.initializer(); if (initializer != null) { resolveAs(initializer, JavaSymbol.VAR); if(((JavaType) initializer.symbolType()).isTagged(JavaType.DEFERRED)) { setInferedType(tree.type().symbolType(), (DeferredType) initializer.symbolType()); } } } /** * Computes type of an assignment expression. Which is always a type of lvalue. * For example in case of {@code double d; int i; res = d = i;} type of assignment expression {@code d = i} is double. */ @Override public void visitAssignmentExpression(AssignmentExpressionTree tree) { resolveAs(tree.variable(), JavaSymbol.VAR); resolveAs(tree.expression(), JavaSymbol.VAR); JavaType type = getType(tree.variable()); if(((JavaType) tree.expression().symbolType()).isTagged(JavaType.DEFERRED)) { setInferedType(type, (DeferredType) tree.expression().symbolType()); } registerType(tree, type); } @Override public void visitLiteral(LiteralTree tree) { JavaType type = typesOfLiterals.get(tree.kind()); registerType(tree, type); } @Override public void visitUnaryExpression(UnaryExpressionTree tree) { resolveAs(tree.expression(), JavaSymbol.VAR); registerType(tree, getType(tree.expression())); } @Override public void visitArrayType(ArrayTypeTree tree) { //FIXME(benzonico) respect invariant to set type. Required for cases like : int i[],j[]; Compute array element type only if not previously computed. if (getType(tree.type()) == null) { resolveAs(tree.type(), JavaSymbol.TYP); } scan(tree.annotations()); registerType(tree, new ArrayJavaType(getType(tree.type()), symbols.arrayClass)); } @Override public void visitTypeCast(TypeCastTree tree) { resolveAs(tree.type(), JavaSymbol.TYP); resolveAs(tree.expression(), JavaSymbol.VAR); registerType(tree, getType(tree.type())); } @Override public void visitUnionType(UnionTypeTree tree) { resolveAs((List<TypeTree>) tree.typeAlternatives(), JavaSymbol.TYP); ImmutableSet.Builder<Type> uniontype = ImmutableSet.builder(); for (TypeTree typeTree : tree.typeAlternatives()) { uniontype.add(typeTree.symbolType()); } registerType(tree, (JavaType) resolve.leastUpperBound(uniontype.build())); } @Override public void visitEnumConstant(EnumConstantTree tree) { scan(tree.modifiers()); NewClassTree newClassTree = tree.initializer(); scan(newClassTree.enclosingExpression()); // register identifier type registerType(newClassTree.identifier(), ((VariableTreeImpl) tree).getSymbol().getType()); scan(newClassTree.typeArguments()); scan(newClassTree.arguments()); ClassTree classBody = newClassTree.classBody(); if(classBody != null) { scan(classBody); ((ClassJavaType) classBody.symbol().type()).supertype = getType(newClassTree.identifier()); } resolveConstructorSymbol(tree.simpleName(), newClassTree.identifier().symbolType(), semanticModel.getEnv(tree), getParameterTypes(newClassTree.arguments())); } @Override public void visitAnnotation(AnnotationTree tree) { if (((AbstractTypedTree) tree.annotationType()).isTypeSet()) { //FIXME: annotation type is set, so we skip this annotation as it was already visited. // This handle the case where type and its annotation is shared between two variables. return; } resolveAs(tree.annotationType(), JavaSymbol.TYP); Arguments arguments = tree.arguments(); if (arguments.size() > 1 || (!arguments.isEmpty() && arguments.get(0).is(Tree.Kind.ASSIGNMENT))) { // resolve by identifying correct identifier in assignment. for (ExpressionTree expressionTree : arguments) { AssignmentExpressionTree aet = (AssignmentExpressionTree) expressionTree; IdentifierTree variable = (IdentifierTree) aet.variable(); JavaSymbol identInType = resolve.findMethod(semanticModel.getEnv(tree), getType(tree.annotationType()), variable.name(), ImmutableList.<JavaType>of()).symbol(); associateReference(variable, identInType); JavaType type = identInType.type; if (type == null) { type = Symbols.unknownType; } registerType(variable, type); resolveAs(aet.expression(), JavaSymbol.VAR); } } else { for (ExpressionTree expressionTree : arguments) { resolveAs(expressionTree, JavaSymbol.VAR); } } registerType(tree, getType(tree.annotationType())); } @Override public void visitIdentifier(IdentifierTree tree) { resolveAs(tree, JavaSymbol.VAR); } @Override public void visitMemberSelectExpression(MemberSelectExpressionTree tree) { if (!((AbstractTypedTree) tree).isTypeSet()) { resolveAs(tree, JavaSymbol.VAR); } } @Override public void visitMethodReference(MethodReferenceTree methodReferenceTree) { MethodReferenceTreeImpl methodRefTree = (MethodReferenceTreeImpl) methodReferenceTree; if (methodRefTree.isTypeSet() && methodReferenceTree.typeArguments() == null) { JavaType methodRefType = (JavaType) methodRefTree.symbolType(); resolve.getSamMethod(methodRefType).ifPresent(samMethod -> { JavaType samReturnType = (JavaType) samMethod.returnType().type(); List<JavaType> samMethodArgs = resolve.findSamMethodArgs(methodRefType); Resolution resolution = resolve.findMethodReference(semanticModel.getEnv(methodReferenceTree), samMethodArgs, methodRefTree); JavaSymbol methodSymbol = resolution.symbol(); if (methodSymbol.isMethodSymbol()) { IdentifierTree methodIdentifier = methodRefTree.method(); addMethodRefReference(methodIdentifier, methodSymbol); setMethodRefType(methodRefTree, methodRefType, resolution.type()); JavaType capturedReturnType = resolve.resolveTypeSubstitution(samReturnType, methodRefType); JavaType refinedReturnType = ((MethodJavaType) methodIdentifier.symbolType()).resultType(); if ("<init>".equals(methodSymbol.name)) { refinedReturnType = refinedTypeForConstructor(capturedReturnType, refinedReturnType); } refineType(methodRefTree, methodRefType, capturedReturnType, refinedReturnType); } else { handleNewArray(methodRefTree, methodRefType, samReturnType); } }); } else { // TODO : SONARJAVA-1663 : consider type arguments for method resolution and substitution scan(methodReferenceTree.typeArguments()); resolveAs(methodReferenceTree.expression(), JavaSymbol.VAR | JavaSymbol.TYP); registerType(methodRefTree, symbols.deferedType(methodRefTree)); } } private static void addMethodRefReference(IdentifierTree methodIdentifier, JavaSymbol methodSymbol) { ((IdentifierTreeImpl) methodIdentifier).setSymbol(methodSymbol); methodSymbol.addUsage(methodIdentifier); } private static void setMethodRefType(MethodReferenceTree methodRef, JavaType methodRefType, JavaType methodType) { ((AbstractTypedTree) methodRef).setType(methodRefType); ((AbstractTypedTree) methodRef.method()).setType(methodType); } private JavaType refinedTypeForConstructor(JavaType capturedReturnType, JavaType refinedReturnType) { JavaType sanitizedCaptured = capturedReturnType; JavaType refinedConstructorType = refinedReturnType; if (refinedConstructorType.symbol().isTypeSymbol() && !((JavaSymbol.TypeJavaSymbol) refinedConstructorType.symbol()).typeParameters().scopeSymbols().isEmpty()) { refinedConstructorType = parametrizedTypeCache.getParametrizedTypeType(refinedConstructorType.symbol, new TypeSubstitution()); } if (sanitizedCaptured.isTagged(JavaType.TYPEVAR)) { sanitizedCaptured = ((TypeVariableJavaType) sanitizedCaptured).bounds.get(0); } if (refinedConstructorType.isParameterized()) { refinedConstructorType = resolve.resolveTypeSubstitutionWithDiamondOperator((ParametrizedTypeJavaType) refinedConstructorType, sanitizedCaptured); } return refinedConstructorType; } private void handleNewArray(MethodReferenceTree methodReferenceTree, JavaType methodRefType, JavaType samReturnType) { JavaType expressionType = getType(methodReferenceTree.expression()); if (expressionType != null && expressionType.isArray() && "new".equals(methodReferenceTree.method().name())) { JavaType capturedReturnType = resolve.resolveTypeSubstitution(samReturnType, methodRefType); refineType((MethodReferenceTreeImpl) methodReferenceTree, methodRefType, capturedReturnType, expressionType); } } @Override public void visitOther(Tree tree) { registerType(tree, Symbols.unknownType); } @VisibleForTesting JavaType getType(Tree tree) { return types.get(tree); } private void registerType(Tree tree, JavaType type) { if (AbstractTypedTree.class.isAssignableFrom(tree.getClass())) { ((AbstractTypedTree) tree).setType(type); } types.put(tree, type); } private static void associateReference(IdentifierTree tree, JavaSymbol symbol) { if (symbol.kind < JavaSymbol.ERRONEOUS) { ((IdentifierTreeImpl) tree).setSymbol(symbol); symbol.addUsage(tree); } } }