Skip to content

Commit c11e5d1

Browse files
SONARKT-739 S6518 Add class exceptions for common user errors where operator is provided via java interop (#701)
1 parent 7ef85bf commit c11e5d1

File tree

9 files changed

+183
-77
lines changed

9 files changed

+183
-77
lines changed

its/ruling/src/test/resources/expected/kotlin/corda/kotlin-S6518.json

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,10 @@
77
"kotlin-corda-project:sources/kotlin/corda/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Cash.kt": [
88
21
99
],
10-
"kotlin-corda-project:sources/kotlin/corda/node/src/main/kotlin/net/corda/node/SerialFilter.kt": [
11-
32,
12-
33
13-
],
1410
"kotlin-corda-project:sources/kotlin/corda/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt": [
1511
145,
1612
146
1713
],
18-
"kotlin-corda-project:sources/kotlin/corda/node/src/main/kotlin/net/corda/node/utilities/ObjectDiffer.kt": [
19-
86,
20-
86
21-
],
2214
"kotlin-corda-project:sources/kotlin/corda/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt": [
2315
218,
2416
227,
@@ -30,18 +22,7 @@
3022
"kotlin-corda-project:sources/kotlin/corda/serialization/src/main/kotlin/net/corda/serialization/internal/ByteBufferStreams.kt": [
3123
41
3224
],
33-
"kotlin-corda-project:sources/kotlin/corda/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializers.kt": [
34-
67
35-
],
36-
"kotlin-corda-project:sources/kotlin/corda/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/PrivatePropertyTests.kt": [
37-
195,
38-
215
39-
],
4025
"kotlin-corda-project:sources/kotlin/corda/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationOutputTests.kt": [
4126
1158
42-
],
43-
"kotlin-corda-project:sources/kotlin/corda/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationPropertyOrdering.kt": [
44-
112,
45-
120
4627
]
4728
}
Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
11
{
2-
"kotlin-intellij-rust-project:sources/kotlin/intellij-rust/src/main/kotlin/org/rust/cargo/project/model/CargoProjectService.kt": [
3-
47
4-
],
5-
"kotlin-intellij-rust-project:sources/kotlin/intellij-rust/src/main/kotlin/org/rust/cargo/project/model/impl/CargoProjectImpl.kt": [
6-
194
7-
],
82
"kotlin-intellij-rust-project:sources/kotlin/intellij-rust/src/main/kotlin/org/rust/ide/annotator/fixes/ConvertToTyUsingTryFromTraitFix.kt": [
93
91
104
],
@@ -13,8 +7,5 @@
137
],
148
"kotlin-intellij-rust-project:sources/kotlin/intellij-rust/src/main/kotlin/org/rust/lang/core/types/ty/TyTraitObject.kt": [
159
24
16-
],
17-
"kotlin-intellij-rust-project:sources/kotlin/intellij-rust/src/test/kotlin/org/rust/stdext/AsyncValueTest.kt": [
18-
38
1910
]
2011
}

its/ruling/src/test/resources/expected/kotlin/kotlin-language-server/kotlin-S6518.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
{
2-
"kotlin-kotlin-language-server-project:server/src/main/kotlin/org/javacs/kt/completion/Completions.kt": [
3-
553
4-
],
52
"kotlin-kotlin-language-server-project:shared/src/main/kotlin/org/javacs/kt/Logger.kt": [
63
150,
74
151

its/ruling/src/test/resources/expected/kotlin/kotlin/kotlin-S6518.json

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -36,50 +36,21 @@
3636
"kotlin-kotlin-project:sources/kotlin/kotlin/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Concat.kt": [
3737
60
3838
],
39-
"kotlin-kotlin-project:sources/kotlin/kotlin/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/JavacWrapper.kt": [
40-
170
41-
],
4239
"kotlin-kotlin-project:sources/kotlin/kotlin/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassForFacade.kt": [
4340
259
4441
],
45-
"kotlin-kotlin-project:sources/kotlin/kotlin/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/LoggingStorageManager.kt": [
46-
87,
47-
95
48-
],
49-
"kotlin-kotlin-project:sources/kotlin/kotlin/compiler/tests/org/jetbrains/kotlin/codegen/AbstractCustomScriptCodegenTest.kt": [
50-
124
51-
],
5242
"kotlin-kotlin-project:sources/kotlin/kotlin/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/TypeDeserializer.kt": [
5343
157
5444
],
55-
"kotlin-kotlin-project:sources/kotlin/kotlin/eval4j/test/org/jetbrains/eval4j/test/main.kt": [
56-
214,
57-
221,
58-
253,
59-
261
60-
],
6145
"kotlin-kotlin-project:sources/kotlin/kotlin/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/platformWrappers.kt": [
6246
103
6347
],
6448
"kotlin-kotlin-project:sources/kotlin/kotlin/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/ExpectedCompletionUtils.kt": [
6549
247
6650
],
67-
"kotlin-kotlin-project:sources/kotlin/kotlin/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinIndicesHelper.kt": [
68-
296
69-
],
7051
"kotlin-kotlin-project:sources/kotlin/kotlin/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinSourcePositionProvider.kt": [
7152
128
7253
],
73-
"kotlin-kotlin-project:sources/kotlin/kotlin/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepActionFactory.kt": [
74-
89
75-
],
76-
"kotlin-kotlin-project:sources/kotlin/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/elementSelectionUtils.kt": [
77-
207,
78-
208
79-
],
80-
"kotlin-kotlin-project:sources/kotlin/kotlin/idea/tests/org/jetbrains/kotlin/idea/intentions/AbstractIntentionTest.kt": [
81-
143
82-
],
8354
"kotlin-kotlin-project:sources/kotlin/kotlin/idea/tests/org/jetbrains/kotlin/idea/resolve/AbstractReferenceResolveTest.kt": [
8455
119
8556
],
@@ -97,9 +68,6 @@
9768
"kotlin-kotlin-project:sources/kotlin/kotlin/libraries/stdlib/jvm/src/kotlin/collections/MapsJVM.kt": [
9869
41
9970
],
100-
"kotlin-kotlin-project:sources/kotlin/kotlin/libraries/stdlib/jvm/test/concurrent/ThreadTest.kt": [
101-
32
102-
],
10371
"kotlin-kotlin-project:sources/kotlin/kotlin/libraries/stdlib/src/kotlin/collections/Maps.kt": [
10472
162
10573
],
@@ -109,9 +77,6 @@
10977
156,
11078
452
11179
],
112-
"kotlin-kotlin-project:sources/kotlin/kotlin/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt": [
113-
322
114-
],
11580
"kotlin-kotlin-project:sources/kotlin/kotlin/plugins/source-sections/source-sections-compiler/src/org/jetbrains/kotlin/sourceSections/FilteredSectionsVirtualFile.kt": [
11681
68
11782
]

kotlin-checks-test-sources/src/main/kotlin/checks/IndexedAccessCheckSample.kt

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,34 @@
11
package checks
22

3+
import com.google.common.collect.ImmutableList
4+
import com.google.common.collect.RangeMap
5+
import com.google.common.collect.Table
6+
import okhttp3.Headers
7+
import otherpackage.KotlinLibContainer
38
import otherpackage.get
9+
import java.nio.ByteBuffer
10+
import java.util.BitSet
411
import java.util.Calendar
12+
import java.util.Stack
513
import java.util.concurrent.CompletableFuture
614
import java.util.concurrent.TimeUnit
715
import java.util.concurrent.atomic.AtomicInteger
16+
import java.util.concurrent.atomic.AtomicIntegerArray
817

918
class IndexedAccessCheckSample {
1019

11-
fun withoutIndexedAccessors(list: MutableList<Int>, map: MutableMap<String, Int>, grid: Grid, value: Any, cal: Calendar, future: CompletableFuture<Int>) {
20+
fun withoutIndexedAccessors(
21+
list: MutableList<Int>,
22+
map: MutableMap<String, Int>,
23+
grid: Grid,
24+
value: Any,
25+
buffer: ByteBuffer,
26+
stack: Stack<String>,
27+
atomicArray: AtomicIntegerArray,
28+
bitSet: BitSet,
29+
arrayList: ArrayList<Int>,
30+
hashMap: HashMap<String, Int>,
31+
) {
1232
list.get(1) // Noncompliant {{Replace function call with indexed accessor.}}
1333
// ^^^
1434
list.set(1, 42) // Noncompliant {{Replace function call with indexed accessor.}}
@@ -19,10 +39,45 @@ class IndexedAccessCheckSample {
1939
grid.get(1, 2) // Noncompliant {{Replace function call with indexed accessor.}}
2040
grid.set(1, 2, 42) // Noncompliant {{Replace function call with indexed accessor.}}
2141
value.get(42) // Noncompliant {{Replace function call with indexed accessor.}}
22-
// Java get/set methods can be replaced with indexed access operators via Java-Interop (https://kotlinlang.org/docs/java-interop.html#operators)
23-
cal.get(Calendar.YEAR) // Noncompliant {{Replace function call with indexed accessor.}}
24-
future.get(1L, TimeUnit.SECONDS) // Noncompliant {{Replace function call with indexed accessor.}}
42+
// Java interop allowed types - indexed access is idiomatic for these
43+
buffer.get(0) // Noncompliant {{Replace function call with indexed accessor.}}
44+
stack.get(0) // Noncompliant {{Replace function call with indexed accessor.}}
45+
atomicArray.get(0) // Noncompliant {{Replace function call with indexed accessor.}}
46+
atomicArray.set(0, 42) // Noncompliant {{Replace function call with indexed accessor.}}
47+
bitSet.get(5) // Noncompliant {{Replace function call with indexed accessor.}}
48+
bitSet.set(5, true) // Noncompliant {{Replace function call with indexed accessor.}}
49+
// Concrete Java collection implementations - caught via List/Map supertype check
50+
arrayList.get(0) // Noncompliant {{Replace function call with indexed accessor.}}
51+
arrayList.set(0, 42) // Noncompliant {{Replace function call with indexed accessor.}}
52+
hashMap.get("key") // Noncompliant {{Replace function call with indexed accessor.}}
53+
}
54+
55+
fun javaInteropExcluded(cal: Calendar, future: CompletableFuture<Int>) {
56+
// Java get/set methods are operators via Java-Interop (https://kotlinlang.org/docs/java-interop.html#operators)
57+
// but indexed access is not idiomatic for these types, so we don't raise
58+
cal.get(Calendar.YEAR) // Compliant - Java interop operator, not in allowed types
59+
cal.set(Calendar.YEAR, 2024) // Compliant - Java interop operator, not in allowed types
60+
future.get(1L, TimeUnit.SECONDS) // Compliant - Java interop operator, not in allowed types
61+
}
62+
63+
fun kotlinLibraryTypes(container: KotlinLibContainer<String>, headers: Headers) {
64+
// Compiled Kotlin library types with operator fun get/set should still be flagged.
65+
// These are NOT Java interop operators — they are genuine Kotlin operators from a library.
66+
container.get(0) // Noncompliant {{Replace function call with indexed accessor.}}
67+
container.set(0, "value") // Noncompliant {{Replace function call with indexed accessor.}}
68+
headers.get("Content-Type") // Noncompliant {{Replace function call with indexed accessor.}}
69+
}
2570

71+
fun javaLibraryTypes(
72+
immutableList: ImmutableList<String>,
73+
table: Table<String, String, Int>,
74+
rangeMap: RangeMap<Int, String>,
75+
) {
76+
// Java library types extending List/Map — allowed Java interop, should still raise
77+
immutableList.get(0) // Noncompliant {{Replace function call with indexed accessor.}}
78+
// Java library types NOT extending List/Map — Java interop excluded
79+
table.get("row", "col") // Compliant - Java interop operator, not in allowed types
80+
rangeMap.get(42) // Compliant - Java interop operator, not in allowed types
2681
}
2782

2883
fun withIndexedAccessors(lisp: Lisp<Int>, maybeNullList: MutableList<Int>?, list: MutableList<Int>, map: MutableMap<String, Int>, grid: Grid, num: AtomicInteger, root: GenericAccessorClass) {

kotlin-checks-test-sources/src/main/kotlin/checks/IndexedAccessCheckSampleNoSemantics.kt

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
package checks
22

3+
import com.google.common.collect.ImmutableList
4+
import com.google.common.collect.RangeMap
5+
import com.google.common.collect.Table
6+
import okhttp3.Headers
7+
import otherpackage.KotlinLibContainer
38
import otherpackage.get
9+
import java.nio.ByteBuffer
10+
import java.util.BitSet
411
import java.util.Calendar
12+
import java.util.Stack
513
import java.util.concurrent.CompletableFuture
614
import java.util.concurrent.TimeUnit
715
import java.util.concurrent.atomic.AtomicInteger
16+
import java.util.concurrent.atomic.AtomicIntegerArray
817

918
class IndexedAccessCheckSampleNoSemantics {
1019

@@ -13,8 +22,12 @@ class IndexedAccessCheckSampleNoSemantics {
1322
map: MutableMap<String, Int>,
1423
grid: Grid2,
1524
value: Any,
16-
cal: Calendar,
17-
future: CompletableFuture<Int>,
25+
buffer: ByteBuffer,
26+
stack: Stack<String>,
27+
atomicArray: AtomicIntegerArray,
28+
bitSet: BitSet,
29+
arrayList: ArrayList<Int>,
30+
hashMap: HashMap<String, Int>,
1831
) {
1932
list.get(1) // Noncompliant {{Replace function call with indexed accessor.}}
2033
// ^^^
@@ -25,10 +38,42 @@ class IndexedAccessCheckSampleNoSemantics {
2538
grid.get(1, 2) // Noncompliant {{Replace function call with indexed accessor.}}
2639
grid.set(1, 2, 42) // Noncompliant {{Replace function call with indexed accessor.}}
2740
value.get(42) // FN, Any has no get operator, only exposed via an extension function in Importable.kt, function in , can't resolve without semantics
28-
// Java get/set methods can be replaced with indexed access operators via Java-Interop (https://kotlinlang.org/docs/java-interop.html#operators)
29-
cal.get(Calendar.YEAR) // Noncompliant {{Replace function call with indexed accessor.}}
30-
// FP without semantics: incorrect resolution due to singleFunctionCallOrNull
31-
future.get(1L, TimeUnit.SECONDS) // Noncompliant {{Replace function call with indexed accessor.}}
41+
// Java interop allowed types - indexed access is idiomatic for these
42+
buffer.get(0) // Noncompliant {{Replace function call with indexed accessor.}}
43+
stack.get(0) // Noncompliant {{Replace function call with indexed accessor.}}
44+
atomicArray.get(0) // Noncompliant {{Replace function call with indexed accessor.}}
45+
atomicArray.set(0, 42) // Noncompliant {{Replace function call with indexed accessor.}}
46+
bitSet.get(5) // Noncompliant {{Replace function call with indexed accessor.}}
47+
bitSet.set(5, true) // Noncompliant {{Replace function call with indexed accessor.}}
48+
// Concrete Java collection implementations - caught via List/Map supertype check with semantics
49+
arrayList.get(0) // FN, supertype check requires classpath to resolve ArrayList extends List
50+
arrayList.set(0, 42) // FN, same reason
51+
hashMap.get("key") // FN, supertype check requires classpath to resolve HashMap extends Map
52+
}
53+
54+
fun javaInteropExcluded(cal: Calendar, future: CompletableFuture<Int>) {
55+
// Java get/set methods are operators via Java-Interop, but indexed access is not idiomatic for these types
56+
cal.get(Calendar.YEAR) // Compliant - Java interop operator, not in allowed types
57+
cal.set(Calendar.YEAR, 2024) // Compliant - Java interop operator, not in allowed types
58+
future.get(1L, TimeUnit.SECONDS) // Compliant - Java interop operator, not in allowed types
59+
}
60+
61+
fun kotlinLibraryTypes(container: KotlinLibContainer<String>, headers: Headers) {
62+
// FN without semantics: operator fun get/set can't be resolved without classpath
63+
container.get(0)
64+
container.set(0, "value")
65+
headers.get("Content-Type")
66+
}
67+
68+
fun javaLibraryTypes(
69+
immutableList: ImmutableList<String>,
70+
table: Table<String, String, Int>,
71+
rangeMap: RangeMap<Int, String>,
72+
) {
73+
// FN without semantics: supertype/interop checks require classpath
74+
immutableList.get(0)
75+
table.get("row", "col")
76+
rangeMap.get(42)
3277
}
3378

3479
fun withIndexedAccessors(

kotlin-checks-test-sources/src/main/kotlin/otherpackage/Importable.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,13 @@ fun functionTakingAny(value: Any) {}
2727
fun String.stringExtFun1() {}
2828
fun String.stringExtFun2() {}
2929
infix fun String.someInfixFun(foo: String) = this
30+
31+
/**
32+
* A Kotlin class with operator fun get/set, simulating a third-party Kotlin library type
33+
* (e.g., Arrow, KotlinX collections, or any user-defined library).
34+
* When resolved from compiled class files (not source), symbol.psi will be null.
35+
*/
36+
class KotlinLibContainer<T>(private val items: List<T>) {
37+
operator fun get(index: Int): T = items[index]
38+
operator fun set(index: Int, value: T) { /* no-op for test */ }
39+
}

0 commit comments

Comments
 (0)