Skip to content

Commit 7c1c34d

Browse files
SONARJAVA-5338 Fix variable owner in the case of lambdas
The computation of owners in lambdas was returning the interface method implemented by the lambda (for instance accept for Function) which doesn't allow distinguishing between symbols declared in different lambdas in different methods with the same variable name.
1 parent 45f37f5 commit 7c1c34d

File tree

3 files changed

+155
-38
lines changed

3 files changed

+155
-38
lines changed

java-frontend/src/main/java/org/sonar/java/model/JSymbol.java

+18-6
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,14 @@ private Symbol variableOwner(IVariableBinding variableBinding) {
194194
if (!variableBinding.isRecordComponent()) {
195195
IMethodBinding declaringMethod = variableBinding.getDeclaringMethod();
196196
if (declaringMethod != null) {
197+
IBinding declaringMember = declaringMethod.getDeclaringMember();
198+
// declaringMember is non-null in the case of lambdas
199+
if (declaringMember instanceof IMethodBinding methodBinding) {
200+
return sema.methodSymbol(methodBinding);
201+
}
202+
if (declaringMember instanceof IVariableBinding varBinding) {
203+
return sema.variableSymbol(varBinding);
204+
}
197205
// local variable
198206
return sema.methodSymbol(declaringMethod);
199207
}
@@ -203,6 +211,10 @@ private Symbol variableOwner(IVariableBinding variableBinding) {
203211
return sema.typeSymbol(declaringClass);
204212
}
205213
}
214+
return ownerOfRecordComponentConstant(variableBinding);
215+
}
216+
217+
private Symbol ownerOfRecordComponentConstant(IVariableBinding variableBinding) {
206218
Tree node = sema.declarations.get(variableBinding);
207219
if (node == null) {
208220
// array.length
@@ -214,8 +226,8 @@ private Symbol variableOwner(IVariableBinding variableBinding) {
214226
node = node.parent();
215227
switch (node.kind()) {
216228
case CLASS,
217-
RECORD,
218-
ENUM:
229+
RECORD,
230+
ENUM:
219231
JTypeSymbol typeSymbol = sema.typeSymbol(((ClassTreeImpl) node).typeBinding);
220232
if (initializerBlock) {
221233
return sema.initializerBlockSymbol(typeSymbol);
@@ -232,7 +244,7 @@ private Symbol variableOwner(IVariableBinding variableBinding) {
232244
staticInitializerBlock = true;
233245
break;
234246
case METHOD,
235-
CONSTRUCTOR:
247+
CONSTRUCTOR:
236248
// local variable declaration in recovered method
237249
// and recovered methods do not have bindings
238250
return Symbols.unknownMethodSymbol;
@@ -252,7 +264,7 @@ public final Type type() {
252264
ITypeBinding variableType = ((IVariableBinding) binding).getType();
253265
return variableType != null ? sema.type(variableType) : Symbols.unknownType;
254266
case IBinding.PACKAGE,
255-
IBinding.METHOD:
267+
IBinding.METHOD:
256268
return Symbols.unknownType;
257269
default:
258270
throw new IllegalStateException(unexpectedBinding());
@@ -447,8 +459,8 @@ private TypeSymbol variableEnclosingClass(IVariableBinding variableBinding) {
447459
node = node.parent();
448460
switch (node.kind()) {
449461
case CLASS,
450-
RECORD,
451-
ENUM:
462+
RECORD,
463+
ENUM:
452464
// variable declaration in a static or instance initializer
453465
// or local variable declaration in recovered method
454466
return sema.typeSymbol(((ClassTreeImpl) node).typeBinding);

java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java

+98-32
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.List;
2121
import java.util.Map;
2222
import java.util.Objects;
23+
import java.util.Optional;
2324
import org.eclipse.jdt.core.JavaCore;
2425
import org.eclipse.jdt.core.dom.AST;
2526
import org.eclipse.jdt.core.dom.ASTParser;
@@ -779,6 +780,7 @@ default void forEach(java.util.function.Consumer<? super T> action) {
779780
assertThat(superKeyword.symbolType().fullyQualifiedName()).isEqualTo("java.lang.Iterable");
780781
}
781782

783+
/** Check that the owner of a lambda parameter is the field to which it is assigned. */
782784
@Test
783785
void expression_lambda() {
784786
String source = """
@@ -793,15 +795,47 @@ class A {
793795
LambdaExpressionTreeImpl lambda = (LambdaExpressionTreeImpl) f.initializer();
794796
VariableTreeImpl p = (VariableTreeImpl) lambda.parameters().get(0);
795797

796-
// owner of lambda parameter is the method which defines the functional interface
797798
Symbol newSymbol = cu.sema.variableSymbol(p.variableBinding);
798799
assertThat(newSymbol.declaration().firstToken().range().start().line()).isEqualTo(3);
799800
Symbol newOwner = newSymbol.owner();
800801
assertThat(newOwner).isNotNull();
801-
assertThat(newOwner.isMethodSymbol()).isTrue();
802+
assertThat(newOwner.isVariableSymbol()).isTrue();
803+
assertThat(newOwner.isTypeSymbol()).isFalse();
804+
assertThat(newOwner.name()).isEqualTo("f");
805+
assertThat(newOwner.owner().type().fullyQualifiedName()).isEqualTo("org.foo.A");
806+
}
807+
808+
/** Check that the owner of lambda parameter is the method inside which the lambda is declared. */
809+
@Test
810+
void expression_lambda_variable_owner() {
811+
String source = """
812+
package org.foo;
813+
class A {
814+
void foo() {
815+
java.util.function.Consumer<String> v = p -> { };
816+
}
817+
}
818+
""";
819+
JavaTree.CompilationUnitTreeImpl cu = test(source);
820+
ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0);
821+
MethodTreeImpl foo = (MethodTreeImpl) c.members().get(0);
822+
Optional<BlockTreeImpl> firstBlock = foo.getChildren().stream()
823+
.filter(BlockTreeImpl.class::isInstance)
824+
.map(BlockTreeImpl.class::cast)
825+
.findFirst();
826+
VariableTree v = (VariableTree) firstBlock.get().body().get(0);
827+
LambdaExpressionTreeImpl lambda = (LambdaExpressionTreeImpl) v.initializer();
828+
VariableTreeImpl p = (VariableTreeImpl) lambda.parameters().get(0);
829+
830+
// owner of lambda parameter is the field or variable to which it is assigned
831+
Symbol newSymbol = cu.sema.variableSymbol(p.variableBinding);
832+
Symbol newOwner = newSymbol.owner();
833+
assertThat(newOwner).isNotNull();
834+
assertThat(newOwner.isVariableSymbol()).isFalse();
802835
assertThat(newOwner.isTypeSymbol()).isFalse();
803-
assertThat(newOwner.name()).isEqualTo("accept");
804-
assertThat(newOwner.owner().type().fullyQualifiedName()).isEqualTo("java.util.function.Consumer");
836+
assertThat(newOwner.isMethodSymbol()).isTrue();
837+
assertThat(newOwner.name()).isEqualTo("foo");
838+
assertThat(newOwner.owner().type().fullyQualifiedName()).isEqualTo("org.foo.A");
805839
}
806840

807841
@Test
@@ -1337,7 +1371,7 @@ void declaration_annotation_member() {
13371371
assertThat(cu.sema.declarations.get(m.methodBinding)).isSameAs(m);
13381372
}
13391373

1340-
@ParameterizedTest(name="[{index}] Type bindings of variable v should not be null in \"{0}\"")
1374+
@ParameterizedTest(name = "[{index}] Type bindings of variable v should not be null in \"{0}\"")
13411375
@ValueSource(strings = {
13421376
"interface I { int v; }", // primitive
13431377
"interface I<T> { I<String> v; }", // parameterized
@@ -1634,19 +1668,19 @@ void nested() {
16341668
@Test
16351669
void constructor_with_type_arguments() {
16361670
String source = """
1637-
class MyClass {
1638-
<T extends I> MyClass(T t) {}
1639-
<T extends J & I> MyClass(T t) {}
1640-
void foo(B b, C c) {
1641-
new<B>MyClass((I) b);
1642-
new<C>MyClass(c);
1643-
}
1671+
class MyClass {
1672+
<T extends I> MyClass(T t) {}
1673+
<T extends J & I> MyClass(T t) {}
1674+
void foo(B b, C c) {
1675+
new<B>MyClass((I) b);
1676+
new<C>MyClass(c);
16441677
}
1645-
interface I {}
1646-
interface J {}
1647-
class B implements I {}
1648-
class C implements I, J {}
1649-
""";
1678+
}
1679+
interface I {}
1680+
interface J {}
1681+
class B implements I {}
1682+
class C implements I, J {}
1683+
""";
16501684

16511685
JavaTree.CompilationUnitTreeImpl cu = test(source);
16521686
ClassTree c = (ClassTree) cu.types().get(0);
@@ -1692,7 +1726,7 @@ void should_skip_implicit_break_statement() {
16921726
private CompilationUnit createAST(String source) {
16931727
JavaVersion version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION;
16941728
ASTParser astParser = ASTParser.newParser(AST.getJLSLatest());
1695-
Map<String, String> options = new HashMap<>(JavaCore.getOptions());
1729+
Map<String, String> options = new HashMap<>(JavaCore.getOptions());
16961730
JavaCore.setComplianceOptions(version.effectiveJavaVersionAsString(), options);
16971731
options.put(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, "enabled");
16981732
astParser.setCompilerOptions(options);
@@ -1737,22 +1771,22 @@ class InnerB extends C.InnerC {
17371771
@Test
17381772
void inner_class_depending_on_outer_class_parametrized_type() {
17391773
final String source = """
1740-
class X<T> {
1741-
InnerClass innerClass;
1742-
class InnerClass {
1743-
T method() {
1744-
return null;
1745-
}
1746-
}
1747-
static void test() {
1748-
new X<Y>().innerClass.method().method1();
1774+
class X<T> {
1775+
InnerClass innerClass;
1776+
class InnerClass {
1777+
T method() {
1778+
return null;
17491779
}
17501780
}
1751-
class Y {
1752-
void method1() {
1753-
}
1781+
static void test() {
1782+
new X<Y>().innerClass.method().method1();
1783+
}
1784+
}
1785+
class Y {
1786+
void method1() {
17541787
}
1755-
""";
1788+
}
1789+
""";
17561790
JavaTree.CompilationUnitTreeImpl cu = test(source);
17571791
ClassTree classX = (ClassTree) cu.types().get(0);
17581792
MethodTreeImpl method = (MethodTreeImpl) ((ClassTree) classX.members().get(1)).members().get(0);
@@ -1788,7 +1822,7 @@ void warnings_are_detected() {
17881822
List<JWarning> castWarnings = cu.warnings(JWarning.Type.REDUNDANT_CAST);
17891823
assertThat(castWarnings).hasSize(2);
17901824

1791-
TypeCastTree typeCast = (TypeCastTree)((VariableTree)((MethodTree)(((ClassTree) cu.types().get(0)).members().get(0))).block().body().get(0)).initializer();
1825+
TypeCastTree typeCast = (TypeCastTree) ((VariableTree) ((MethodTree) (((ClassTree) cu.types().get(0)).members().get(0))).block().body().get(0)).initializer();
17921826
JWarning parentCastWarning = castWarnings.get(0);
17931827
assertThat(parentCastWarning.message()).isEqualTo("Unnecessary cast from String to String");
17941828
assertThat(parentCastWarning.syntaxTree()).isEqualTo(typeCast);
@@ -1798,4 +1832,36 @@ void warnings_are_detected() {
17981832
assertThat(nestedCastWarning.message()).isEqualTo("Unnecessary cast from String to String");
17991833
assertThat(nestedCastWarning.syntaxTree()).isEqualTo(parenthesizedTree);
18001834
}
1835+
1836+
@Test
1837+
void test_variable_equals_in_lambda() {
1838+
String source = """
1839+
package org.foo;
1840+
class A {
1841+
void f1() {
1842+
java.util.function.Consumer<String> a = p -> { System.out.println(p); };
1843+
java.util.function.Consumer<String> c = p -> { System.out.println(p); };
1844+
}
1845+
void f2() {
1846+
java.util.function.Consumer<String> b = p -> { System.out.println(p); };
1847+
}
1848+
}
1849+
""";
1850+
JavaTree.CompilationUnitTreeImpl cu = test(source);
1851+
ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0);
1852+
MethodTree f1 = (MethodTree) c.members().get(0);
1853+
MethodTree f2 = (MethodTree) c.members().get(1);
1854+
VariableTree variableTreeA = (VariableTree) f1.block().body().get(0);
1855+
VariableTree variableTreeC = (VariableTree) f1.block().body().get(1);
1856+
VariableTree variableTreeB = (VariableTree) f2.block().body().get(0);
1857+
LambdaExpressionTreeImpl lambdaA = (LambdaExpressionTreeImpl) variableTreeA.initializer();
1858+
LambdaExpressionTreeImpl lambdaB = (LambdaExpressionTreeImpl) variableTreeB.initializer();
1859+
LambdaExpressionTreeImpl lambdaC = (LambdaExpressionTreeImpl) variableTreeC.initializer();
1860+
VariableTreeImpl pOfA = (VariableTreeImpl) lambdaA.parameters().get(0);
1861+
VariableTreeImpl pOfB = (VariableTreeImpl) lambdaB.parameters().get(0);
1862+
VariableTreeImpl pOfC = (VariableTreeImpl) lambdaC.parameters().get(0);
1863+
1864+
assertThat(pOfA.symbol()).isNotSameAs(pOfB.symbol());
1865+
assertThat(pOfA.symbol()).isNotSameAs(pOfC.symbol());
1866+
}
18011867
}

java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java

+39
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
import org.sonar.plugins.java.api.tree.VariableTree;
4343

4444
import static org.assertj.core.api.Assertions.assertThat;
45+
import static org.junit.jupiter.api.Assertions.assertEquals;
46+
import static org.junit.jupiter.api.Assertions.assertTrue;
4547
import static org.mockito.Mockito.mock;
4648
import static org.mockito.Mockito.when;
4749
import static org.sonar.java.model.assertions.SymbolAssert.assertThat;
@@ -163,6 +165,43 @@ void owner_local_record() {
163165
.hasOwner(cu.sema.typeSymbol(c1.typeBinding));
164166
}
165167

168+
@Test
169+
void owner_lambdaVariable_variableBinding() {
170+
JavaTree.CompilationUnitTreeImpl cu = test("""
171+
class C {
172+
java.util.function.Function<Integer, Integer> variableBinding = i -> i;
173+
}
174+
"""
175+
);
176+
ClassTreeImpl classTree = (ClassTreeImpl) cu.types().get(0);
177+
VariableTree variableBinding = (VariableTree) classTree.members().get(0);
178+
LambdaExpressionTree lambda = (LambdaExpressionTree) variableBinding.initializer();
179+
IdentifierTree i = (IdentifierTree) lambda.body();
180+
Symbol owner = i.symbol().owner();
181+
assertTrue(owner.isVariableSymbol());
182+
assertEquals("variableBinding", owner.name());
183+
}
184+
185+
@Test
186+
void owner_lambdaVariable_methodBinding() {
187+
JavaTree.CompilationUnitTreeImpl cu = test("""
188+
class C {
189+
void foo() {
190+
java.util.function.Function<Long, Long> methodBinding = l -> l;
191+
}
192+
}
193+
"""
194+
);
195+
ClassTreeImpl classTree = (ClassTreeImpl) cu.types().get(0);
196+
MethodTree method = (MethodTree) classTree.members().get(0);
197+
VariableTree bindingInsideMethod = (VariableTree) method.block().body().get(0);
198+
LambdaExpressionTree lambda = (LambdaExpressionTree) bindingInsideMethod.initializer();
199+
VariableTree l = lambda.parameters().get(0);
200+
Symbol owner = l.symbol().owner();
201+
assertTrue(owner.isMethodSymbol());
202+
assertEquals("foo", owner.name());
203+
}
204+
166205
@Test
167206
void primitive_type_hash_code() {
168207
JavaTree.CompilationUnitTreeImpl cu = test("class C { int u; int v; }");

0 commit comments

Comments
 (0)