diff --git a/integration-testing/.gitignore b/integration-testing/.gitignore new file mode 100644 index 0000000000..24d00437ce --- /dev/null +++ b/integration-testing/.gitignore @@ -0,0 +1,2 @@ +.kotlin +kotlin-js-store \ No newline at end of file diff --git a/integration-testing/build.gradle b/integration-testing/build.gradle index 64301dd9c5..8af3ec336e 100644 --- a/integration-testing/build.gradle +++ b/integration-testing/build.gradle @@ -52,6 +52,7 @@ repositories { java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 + modularity.inferModulePath = true } dependencies { @@ -117,6 +118,46 @@ sourceSets { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" } } + + debugDynamicAgentJpmsTest { + compileClasspath += sourceSets.test.runtimeClasspath + runtimeClasspath += sourceSets.test.runtimeClasspath + + dependencies { + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-debug:$coroutines_version" + } + } + + tasks.compileDebugDynamicAgentJpmsTestKotlin.configure { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } + } + + tasks.compileDebugDynamicAgentJpmsTestJava.configure { + options.release.set(17) + } + + debugDynamicAgentJpmsTest { + compileClasspath += sourceSets.test.runtimeClasspath + runtimeClasspath += sourceSets.test.runtimeClasspath + + dependencies { + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-debug:$coroutines_version" + } + } + + tasks.compileDebugDynamicAgentJpmsTestKotlin.configure { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } + } + + tasks.compileDebugDynamicAgentJpmsTestJava.configure { + options.release.set(17) + } } compileDebugAgentTestKotlin { @@ -154,6 +195,12 @@ task debugDynamicAgentTest(type: Test) { classpath = sourceSet.runtimeClasspath } +task debugDynamicAgentJpmsTest(type: Test) { + def sourceSet = sourceSets.debugDynamicAgentJpmsTest + testClassesDirs = sourceSet.output.classesDirs + classpath = sourceSet.runtimeClasspath +} + task coreAgentTest(type: Test) { def sourceSet = sourceSets.coreAgentTest def coroutinesCoreJar = sourceSet.runtimeClasspath.filter {it.name == "kotlinx-coroutines-core-jvm-${coroutines_version}.jar" }.singleFile @@ -167,7 +214,7 @@ compileTestKotlin { } check { - dependsOn([jvmCoreTest, debugDynamicAgentTest, mavenTest, debugAgentTest, coreAgentTest, 'smokeTest:build']) + dependsOn([jvmCoreTest, debugDynamicAgentTest, mavenTest, debugAgentTest, coreAgentTest, debugDynamicAgentJpmsTest, 'smokeTest:build']) } compileKotlin { kotlinOptions { diff --git a/integration-testing/src/debugDynamicAgentJpmsTest/java/module-info.java b/integration-testing/src/debugDynamicAgentJpmsTest/java/module-info.java new file mode 100644 index 0000000000..180d85c36a --- /dev/null +++ b/integration-testing/src/debugDynamicAgentJpmsTest/java/module-info.java @@ -0,0 +1,7 @@ +module debug.dynamic.agent.jpms.test { + requires kotlin.stdlib; + requires kotlinx.coroutines.core; + requires kotlinx.coroutines.debug; + requires junit; + requires kotlin.test; +} \ No newline at end of file diff --git a/integration-testing/src/debugDynamicAgentJpmsTest/kotlin/DynamicAttachDebugTest.kt b/integration-testing/src/debugDynamicAgentJpmsTest/kotlin/DynamicAttachDebugTest.kt new file mode 100644 index 0000000000..caaa798230 --- /dev/null +++ b/integration-testing/src/debugDynamicAgentJpmsTest/kotlin/DynamicAttachDebugTest.kt @@ -0,0 +1,46 @@ +@file:OptIn(ExperimentalCoroutinesApi::class) + +import org.junit.* +import kotlinx.coroutines.* +import kotlinx.coroutines.debug.* +import org.junit.Ignore +import org.junit.Test +import java.io.* +import java.lang.IllegalStateException +import kotlin.test.* + +class DynamicAttachDebugTest { + + /** + * Using: + * + * jvmArgs("--add-exports=kotlinx.coroutines.debug/kotlinx.coroutines.repackaged.net.bytebuddy=com.sun.jna") + * jvmArgs("--add-exports=kotlinx.coroutines.debug/kotlinx.coroutines.repackaged.net.bytebuddy.agent=com.sun.jna") + * + * + * Caused by: java.lang.IllegalStateException: The Byte Buddy agent is not loaded or this method is not called via the system class loader + * at kotlinx.coroutines.debug/kotlinx.coroutines.repackaged.net.bytebuddy.agent.Installer.getInstrumentation(Installer.java:61) + * ... 54 more + */ + @Ignore("shaded byte-buddy does not work with JPMS") + @Test + fun testAgentDumpsCoroutines() = + DebugProbes.withDebugProbes { + runBlocking { + val baos = ByteArrayOutputStream() + DebugProbes.dumpCoroutines(PrintStream(baos)) + // if the agent works, then dumps should contain something, + // at least the fact that this test is running. + Assert.assertTrue(baos.toString().contains("testAgentDumpsCoroutines")) + } + } + + @Test() + fun testAgentIsNotInstalled() { + assertEquals(false, DebugProbes.isInstalled) + assertFailsWith { + DebugProbes.dumpCoroutines(PrintStream(ByteArrayOutputStream())) + } + } + +} diff --git a/kotlinx-coroutines-core/build.gradle.kts b/kotlinx-coroutines-core/build.gradle.kts index 9941dcc1ee..d6abbd7e8a 100644 --- a/kotlinx-coroutines-core/build.gradle.kts +++ b/kotlinx-coroutines-core/build.gradle.kts @@ -188,7 +188,7 @@ val allMetadataJar by tasks.getting(Jar::class) { setupManifest(this) } fun setupManifest(jar: Jar) { jar.manifest { attributes(mapOf( - "Premain-Class" to "kotlinx.coroutines.debug.AgentPremain", + "Premain-Class" to "kotlinx.coroutines.debug.internal.AgentPremain", "Can-Retransform-Classes" to "true", )) } diff --git a/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/proguard/coroutines.pro b/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/proguard/coroutines.pro index c3911b83b5..1380396073 100644 --- a/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/proguard/coroutines.pro +++ b/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/proguard/coroutines.pro @@ -16,7 +16,7 @@ volatile ; } -# These classes are only required by kotlinx.coroutines.debug.AgentPremain, which is only loaded when +# These classes are only required by kotlinx.coroutines.debug.internal.AgentPremain, which is only loaded when # kotlinx-coroutines-core is used as a Java agent, so these are not needed in contexts where ProGuard is used. -dontwarn java.lang.instrument.ClassFileTransformer -dontwarn sun.misc.SignalHandler diff --git a/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/r8/coroutines.pro b/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/r8/coroutines.pro index 1ac5ce5710..69a28956ac 100644 --- a/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/r8/coroutines.pro +++ b/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/r8/coroutines.pro @@ -12,7 +12,7 @@ volatile ; } -# These classes are only required by kotlinx.coroutines.debug.AgentPremain, which is only loaded when +# These classes are only required by kotlinx.coroutines.debug.internal.AgentPremain, which is only loaded when # kotlinx-coroutines-core is used as a Java agent, so these are not needed in contexts where ProGuard is used. -dontwarn java.lang.instrument.ClassFileTransformer -dontwarn sun.misc.SignalHandler diff --git a/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro b/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro index 6d29ed25ce..874b097457 100644 --- a/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro +++ b/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro @@ -16,7 +16,7 @@ volatile ; } -# These classes are only required by kotlinx.coroutines.debug.AgentPremain, which is only loaded when +# These classes are only required by kotlinx.coroutines.debug.internal.AgentPremain, which is only loaded when # kotlinx-coroutines-core is used as a Java agent, so these are not needed in contexts where ProGuard is used. -dontwarn java.lang.instrument.ClassFileTransformer -dontwarn sun.misc.SignalHandler diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/AgentInstallationType.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/AgentInstallationType.kt index 15eead7fc3..570d6d0771 100644 --- a/kotlinx-coroutines-core/jvm/src/debug/internal/AgentInstallationType.kt +++ b/kotlinx-coroutines-core/jvm/src/debug/internal/AgentInstallationType.kt @@ -3,9 +3,13 @@ package kotlinx.coroutines.debug.internal /** * Object used to differentiate between agent installed statically or dynamically. * This is done in a separate object so [DebugProbesImpl] can check for static installation - * without having to depend on [kotlinx.coroutines.debug.AgentPremain], which is not compatible with Android. + * without having to depend on [AgentPremain], which is not compatible with Android. * Otherwise, access to `AgentPremain.isInstalledStatically` triggers the load of its internal `ClassFileTransformer` * that is not available on Android. + * + * Usage Note: Fleet (Reflection): FleetDebugProbes + * Usage Note: Android (Hard Coded, ignored for Leak Detection) + * Usage Note: IntelliJ (Suppress KotlinInternalInJava): CoroutineDumpState */ internal object AgentInstallationType { internal var isInstalledStatically = false diff --git a/kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/AgentPremain.kt similarity index 97% rename from kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt rename to kotlinx-coroutines-core/jvm/src/debug/internal/AgentPremain.kt index 4f8abb8700..8d0c557ed2 100644 --- a/kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt +++ b/kotlinx-coroutines-core/jvm/src/debug/internal/AgentPremain.kt @@ -1,7 +1,6 @@ -package kotlinx.coroutines.debug +package kotlinx.coroutines.debug.internal import android.annotation.* -import kotlinx.coroutines.debug.internal.* import org.codehaus.mojo.animal_sniffer.* import sun.misc.* import java.lang.instrument.* diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt index 2ab03a770c..25594ad01a 100644 --- a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt +++ b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt @@ -51,7 +51,7 @@ internal object DebugProbesImpl { @Suppress("UNCHECKED_CAST") private fun getDynamicAttach(): Function1? = runCatching { - val clz = Class.forName("kotlinx.coroutines.debug.internal.ByteBuddyDynamicAttach") + val clz = Class.forName("kotlinx.coroutines.debug.ByteBuddyDynamicAttach") val ctor = clz.constructors[0] ctor.newInstance() as Function1 }.getOrNull() diff --git a/kotlinx-coroutines-core/jvm/src/module-info.java b/kotlinx-coroutines-core/jvm/src/module-info.java index 2759a34296..ad4c2ee066 100644 --- a/kotlinx-coroutines-core/jvm/src/module-info.java +++ b/kotlinx-coroutines-core/jvm/src/module-info.java @@ -5,13 +5,12 @@ requires transitive kotlin.stdlib; requires kotlinx.atomicfu; - // these are used by kotlinx.coroutines.debug.AgentPremain + // these are used by kotlinx.coroutines.debug.internal.AgentPremain requires static java.instrument; // contains java.lang.instrument.* requires static jdk.unsupported; // contains sun.misc.Signal exports kotlinx.coroutines; exports kotlinx.coroutines.channels; - exports kotlinx.coroutines.debug; exports kotlinx.coroutines.debug.internal; exports kotlinx.coroutines.flow; exports kotlinx.coroutines.flow.internal; diff --git a/kotlinx-coroutines-debug/build.gradle.kts b/kotlinx-coroutines-debug/build.gradle.kts index a9b2d6730d..1d0be22bfd 100644 --- a/kotlinx-coroutines-debug/build.gradle.kts +++ b/kotlinx-coroutines-debug/build.gradle.kts @@ -68,7 +68,7 @@ val shadowJar by tasks.existing(ShadowJar::class) { manifest { attributes( mapOf( - "Premain-Class" to "kotlinx.coroutines.debug.AgentPremain", + "Premain-Class" to "kotlinx.coroutines.debug.internal.AgentPremain", "Can-Redefine-Classes" to "true", "Multi-Release" to "true" ) @@ -104,7 +104,7 @@ kover { filters { excludes { // Never used, safety mechanism - classes("kotlinx.coroutines.debug.internal.NoOpProbesKt") + classes("kotlinx.coroutines.debug.NoOpProbesKt") } } } diff --git a/kotlinx-coroutines-debug/src/internal/Attach.kt b/kotlinx-coroutines-debug/src/Attach.kt similarity index 90% rename from kotlinx-coroutines-debug/src/internal/Attach.kt rename to kotlinx-coroutines-debug/src/Attach.kt index 63bfe8eafe..291d913f3e 100644 --- a/kotlinx-coroutines-debug/src/internal/Attach.kt +++ b/kotlinx-coroutines-debug/src/Attach.kt @@ -1,5 +1,5 @@ @file:Suppress("unused") -package kotlinx.coroutines.debug.internal +package kotlinx.coroutines.debug import net.bytebuddy.* import net.bytebuddy.agent.* @@ -28,7 +28,7 @@ internal class ByteBuddyDynamicAttach : Function1 { private fun detach() { val cl = Class.forName("kotlin.coroutines.jvm.internal.DebugProbesKt") - val cl2 = Class.forName("kotlinx.coroutines.debug.internal.NoOpProbesKt") + val cl2 = Class.forName("kotlinx.coroutines.debug.NoOpProbesKt") ByteBuddy() .redefine(cl2) .name(cl.name) diff --git a/kotlinx-coroutines-debug/src/internal/NoOpProbes.kt b/kotlinx-coroutines-debug/src/NoOpProbes.kt similarity index 92% rename from kotlinx-coroutines-debug/src/internal/NoOpProbes.kt rename to kotlinx-coroutines-debug/src/NoOpProbes.kt index b7b8bf50d4..927936eaa9 100644 --- a/kotlinx-coroutines-debug/src/internal/NoOpProbes.kt +++ b/kotlinx-coroutines-debug/src/NoOpProbes.kt @@ -1,6 +1,6 @@ @file:Suppress("unused", "UNUSED_PARAMETER") -package kotlinx.coroutines.debug.internal +package kotlinx.coroutines.debug import kotlin.coroutines.* diff --git a/kotlinx-coroutines-debug/src/module-info.java b/kotlinx-coroutines-debug/src/module-info.java index 2c7571ec0d..a23e422eee 100644 --- a/kotlinx-coroutines-debug/src/module-info.java +++ b/kotlinx-coroutines-debug/src/module-info.java @@ -8,7 +8,7 @@ requires org.junit.jupiter.api; requires org.junit.platform.commons; -// exports kotlinx.coroutines.debug; // already exported by kotlinx.coroutines.core + exports kotlinx.coroutines.debug; exports kotlinx.coroutines.debug.junit4; exports kotlinx.coroutines.debug.junit5; }