Skip to content

Commit 678c8e6

Browse files
committed
StackOverflowError on circular reference #376
1 parent f5172c5 commit 678c8e6

File tree

11 files changed

+64
-31
lines changed

11 files changed

+64
-31
lines changed

compiler-plugin/compiler-plugin-k2/src/main/kotlin/kotlinx/rpc/codegen/checkers/FirRpcStrictModeClassChecker.kt

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ object FirRpcStrictModeClassChecker {
7474
} memoryOptimizedPlus (function.resolvedReturnTypeRef.source to function.resolvedReturnTypeRef)
7575

7676
types.forEach { (source, symbol) ->
77-
checkSerializableTypes<FirClassLikeSymbol<*>>(
77+
checkSerializableTypes(
7878
context = context,
7979
typeRef = symbol,
8080
serializablePropertiesProvider = serializablePropertiesProvider,
@@ -96,8 +96,6 @@ object FirRpcStrictModeClassChecker {
9696
}
9797
}
9898
}
99-
100-
symbol
10199
}
102100
}
103101

@@ -106,34 +104,36 @@ object FirRpcStrictModeClassChecker {
106104
}
107105
}
108106

109-
private fun <ContextElement> checkSerializableTypes(
107+
private fun checkSerializableTypes(
110108
context: CheckerContext,
111109
typeRef: FirTypeRef,
112110
serializablePropertiesProvider: FirSerializablePropertiesProvider,
113-
parentContext: List<ContextElement> = emptyList(),
114-
checker: (FirClassLikeSymbol<*>, List<ContextElement>) -> ContextElement?,
111+
parentContext: List<FirClassLikeSymbol<*>> = emptyList(),
112+
checker: (FirClassLikeSymbol<*>, List<FirClassLikeSymbol<*>>) -> Unit,
115113
) {
116114
val symbol = typeRef.toClassLikeSymbol(context.session) ?: return
117-
val newElement = checker(symbol, parentContext)
118-
val nextContext = if (newElement != null) {
119-
parentContext memoryOptimizedPlus newElement
120-
} else {
121-
parentContext
122-
}
115+
116+
checker(symbol, parentContext)
123117

124118
if (symbol !is FirClassSymbol<*>) {
125119
return
126120
}
127121

128-
val extracted = extractArgumentsTypeRefAndSource(typeRef)
122+
val nextContext = parentContext memoryOptimizedPlus symbol
123+
124+
if (symbol in parentContext && symbol.typeParameterSymbols.isEmpty()) {
125+
return
126+
}
127+
128+
val typeParameters = extractArgumentsTypeRefAndSource(typeRef)
129129
.orEmpty()
130130
.withIndex()
131131
.associate { (i, refSource) ->
132132
symbol.typeParameterSymbols[i].toConeType() to refSource.typeRef
133133
}
134134

135135
val flowProps: List<FirTypeRef> = if (symbol.classId == RpcClassId.flow) {
136-
listOf(extracted.values.toList()[0]!!)
136+
listOf(typeParameters.values.toList()[0]!!)
137137
} else {
138138
emptyList()
139139
}
@@ -144,7 +144,7 @@ object FirRpcStrictModeClassChecker {
144144
if (resolvedTypeRef.toClassLikeSymbol(context.session) != null) {
145145
resolvedTypeRef
146146
} else {
147-
extracted[property.resolvedReturnType]
147+
typeParameters[property.resolvedReturnType]
148148
}
149149
}.memoryOptimizedPlus(flowProps)
150150
.forEach { symbol ->

compiler-plugin/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ org.gradle.parallel=true
1515
org.gradle.workers.max=8
1616
org.gradle.caching=true
1717
org.gradle.configuration-cache=true
18-
org.gradle.configureondemand=true
18+
#org.gradle.configureondemand=true // breaks compiler tests
1919

2020
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
2121
org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true

dokka-plugin/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ org.gradle.parallel=true
1515
org.gradle.workers.max=8
1616
org.gradle.caching=true
1717
org.gradle.configuration-cache=true
18-
org.gradle.configureondemand=true
18+
#org.gradle.configureondemand=true // breaks compiler tests
1919

2020
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
2121
org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true

gradle-conventions/src/main/kotlin/conventions-common.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
import io.gitlab.arturbosch.detekt.Detekt

gradle-conventions/src/main/kotlin/conventions-kmp.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
@file:OptIn(ExperimentalAbiValidation::class)

gradle-conventions/src/main/kotlin/conventions-publishing.gradle.kts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -138,15 +138,6 @@ fun RepositoryHandler.configureLocalDevRepository() {
138138
}
139139
}
140140

141-
val sonatypeRepositoryUri: String?
142-
get() {
143-
val repositoryId: String = project.getSensitiveProperty("libs.repository.id")
144-
?.takeIf { it.isNotBlank() }
145-
?: return null
146-
147-
return "https://oss.sonatype.org/service/local/staging/deployByRepositoryId/$repositoryId"
148-
}
149-
150141
fun configureEmptyJavadocArtifact(): TaskProvider<Jar?> {
151142
val javadocJar by project.tasks.registering(Jar::class) {
152143
archiveClassifier.set("javadoc")

gradle-plugin/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ org.gradle.parallel=true
1515
org.gradle.workers.max=8
1616
org.gradle.caching=true
1717
org.gradle.configuration-cache=true
18-
org.gradle.configureondemand=true
18+
#org.gradle.configureondemand=true // breaks compiler tests
1919

2020
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
2121
org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ org.gradle.parallel=true
1515
org.gradle.workers.max=8
1616
org.gradle.caching=true
1717
org.gradle.configuration-cache=true
18-
org.gradle.configureondemand=true
18+
#org.gradle.configureondemand=true // breaks compiler tests
1919

2020
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
2121
org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true

tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.fir.txt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,37 @@ FILE: b.kt
4848

4949
public final fun copy(flow1: R|kotlinx/coroutines/flow/Flow<kotlin/Int>| = this@R|/MultiFlow|.R|/MultiFlow.flow1|, flow2: R|kotlinx/coroutines/flow/Flow<kotlin/Int>| = this@R|/MultiFlow|.R|/MultiFlow.flow2|, flow3: R|kotlinx/coroutines/flow/Flow<kotlin/Int>| = this@R|/MultiFlow|.R|/MultiFlow.flow3|): R|MultiFlow|
5050

51+
}
52+
public final data class ComplexFilter : R|kotlin/Any| {
53+
public constructor(a: R|SimpleFilter?|, b: R|ComplexFilter?|): R|ComplexFilter| {
54+
super<R|kotlin/Any|>()
55+
}
56+
57+
public final val a: R|SimpleFilter?| = R|<local>/a|
58+
public get(): R|SimpleFilter?|
59+
60+
public final val b: R|ComplexFilter?| = R|<local>/b|
61+
public get(): R|ComplexFilter?|
62+
63+
public final operator fun component1(): R|SimpleFilter?|
64+
65+
public final operator fun component2(): R|ComplexFilter?|
66+
67+
public final fun copy(a: R|SimpleFilter?| = this@R|/ComplexFilter|.R|/ComplexFilter.a|, b: R|ComplexFilter?| = this@R|/ComplexFilter|.R|/ComplexFilter.b|): R|ComplexFilter|
68+
69+
}
70+
public final data class SimpleFilter : R|kotlin/Any| {
71+
public constructor(text: R|kotlin/String|): R|SimpleFilter| {
72+
super<R|kotlin/Any|>()
73+
}
74+
75+
public final val text: R|kotlin/String| = R|<local>/text|
76+
public get(): R|kotlin/String|
77+
78+
public final operator fun component1(): R|kotlin/String|
79+
80+
public final fun copy(text: R|kotlin/String| = this@R|/SimpleFilter|.R|/SimpleFilter.text|): R|SimpleFilter|
81+
5182
}
5283
@R|kotlinx/rpc/annotations/Rpc|() public abstract interface MyService : R|kotlin/Any| {
5384
public abstract val flow: R|kotlinx/coroutines/flow/Flow<kotlin/Int>|
@@ -105,6 +136,8 @@ FILE: b.kt
105136

106137
public abstract fun nonSuspendNoFlowString(): R|kotlin/String|
107138

139+
public abstract suspend fun complex(filter: R|ComplexFilter|): R|kotlin/String|
140+
108141
public final class $rpcServiceStub : R|kotlin/Any| {
109142
public final companion object Companion : R|kotlin/Any| {
110143
}

tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ data class MultiFlow(
2929
val flow3: Flow<Int>,
3030
)
3131

32+
data class ComplexFilter(
33+
val a: SimpleFilter?,
34+
val b: ComplexFilter?
35+
)
36+
37+
data class SimpleFilter(val text: String)
38+
3239
@Rpc
3340
interface MyService {
3441
<!FIELD_IN_RPC_SERVICE!>val flow: Flow<Int><!>
@@ -57,4 +64,5 @@ interface MyService {
5764
suspend fun clientNestedTrickyFlow(<!NESTED_STREAMING_IN_RPC_SERVICE!>inner: Wrapper<Flow<Wrapper<Flow<Int>>>><!>)
5865
<!NON_SUSPENDING_REQUEST_WITHOUT_STREAMING_RETURN_TYPE!>fun nonSuspendNoFlow()<!>
5966
<!NON_SUSPENDING_REQUEST_WITHOUT_STREAMING_RETURN_TYPE!>fun nonSuspendNoFlowString(): String<!>
67+
suspend fun complex(filter: ComplexFilter): String // doesn't fail on circular dependency
6068
}

0 commit comments

Comments
 (0)