Skip to content

Commit 7ff696b

Browse files
authored
Merge pull request #20726 from igfoo/igfoo/ClassInstanceStack
Kotlin: Avoid infinite recursion when extracting recursive interfaces
2 parents bda6513 + 06218d8 commit 7ff696b

File tree

16 files changed

+175
-7
lines changed

16 files changed

+175
-7
lines changed

java/kotlin-extractor/src/main/kotlin/ExternalDeclExtractor.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.codeql
22

3+
import com.github.codeql.utils.ClassInstanceStack
34
import com.github.codeql.utils.isExternalFileClassMember
45
import com.semmle.extractor.java.OdasaOutput
56
import com.semmle.util.data.StringDigestor
@@ -18,6 +19,7 @@ class ExternalDeclExtractor(
1819
val compression: Compression,
1920
val invocationTrapFile: String,
2021
val sourceFilePath: String,
22+
val classInstanceStack: ClassInstanceStack,
2123
val primitiveTypeMapping: PrimitiveTypeMapping,
2224
val pluginContext: IrPluginContext,
2325
val globalExtensionState: KotlinExtractorGlobalState,
@@ -163,6 +165,7 @@ class ExternalDeclExtractor(
163165
binaryPath,
164166
manager,
165167
this,
168+
classInstanceStack,
166169
primitiveTypeMapping,
167170
pluginContext,
168171
KotlinFileExtractor.DeclarationStack(),

java/kotlin-extractor/src/main/kotlin/KotlinExtractorExtension.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.codeql
22

3+
import com.github.codeql.utils.ClassInstanceStack
34
import com.github.codeql.utils.versions.usesK2
45
import com.semmle.util.files.FileUtil
56
import com.semmle.util.trap.pathtransformers.PathTransformer
@@ -151,6 +152,7 @@ class KotlinExtractorExtension(
151152
}
152153
val compression = getCompression(logger)
153154

155+
val classInstanceStack = ClassInstanceStack()
154156
val primitiveTypeMapping = PrimitiveTypeMapping(logger, pluginContext)
155157
// FIXME: FileUtil expects a static global logger
156158
// which should be provided by SLF4J's factory facility. For now we set it here.
@@ -182,6 +184,7 @@ class KotlinExtractorExtension(
182184
trapDir,
183185
srcDir,
184186
file,
187+
classInstanceStack,
185188
primitiveTypeMapping,
186189
pluginContext,
187190
globalExtensionState
@@ -358,6 +361,7 @@ private fun doFile(
358361
dbTrapDir: File,
359362
dbSrcDir: File,
360363
srcFile: IrFile,
364+
classInstanceStack: ClassInstanceStack,
361365
primitiveTypeMapping: PrimitiveTypeMapping,
362366
pluginContext: IrPluginContext,
363367
globalExtensionState: KotlinExtractorGlobalState
@@ -415,6 +419,7 @@ private fun doFile(
415419
compression,
416420
invocationTrapFile,
417421
srcFilePath,
422+
classInstanceStack,
418423
primitiveTypeMapping,
419424
pluginContext,
420425
globalExtensionState,
@@ -429,6 +434,7 @@ private fun doFile(
429434
srcFilePath,
430435
null,
431436
externalDeclExtractor,
437+
classInstanceStack,
432438
primitiveTypeMapping,
433439
pluginContext,
434440
KotlinFileExtractor.DeclarationStack(),

java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ open class KotlinFileExtractor(
6262
val filePath: String,
6363
dependencyCollector: OdasaOutput.TrapFileManager?,
6464
externalClassExtractor: ExternalDeclExtractor,
65+
classInstanceStack: ClassInstanceStack,
6566
primitiveTypeMapping: PrimitiveTypeMapping,
6667
pluginContext: IrPluginContext,
6768
val declarationStack: DeclarationStack,
@@ -72,6 +73,7 @@ open class KotlinFileExtractor(
7273
tw,
7374
dependencyCollector,
7475
externalClassExtractor,
76+
classInstanceStack,
7577
primitiveTypeMapping,
7678
pluginContext,
7779
globalExtensionState
@@ -496,12 +498,17 @@ open class KotlinFileExtractor(
496498
}
497499

498500
extractClassModifiers(c, id)
499-
extractClassSupertypes(
500-
c,
501-
id,
502-
if (argsIncludingOuterClasses == null) ExtractSupertypesMode.Raw
503-
else ExtractSupertypesMode.Specialised(argsIncludingOuterClasses)
504-
)
501+
classInstanceStack.push(c)
502+
try {
503+
extractClassSupertypes(
504+
c,
505+
id,
506+
if (argsIncludingOuterClasses == null) ExtractSupertypesMode.Raw
507+
else ExtractSupertypesMode.Specialised(argsIncludingOuterClasses)
508+
)
509+
} finally {
510+
classInstanceStack.pop()
511+
}
505512

506513
val locId = getLocation(c, argsIncludingOuterClasses)
507514
tw.writeHasLocation(id, locId)

java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ open class KotlinUsesExtractor(
4949
open val tw: TrapWriter,
5050
val dependencyCollector: OdasaOutput.TrapFileManager?,
5151
val externalClassExtractor: ExternalDeclExtractor,
52+
val classInstanceStack: ClassInstanceStack,
5253
val primitiveTypeMapping: PrimitiveTypeMapping,
5354
val pluginContext: IrPluginContext,
5455
val globalExtensionState: KotlinExtractorGlobalState
@@ -182,6 +183,7 @@ open class KotlinUsesExtractor(
182183
filePath,
183184
dependencyCollector,
184185
externalClassExtractor,
186+
classInstanceStack,
185187
primitiveTypeMapping,
186188
pluginContext,
187189
newDeclarationStack,
@@ -199,6 +201,7 @@ open class KotlinUsesExtractor(
199201
clsFile.path,
200202
dependencyCollector,
201203
externalClassExtractor,
204+
classInstanceStack,
202205
primitiveTypeMapping,
203206
pluginContext,
204207
newDeclarationStack,
@@ -537,6 +540,19 @@ open class KotlinUsesExtractor(
537540
return Pair(p?.first ?: c, p?.second ?: argsIncludingOuterClassesBeforeReplacement)
538541
}
539542

543+
private fun avoidInfiniteRecursion(
544+
pair: Pair<IrClass, List<IrTypeArgument>?>
545+
): Pair<IrClass, List<IrTypeArgument>?> {
546+
val c = pair.first
547+
val args = pair.second
548+
if (args != null && args.isNotEmpty() && classInstanceStack.possiblyCyclicExtraction(c, args)) {
549+
logger.warn("Making use of ${c.name} a raw type to avoid infinite recursion")
550+
return Pair(c, null)
551+
} else {
552+
return pair
553+
}
554+
}
555+
540556
// `typeArgs` can be null to describe a raw generic type.
541557
// For non-generic types it will be zero-length list.
542558
private fun addClassLabel(
@@ -545,7 +561,7 @@ open class KotlinUsesExtractor(
545561
inReceiverContext: Boolean = false
546562
): TypeResult<DbClassorinterface> {
547563
val replaced =
548-
tryReplaceType(cBeforeReplacement, argsIncludingOuterClassesBeforeReplacement)
564+
avoidInfiniteRecursion(tryReplaceType(cBeforeReplacement, argsIncludingOuterClassesBeforeReplacement))
549565
val replacedClass = replaced.first
550566
val replacedArgsIncludingOuterClasses = replaced.second
551567

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.github.codeql.utils
2+
3+
import java.util.Stack
4+
import org.jetbrains.kotlin.ir.declarations.IrClass
5+
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
6+
import org.jetbrains.kotlin.ir.types.*
7+
8+
class ClassInstanceStack {
9+
private val stack: Stack<IrClass> = Stack()
10+
11+
fun push(c: IrClass) = stack.push(c)
12+
fun pop() = stack.pop()
13+
14+
private fun checkTypeArgs(sym: IrClassSymbol, args: List<IrTypeArgument>): Boolean {
15+
for (arg in args) {
16+
if (arg is IrTypeProjection) {
17+
if (checkType(sym, arg.type)) {
18+
return true
19+
}
20+
}
21+
}
22+
return false
23+
}
24+
25+
private fun checkType(sym: IrClassSymbol, type: IrType): Boolean {
26+
if (type is IrSimpleType) {
27+
val decl = type.classifier.owner
28+
if (decl.symbol == sym) {
29+
return true
30+
}
31+
if (checkTypeArgs(sym, type.arguments)) {
32+
return true
33+
}
34+
}
35+
return false
36+
}
37+
38+
fun possiblyCyclicExtraction(classToCheck: IrClass, args: List<IrTypeArgument>): Boolean {
39+
for (c in stack) {
40+
if (c.symbol == classToCheck.symbol && checkTypeArgs(c.symbol, args)) {
41+
return true
42+
}
43+
}
44+
return false
45+
}
46+
}
47+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
package somepkg;
3+
4+
public interface IfaceA<T> extends IfaceB<T> {}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
package somepkg;
3+
4+
public interface IfaceB<T> extends IfaceC<IfaceA<IfaceB<T>>> {}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
package somepkg;
3+
4+
public interface IfaceC<T> {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
package somepkg;
3+
4+
public interface IfaceZ {
5+
public <T> IfaceA<String> someFun();
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package mypkg
2+
3+
import somepkg.IfaceZ
4+
5+
class SomeClass(private val myVal: IfaceZ) { }

0 commit comments

Comments
 (0)