From e2fb3a7b5b700816678306cfad8f57e576b690bf Mon Sep 17 00:00:00 2001 From: slop Date: Mon, 13 Apr 2026 08:14:29 -0700 Subject: [PATCH 1/2] Fix PsiEnvironment thread safety in SymbolExtractor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit extractSymbolsFromDirs and extractDependenciesFromBuildFile each created a new PsiEnvironment per call. When ContextTask runs these in parallel across 4 threads, multiple threads simultaneously call KotlinCoreEnvironment.createForProduction(), racing on IntelliJ's global ApplicationManager singleton and leaving the internal lateinit module uninitialized — crashing every project with "lateinit property module has not been initialized". Switch both methods to use PsiEnvironment.shared() with synchronized access, matching the pattern already used by scanSources. --- .../zone/clanker/gradle/srcx/scan/SymbolExtractor.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt b/src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt index 38cdc34..b87b519 100644 --- a/src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt +++ b/src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt @@ -74,7 +74,8 @@ object SymbolExtractor { } if (sourceFiles.isEmpty()) return emptyList() - return PsiEnvironment().use { env -> + val env = PsiEnvironment.shared() ?: return emptyList() + return synchronized(env) { val parser = PsiParser(env) sourceFiles.flatMap { file -> val sourceDir = dirs.first { file.startsWith(it) } @@ -287,9 +288,10 @@ object SymbolExtractor { ?: File(projectDir, "build.gradle").takeIf { it.exists() } ?: return emptyList() - return PsiEnvironment().use { env -> + val env = PsiEnvironment.shared() ?: return emptyList() + return synchronized(env) { val vf = LightVirtualFile(buildFile.name, KotlinFileType.INSTANCE, buildFile.readText()) - val ktFile = env.psiManager.findFile(vf) as? KtFile ?: return@use emptyList() + val ktFile = env.psiManager.findFile(vf) as? KtFile ?: return@synchronized emptyList() ktFile .collectDescendantsOfType() From e3b8779a0bd61999fca15a9ba277acce7b642e32 Mon Sep 17 00:00:00 2001 From: slop Date: Mon, 13 Apr 2026 08:22:22 -0700 Subject: [PATCH 2/2] Log full stack traces for PSI and analysis failures Pass the exception as the second argument to logger.warn/error in handleAnalysisFailure, extractSymbolsFromDirs, and PsiEnvironment.shared so Gradle prints the full stack trace instead of just the message. --- .../kotlin/zone/clanker/gradle/srcx/parse/PsiEnvironment.kt | 1 + .../kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/zone/clanker/gradle/srcx/parse/PsiEnvironment.kt b/src/main/kotlin/zone/clanker/gradle/srcx/parse/PsiEnvironment.kt index 8c142d6..1e8169e 100644 --- a/src/main/kotlin/zone/clanker/gradle/srcx/parse/PsiEnvironment.kt +++ b/src/main/kotlin/zone/clanker/gradle/srcx/parse/PsiEnvironment.kt @@ -68,6 +68,7 @@ class PsiEnvironment : AutoCloseable { logger.error( "srcx: PSI environment initialization failed: ${e.message}. " + "Hub classes and entry points will not be available.", + e, ) }.getOrNull() } diff --git a/src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt b/src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt index b87b519..920d8c7 100644 --- a/src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt +++ b/src/main/kotlin/zone/clanker/gradle/srcx/scan/SymbolExtractor.kt @@ -52,7 +52,7 @@ object SymbolExtractor { throw e } else -> { - logger.warn("srcx: Analysis failed for '$projectName': ${e.message}") + logger.warn("srcx: Analysis failed for '$projectName': ${e.message}", e) return null } } @@ -83,6 +83,8 @@ object SymbolExtractor { parser.extractDeclarations(file).map { symbol -> symbol.toEntry(sourceDir) } + }.onFailure { e -> + logger.warn("srcx: Symbol extraction failed for '${file.name}': ${e.message}", e) }.getOrDefault(emptyList()) } }