diff --git a/.gitignore b/.gitignore index 08c561e..7e762bc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ .idea build local.properties -/stacktrace-decoroutinator-android/src/main/resources/decoroutinatorBaseContinuation.dex diff --git a/README.md b/README.md index 8bcc4bc..5e4369b 100644 --- a/README.md +++ b/README.md @@ -84,9 +84,9 @@ Thus, if the coroutine throws an exception, they mimic the real call stack of th ### JVM There are three ways to enable Stacktrace-decoroutinator for JVM. -1. (recommended) Add dependency `dev.reformator.stacktracedecoroutinator:stacktrace-decoroutinator-jvm:2.3.7` and call method `DecoroutinatorRuntime.load()`. -2. Add `-javaagent:stacktrace-decoroutinator-jvm-agent-2.3.7.jar` to your JVM start arguments. Corresponding dependency is `dev.reformator.stacktracedecoroutinator:stacktrace-decoroutinator-jvm-agent:2.3.7`. -3. (less recommended) Add dependency `dev.reformator.stacktracedecoroutinator:stacktrace-decoroutinator-jvm-legacy:2.3.7` and call method `DecoroutinatorRuntime.load()`. This way doesn't use Java instrumentation API unlike the way number 1. +1. (recommended) Add dependency `dev.reformator.stacktracedecoroutinator:stacktrace-decoroutinator-jvm:2.3.8` and call method `DecoroutinatorRuntime.load()`. +2. Add `-javaagent:stacktrace-decoroutinator-jvm-agent-2.3.8.jar` to your JVM start arguments. Corresponding dependency is `dev.reformator.stacktracedecoroutinator:stacktrace-decoroutinator-jvm-agent:2.3.8`. +3. (less recommended) Add dependency `dev.reformator.stacktracedecoroutinator:stacktrace-decoroutinator-jvm-legacy:2.3.8` and call method `DecoroutinatorRuntime.load()`. This way doesn't use Java instrumentation API unlike the way number 1. Usage example: ```kotlin @@ -152,7 +152,7 @@ java.lang.Exception: exception at 1653565535416 ``` ### Android -To enable Stacktrace-decoroutinator for Android you should add dependency `dev.reformator.stacktracedecoroutinator:stacktrace-decoroutinator-android:2.3.7` in your Android application and call method `DecoroutinatorRuntime.load()` before creating any coroutines. +To enable Stacktrace-decoroutinator for Android you should add dependency `dev.reformator.stacktracedecoroutinator:stacktrace-decoroutinator-android:2.3.8` in your Android application and call method `DecoroutinatorRuntime.load()` before creating any coroutines. It's recomended to add `DecoroutinatorRuntime.load()` call in your `Application.onCreate()` method. Example: ```kotlin diff --git a/build.gradle.kts b/build.gradle.kts index c8cdd2c..106054c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ buildscript { } } dependencies { - classpath("com.android.tools.build:gradle:7.0.4") + classpath("com.android.tools.build:gradle:8.2.0") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.22") classpath("gradle.plugin.com.github.johnrengelman:shadow:7.1.2") } @@ -15,5 +15,5 @@ buildscript { subprojects { group = "dev.reformator.stacktracedecoroutinator" - version = "2.3.7" + version = "2.3.8" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 69a9715..a595206 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/stacktrace-decoroutinator-android/baseContinuationContent.ktTemplate b/stacktrace-decoroutinator-android/baseContinuationContent.ktTemplate new file mode 100644 index 0000000..cc9b1b6 --- /dev/null +++ b/stacktrace-decoroutinator-android/baseContinuationContent.ktTemplate @@ -0,0 +1,5 @@ +package dev.reformator.stacktracedecoroutinator.android.utils + +import android.util.Base64 + +internal val baseContinuationDexBody: ByteArray = Base64.decode("$CONTENT$", Base64.DEFAULT) diff --git a/stacktrace-decoroutinator-android/build.gradle.kts b/stacktrace-decoroutinator-android/build.gradle.kts index cde463f..55a7f1b 100644 --- a/stacktrace-decoroutinator-android/build.gradle.kts +++ b/stacktrace-decoroutinator-android/build.gradle.kts @@ -1,3 +1,5 @@ +import java.util.* + plugins { id("com.android.library") id("kotlin-android") @@ -5,6 +7,11 @@ plugins { signing } +val baseContinuationDexSourcesDir = layout.buildDirectory.get() + .dir("generated") + .dir("baseContinuationDexSources") + .asFile + android { compileSdk = 26 @@ -21,6 +28,12 @@ android { kotlinOptions { jvmTarget = "1.8" } + + sourceSets { + android.sourceSets["main"].java.srcDir(baseContinuationDexSourcesDir) + } + + namespace = "dev.reformator.stacktracedecoroutinator" } repositories { @@ -39,18 +52,10 @@ dependencies { androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:${properties["kotlinxCoroutinesVersion"]}") } -val baseContinuationDexFile = - projectDir - .resolve("src") - .resolve("main") - .resolve("resources") - .resolve("decoroutinatorBaseContinuation.dex") - -val generateBaseContinuationDexTask = task("generateBaseContinuationDex") { +val generateBaseContinuationDexSourcesTask = task("generateBaseContinuationDexSources") { dependsOn(":stdlib:jar") doLast { - baseContinuationDexFile.delete() - val destFolder = baseContinuationDexFile.parent + baseContinuationDexSourcesDir.deleteRecursively() val tmpDir = temporaryDir val jarFile = project(":stdlib") .tasks @@ -67,21 +72,16 @@ val generateBaseContinuationDexTask = task("generateBaseContinuationDex") { jarFile.absolutePath ) } - copy { - from(tmpDir) - into(destFolder) - include("classes.dex") - rename("classes.dex", baseContinuationDexFile.name) - } + baseContinuationDexSourcesDir.mkdirs() + file(baseContinuationDexSourcesDir.resolve("baseContinuationContent.kt")).writeText( + file(projectDir.resolve("baseContinuationContent.ktTemplate")).readText() + .replace("\$CONTENT\$", Base64.getEncoder().encodeToString(tmpDir.resolve("classes.dex").readBytes())) + ) } } -tasks.clean { - delete(baseContinuationDexFile) -} - tasks.named("preBuild") { - dependsOn(generateBaseContinuationDexTask) + dependsOn(generateBaseContinuationDexSourcesTask) } val androidJavadocs = task("androidJavadocs", Javadoc::class) { diff --git a/stacktrace-decoroutinator-android/src/androidTest/AndroidManifest.xml b/stacktrace-decoroutinator-android/src/androidTest/AndroidManifest.xml index 4997203..e8d47d3 100644 --- a/stacktrace-decoroutinator-android/src/androidTest/AndroidManifest.xml +++ b/stacktrace-decoroutinator-android/src/androidTest/AndroidManifest.xml @@ -1,4 +1,3 @@ - + diff --git a/stacktrace-decoroutinator-android/src/main/kotlin/runtime-android.kt b/stacktrace-decoroutinator-android/src/main/kotlin/runtime-android.kt index 3b6fc54..8a74f33 100644 --- a/stacktrace-decoroutinator-android/src/main/kotlin/runtime-android.kt +++ b/stacktrace-decoroutinator-android/src/main/kotlin/runtime-android.kt @@ -3,6 +3,7 @@ package dev.reformator.stacktracedecoroutinator.runtime import dalvik.system.BaseDexClassLoader import dalvik.system.InMemoryDexClassLoader import dev.reformator.stacktracedecoroutinator.android.DecoroutinatorAndroidStacktraceMethodHandleRegistryImpl +import dev.reformator.stacktracedecoroutinator.android.utils.baseContinuationDexBody import dev.reformator.stacktracedecoroutinator.common.* import java.lang.reflect.Field import java.nio.ByteBuffer @@ -34,10 +35,7 @@ object DecoroutinatorRuntime { val dexElements = dexElementsField[pathList] as Array val decoroutinatorDexElements = run { - val dexBody = loader.getResourceAsStream("decoroutinatorBaseContinuation.dex").use{ - it.readBytes() - } - val dexClassLoader = InMemoryDexClassLoader(ByteBuffer.wrap(dexBody), null) + val dexClassLoader = InMemoryDexClassLoader(ByteBuffer.wrap(baseContinuationDexBody), null) val pathList = pathListField[dexClassLoader] dexElementsField[pathList] as Array } diff --git a/stacktrace-decoroutinator-jvm-legacy/build.gradle.kts b/stacktrace-decoroutinator-jvm-legacy/build.gradle.kts index 0fca42f..d5faaf5 100644 --- a/stacktrace-decoroutinator-jvm-legacy/build.gradle.kts +++ b/stacktrace-decoroutinator-jvm-legacy/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { tasks.test { useJUnitPlatform() systemProperty("kotlinx.coroutines.stacktrace.recovery", "false") + jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED") } tasks.withType { diff --git a/stdlib/src/main/kotlin/continuation-stdlib.kt b/stdlib/src/main/kotlin/continuation-stdlib.kt index 220cb92..17e4e38 100644 --- a/stdlib/src/main/kotlin/continuation-stdlib.kt +++ b/stdlib/src/main/kotlin/continuation-stdlib.kt @@ -4,7 +4,11 @@ import dev.reformator.stacktracedecoroutinator.common.DecoroutinatorMarker import dev.reformator.stacktracedecoroutinator.common.decoroutinatorRegistry import dev.reformator.stacktracedecoroutinator.common.JavaUtilsImpl import dev.reformator.stacktracedecoroutinator.stdlib.decoroutinatorResumeWith +import dev.reformator.stacktracedecoroutinator.stdlib.invokeSuspendFunc import java.io.Serializable +import java.lang.invoke.MethodHandle +import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodType import kotlin.coroutines.Continuation import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED @@ -15,9 +19,14 @@ internal abstract class BaseContinuationImpl( ): Continuation, CoroutineStackFrame, Serializable { final override fun resumeWith(result: Result) { if (decoroutinatorRegistry.enabled) { + val invokeSuspendFuncLocal: MethodHandle = invokeSuspendFunc ?: run { + val result = MethodHandles.lookup().findVirtual(BaseContinuationImpl::class.java, "invokeSuspend", MethodType.methodType(Object::class.java, Object::class.java)) + invokeSuspendFunc = result + result + } decoroutinatorResumeWith( result, - invokeSuspendFunc = { invokeSuspend(it) }, + invokeSuspendFunc = invokeSuspendFuncLocal, releaseInterceptedFunc = { releaseIntercepted() } ) } else { diff --git a/stdlib/src/main/kotlin/stdlib.kt b/stdlib/src/main/kotlin/stdlib.kt index 73e5bb0..ef2cfdf 100644 --- a/stdlib/src/main/kotlin/stdlib.kt +++ b/stdlib/src/main/kotlin/stdlib.kt @@ -9,9 +9,11 @@ import kotlin.coroutines.Continuation import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED import kotlin.coroutines.jvm.internal.BaseContinuationImpl +var invokeSuspendFunc: MethodHandle? = null + internal fun BaseContinuationImpl.decoroutinatorResumeWith( result: Result, - invokeSuspendFunc: BaseContinuationImpl.(Result) -> Any?, + invokeSuspendFunc: MethodHandle, releaseInterceptedFunc: BaseContinuationImpl.() -> Unit ) { val baseContinuations = buildList { @@ -33,7 +35,7 @@ internal fun BaseContinuationImpl.decoroutinatorResumeWith( val continuation = baseContinuations[index] JavaUtilsImpl.probeCoroutineResumed(continuation) val newResult = try { - val newResult = continuation.invokeSuspendFunc(Result.success(result)) + val newResult = invokeSuspendFunc.invokeExact(continuation, result) if (newResult === COROUTINE_SUSPENDED) { return@BiFunction COROUTINE_SUSPENDED }