From c3e98344b57c2f36ccae8414f21037e69890195f Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 22 Oct 2026 13:41:29 +0200 Subject: [PATCH 1/4] fix(build): Strip MethodParameters so AGP 8.0 and older can dex the SDK javac 21 writes nameless MethodParameters entries for synthetic and mandated parameters (JDK-8292275), which crash D8 older than 8.1 with a NullPointerException. Remove the attribute from the main Java classes of published modules after compiling, restoring the javac 17 output. Fixes #6355 Co-Authored-By: Claude Opus 5.5 --- build-logic/build.gradle.kts | 1 + .../io/sentry/gradle/JdkTargetConvention.kt | 7 +++ .../io/sentry/gradle/StripMethodParameters.kt | 57 +++++++++++++++++++ gradle/libs.versions.toml | 2 + 4 files changed, 67 insertions(+) create mode 100644 build-logic/src/main/kotlin/io/sentry/gradle/StripMethodParameters.kt diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index d02ab58488..60a340c9b8 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -5,6 +5,7 @@ plugins { dependencies { implementation(libs.animalsniffer.gradle.plugin) implementation(libs.spotlessLib) + implementation(libs.asm) compileOnly(libs.kotlin.gradle.plugin) compileOnly(libs.android.gradle.plugin) } diff --git a/build-logic/src/main/kotlin/io/sentry/gradle/JdkTargetConvention.kt b/build-logic/src/main/kotlin/io/sentry/gradle/JdkTargetConvention.kt index c92c83bbac..dea3a08b50 100644 --- a/build-logic/src/main/kotlin/io/sentry/gradle/JdkTargetConvention.kt +++ b/build-logic/src/main/kotlin/io/sentry/gradle/JdkTargetConvention.kt @@ -27,6 +27,7 @@ private val PUBLISHED_JAVA_VERSION = JavaVersion.VERSION_1_8 * - sample apps target the build JDK (21) * - unit test code will target the build JDK (21) (required for Mockito 5) * - published JVM modules main code pins the Java 1.8 release, for Java and for Kotlin + * - published modules main Java code drops the MethodParameters attribute javac 21 adds * - published Android modules main code targets Java 1.8 * - minSdk is set for Android libraries */ @@ -39,6 +40,12 @@ class JdkTargetConvention : Plugin { } project.plugins.withId("com.vanniktech.maven.publish") { + // Published Java classes must stay dexable by older D8 versions, see StripMethodParameters + project.tasks.withType(JavaCompile::class.java).configureEach { + if (!name.contains("Test")) { + doLast(StripMethodParameters()) + } + } // Only works for non-android modules // See https://issuetracker.google.com/issues/278800528 project.plugins.withId("java-library") { diff --git a/build-logic/src/main/kotlin/io/sentry/gradle/StripMethodParameters.kt b/build-logic/src/main/kotlin/io/sentry/gradle/StripMethodParameters.kt new file mode 100644 index 0000000000..2701baf1a3 --- /dev/null +++ b/build-logic/src/main/kotlin/io/sentry/gradle/StripMethodParameters.kt @@ -0,0 +1,57 @@ +package io.sentry.gradle + +import org.gradle.api.Action +import org.gradle.api.Task +import org.gradle.api.tasks.compile.JavaCompile +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes + +/** + * Removes the MethodParameters attribute from the classes a JavaCompile task produced. + * + * Since JDK 21 (JDK-8292275) javac writes MethodParameters for every method with synthetic or + * mandated parameters, e.g. bridge methods, even without `-parameters`. Those entries carry flags + * but no name, and the D8 of AGP 7.x (used e.g. by React Native 0.71 apps) crashes on them with + * `NullPointerException: Cannot invoke "String.length()"` while dexing. Without `-parameters` this + * attribute holds nothing the SDK relies on, so dropping it restores the class files javac 17 + * produced. + */ +internal class StripMethodParameters : Action { + override fun execute(task: Task) { + val classesDir = (task as JavaCompile).destinationDirectory.get().asFile + classesDir + .walkTopDown() + .filter { it.isFile && it.extension == "class" } + .forEach { file -> + val reader = ClassReader(file.readBytes()) + val writer = ClassWriter(reader, 0) + val stripper = MethodParametersStripper(writer) + reader.accept(stripper, 0) + if (stripper.stripped) { + file.writeBytes(writer.toByteArray()) + } + } + } + + private class MethodParametersStripper(next: ClassVisitor) : ClassVisitor(Opcodes.ASM9, next) { + var stripped = false + + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array?, + ): MethodVisitor? { + val next = super.visitMethod(access, name, descriptor, signature, exceptions) + return object : MethodVisitor(Opcodes.ASM9, next) { + override fun visitParameter(name: String?, access: Int) { + stripped = true + } + } + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e5c2f9e217..30b60fde68 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -157,6 +157,8 @@ lottie-compose = { module = "com.airbnb.android:lottie-compose", version = "6.7. logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" } +# Matches the ASM that AGP brings, used by build-logic to post-process published class files +asm = { module = "org.ow2.asm:asm", version = "9.9" } nopen-annotations = { module = "com.jakewharton.nopen:nopen-annotations", version.ref = "nopen" } nopen-checker = { module = "com.jakewharton.nopen:nopen-checker", version.ref = "nopen" } nullaway = { module = "com.uber.nullaway:nullaway", version = "0.14.2" } From 79da542c1087effc059dfb25d38272219ce0cce0 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 22 Oct 2026 13:41:55 +0200 Subject: [PATCH 2/4] changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfffb41a11..ebe808c535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- Fix a D8 crash (`NullPointerException: Cannot invoke "String.length()"`) when dexing the SDK with AGP 8.0 and older, a regression from 8.61.1 ([#6356](https://github.com/getsentry/sentry-java/pull/6356)) + ## 8.61.1 ### Fixes From e5e54aee40a83fdfb810572caaf2cba6b2868c07 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 22 Oct 2026 16:26:51 +0200 Subject: [PATCH 3/4] fix(build): Strip MethodParameters from every module, not only published main code --- .../kotlin/io/sentry/gradle/JdkTargetConvention.kt | 13 ++++++------- .../io/sentry/gradle/StripMethodParameters.kt | 12 +++++++----- gradle/libs.versions.toml | 1 - 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/build-logic/src/main/kotlin/io/sentry/gradle/JdkTargetConvention.kt b/build-logic/src/main/kotlin/io/sentry/gradle/JdkTargetConvention.kt index dea3a08b50..67d37ce6f9 100644 --- a/build-logic/src/main/kotlin/io/sentry/gradle/JdkTargetConvention.kt +++ b/build-logic/src/main/kotlin/io/sentry/gradle/JdkTargetConvention.kt @@ -27,7 +27,7 @@ private val PUBLISHED_JAVA_VERSION = JavaVersion.VERSION_1_8 * - sample apps target the build JDK (21) * - unit test code will target the build JDK (21) (required for Mockito 5) * - published JVM modules main code pins the Java 1.8 release, for Java and for Kotlin - * - published modules main Java code drops the MethodParameters attribute javac 21 adds + * - every module's Java code drops the MethodParameters attribute javac 21 adds * - published Android modules main code targets Java 1.8 * - minSdk is set for Android libraries */ @@ -39,13 +39,12 @@ class JdkTargetConvention : Plugin { } } + // Java classes must stay dexable by older D8 versions, see StripMethodParameters + project.tasks.withType(JavaCompile::class.java).configureEach { + doLast(StripMethodParameters()) + } + project.plugins.withId("com.vanniktech.maven.publish") { - // Published Java classes must stay dexable by older D8 versions, see StripMethodParameters - project.tasks.withType(JavaCompile::class.java).configureEach { - if (!name.contains("Test")) { - doLast(StripMethodParameters()) - } - } // Only works for non-android modules // See https://issuetracker.google.com/issues/278800528 project.plugins.withId("java-library") { diff --git a/build-logic/src/main/kotlin/io/sentry/gradle/StripMethodParameters.kt b/build-logic/src/main/kotlin/io/sentry/gradle/StripMethodParameters.kt index 2701baf1a3..3a13fc3f54 100644 --- a/build-logic/src/main/kotlin/io/sentry/gradle/StripMethodParameters.kt +++ b/build-logic/src/main/kotlin/io/sentry/gradle/StripMethodParameters.kt @@ -10,14 +10,16 @@ import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes /** - * Removes the MethodParameters attribute from the classes a JavaCompile task produced. + * Removes the + * [MethodParameters](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24) + * attribute from the classes a JavaCompile task produced. * * Since JDK 21 (JDK-8292275) javac writes MethodParameters for every method with synthetic or * mandated parameters, e.g. bridge methods, even without `-parameters`. Those entries carry flags - * but no name, and the D8 of AGP 7.x (used e.g. by React Native 0.71 apps) crashes on them with - * `NullPointerException: Cannot invoke "String.length()"` while dexing. Without `-parameters` this - * attribute holds nothing the SDK relies on, so dropping it restores the class files javac 17 - * produced. + * but no name, and the D8 of AGP 8.0 and older fails the build while dexing. See the + * [R8 fix](https://r8.googlesource.com/r8/+/9da58a26e22f195d8187492fac350a90e3315e92) for more + * details. An alternative would be adding the `-parameters` compile flag, but this could lead to + * other binary incompatibility issues along the road. */ internal class StripMethodParameters : Action { override fun execute(task: Task) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 30b60fde68..0e8b0c790d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -157,7 +157,6 @@ lottie-compose = { module = "com.airbnb.android:lottie-compose", version = "6.7. logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" } -# Matches the ASM that AGP brings, used by build-logic to post-process published class files asm = { module = "org.ow2.asm:asm", version = "9.9" } nopen-annotations = { module = "com.jakewharton.nopen:nopen-annotations", version.ref = "nopen" } nopen-checker = { module = "com.jakewharton.nopen:nopen-checker", version.ref = "nopen" } From cc937fd7ad335995065ddbfe2cf284192b1c1d43 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 22 Oct 2026 16:54:28 +0200 Subject: [PATCH 4/4] fix(build): Strip MethodParameters only in published modules again --- .../kotlin/io/sentry/gradle/JdkTargetConvention.kt | 11 +++++------ .../kotlin/io/sentry/gradle/StripMethodParameters.kt | 4 ++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/build-logic/src/main/kotlin/io/sentry/gradle/JdkTargetConvention.kt b/build-logic/src/main/kotlin/io/sentry/gradle/JdkTargetConvention.kt index 67d37ce6f9..38d186e66d 100644 --- a/build-logic/src/main/kotlin/io/sentry/gradle/JdkTargetConvention.kt +++ b/build-logic/src/main/kotlin/io/sentry/gradle/JdkTargetConvention.kt @@ -27,7 +27,7 @@ private val PUBLISHED_JAVA_VERSION = JavaVersion.VERSION_1_8 * - sample apps target the build JDK (21) * - unit test code will target the build JDK (21) (required for Mockito 5) * - published JVM modules main code pins the Java 1.8 release, for Java and for Kotlin - * - every module's Java code drops the MethodParameters attribute javac 21 adds + * - published modules Java code drops the MethodParameters attribute javac 21 adds * - published Android modules main code targets Java 1.8 * - minSdk is set for Android libraries */ @@ -39,12 +39,11 @@ class JdkTargetConvention : Plugin { } } - // Java classes must stay dexable by older D8 versions, see StripMethodParameters - project.tasks.withType(JavaCompile::class.java).configureEach { - doLast(StripMethodParameters()) - } - project.plugins.withId("com.vanniktech.maven.publish") { + // Published Java classes must stay dexable by older D8 versions, see StripMethodParameters + project.tasks.withType(JavaCompile::class.java).configureEach { + doLast(StripMethodParameters()) + } // Only works for non-android modules // See https://issuetracker.google.com/issues/278800528 project.plugins.withId("java-library") { diff --git a/build-logic/src/main/kotlin/io/sentry/gradle/StripMethodParameters.kt b/build-logic/src/main/kotlin/io/sentry/gradle/StripMethodParameters.kt index 3a13fc3f54..21b8fb4c89 100644 --- a/build-logic/src/main/kotlin/io/sentry/gradle/StripMethodParameters.kt +++ b/build-logic/src/main/kotlin/io/sentry/gradle/StripMethodParameters.kt @@ -20,6 +20,10 @@ import org.objectweb.asm.Opcodes * [R8 fix](https://r8.googlesource.com/r8/+/9da58a26e22f195d8187492fac350a90e3315e92) for more * details. An alternative would be adding the `-parameters` compile flag, but this could lead to * other binary incompatibility issues along the road. + * + * The action removes every MethodParameters entry, also the named ones that `-parameters` writes. + * Keep it away from code that compiles with `-parameters`, e.g. the Spring Boot samples, where + * Spring resolves bean and handler parameters by name at runtime. */ internal class StripMethodParameters : Action { override fun execute(task: Task) {