Skip to content

Commit 67f990b

Browse files
authored
Fix concurrency issue in PluginInfo.kt (#275)
1 parent bf03852 commit 67f990b

7 files changed

Lines changed: 61 additions & 53 deletions

File tree

modules/intellij-shared/api/intellij-shared.api

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,9 @@ public final class dev/ghostflyby/intellij/AutoCleanKeyKt {
1515
public static final fun toAutoCleanKey (Lcom/intellij/openapi/util/Key;Lkotlinx/coroutines/CoroutineScope;)Ldev/ghostflyby/intellij/AutoCleanKey;
1616
}
1717

18-
public final class dev/ghostflyby/intellij/PluginInfoProvider {
19-
public fun <init> (Ljava/lang/Class;)V
20-
public fun <init> (Ljava/lang/ClassLoader;)V
21-
public final fun getId ()Ljava/lang/String;
22-
public final fun getName ()Ljava/lang/String;
23-
public final fun getPluginDescriptor ()Lcom/intellij/openapi/extensions/PluginDescriptor;
24-
public final fun getVersion ()Ljava/lang/String;
18+
public final class dev/ghostflyby/intellij/PluginInfoKt {
19+
public static final fun getPluginId (Ljava/lang/Class;)Lcom/intellij/openapi/extensions/PluginId;
20+
public static final fun getPluginName (Ljava/lang/Class;)Ljava/lang/String;
21+
public static final fun getPluginVersion (Ljava/lang/Class;)Ljava/lang/String;
2522
}
2623

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright (c) 2026 ghostflyby
3+
* SPDX-FileCopyrightText: 2026 ghostflyby
4+
* SPDX-License-Identifier: LGPL-3.0-or-later
5+
*/
6+
7+
package dev.ghostflyby.intellij
8+
9+
import com.intellij.ide.plugins.cl.PluginAwareClassLoader
10+
import com.intellij.openapi.extensions.PluginDescriptor
11+
import com.intellij.openapi.extensions.PluginId
12+
13+
public val Class<*>.pluginId: PluginId
14+
get() = requirePluginAwareClassLoader().pluginId
15+
16+
public val Class<*>.pluginName: String
17+
get() = readPluginDescriptor { name }
18+
19+
public val Class<*>.pluginVersion: String
20+
get() = readPluginDescriptor { version }
21+
22+
private inline fun <T> Class<*>.readPluginDescriptor(read: PluginDescriptor.() -> T): T {
23+
return read(requirePluginAwareClassLoader().pluginDescriptor)
24+
}
25+
26+
private fun Class<*>.requirePluginAwareClassLoader(): PluginAwareClassLoader {
27+
val classLoader = classLoader
28+
require(classLoader is PluginAwareClassLoader) {
29+
"Plugin metadata for $name requires a PluginAwareClassLoader, " +
30+
"but got ${classLoader?.javaClass?.name ?: "the bootstrap class loader"}."
31+
}
32+
return classLoader
33+
}

modules/intellij-shared/src/main/kotlin/dev/ghostflyby/intellij/PluginInfoProvider.kt

Lines changed: 0 additions & 39 deletions
This file was deleted.

modules/intellij-shared/src/test/kotlin/dev/ghostflyby/intellij/PluginInfoTest.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,37 @@
66

77
package dev.ghostflyby.intellij
88

9+
import com.intellij.ide.plugins.cl.PluginAwareClassLoader
10+
import com.intellij.openapi.extensions.PluginDescriptor
911
import org.junit.jupiter.api.Assertions.assertThrows
1012
import org.junit.jupiter.api.Assertions.assertTrue
1113
import org.junit.jupiter.api.Test
14+
import java.lang.reflect.Proxy
1215
import java.net.URLClassLoader
1316

1417
internal class PluginInfoTest {
1518
@Test
1619
fun rejectsPlainClassLoader() {
1720
URLClassLoader(emptyArray(), null).use { classLoader ->
18-
val info = PluginInfoProvider(classLoader)
21+
val anchorClass = Proxy.newProxyInstance(
22+
classLoader,
23+
arrayOf(Runnable::class.java),
24+
) { _, _, _ -> null }.javaClass
1925

2026
val error = assertThrows(IllegalArgumentException::class.java) {
21-
info.id
27+
anchorClass.pluginId
2228
}
2329

2430
assertTrue(error.message.orEmpty().contains("PluginAwareClassLoader"))
2531
}
2632
}
33+
34+
@Test
35+
fun doesNotStorePluginClassLoaderState() {
36+
val fields = Class.forName("dev.ghostflyby.intellij.PluginInfoKt").declaredFields
37+
38+
assertTrue(fields.none { ClassLoader::class.java.isAssignableFrom(it.type) })
39+
assertTrue(fields.none { PluginAwareClassLoader::class.java.isAssignableFrom(it.type) })
40+
assertTrue(fields.none { PluginDescriptor::class.java.isAssignableFrom(it.type) })
41+
}
2742
}

plugins/WorkspaceMcpTools/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
## [Unreleased]
66

7+
### Fixed
8+
9+
- NPE on server launch due to `lazy` concurrency [#275](https://github.com/ghostflyby/IntelliJ-Plugins/pull/275)
10+
711
## [2.0.1] - 2026-07-02
812

913
### Fixed

plugins/WorkspaceMcpTools/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ plugins {
1212
alias(libs.plugins.kotlin.serialization)
1313
}
1414

15-
version = "2.0.1"
15+
version = "2.0.2"
1616

1717
dependencies {
1818
implementation(libs.ktor.resources)

plugins/WorkspaceMcpTools/src/main/kotlin/dev/ghostflyby/mcp/PluginInfo.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66

77
package dev.ghostflyby.mcp
88

9-
import dev.ghostflyby.intellij.PluginInfoProvider
10-
11-
internal val PluginInfo = PluginInfoProvider(WorkspaceMcpStartupActivity::class.java)
9+
import dev.ghostflyby.intellij.pluginVersion
1210

1311
internal val pluginVersion: String
14-
get() = PluginInfo.version
12+
get() = WorkspaceMcpStartupActivity::class.java.pluginVersion

0 commit comments

Comments
 (0)