diff --git a/ndk/lib/build.gradle.kts b/ndk/lib/build.gradle.kts index e012680e0..4e1aa5655 100644 --- a/ndk/lib/build.gradle.kts +++ b/ndk/lib/build.gradle.kts @@ -1,4 +1,9 @@ import com.android.build.gradle.internal.lint.AndroidLintAnalysisTask +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 plugins { id("com.android.library") @@ -187,3 +192,85 @@ afterEvaluate { tasks.withType().configureEach { doNotTrackState("Dependency metadata can change without code changes") } + +// keep the classes dexable by D8/R8 of AGP 8.0 and older when compiled with JDK 21+ +tasks.withType().configureEach { + doLast(StripMethodParameters()) +} + +/** + * Removes the MethodParameters attribute from methods that have a parameter without a name. + * + * Since JDK 21 (JDK-8292275) javac writes MethodParameters for synthetic or mandated parameters, + * e.g. of enums, even without `-parameters`. Those entries have no name, and the D8/R8 of AGP 8.0 + * and older crash on them. Same as getsentry/sentry-java#6356. + */ +class StripMethodParameters : Action { + override fun execute(task: Task) { + (task as JavaCompile) + .destinationDirectory + .get() + .asFile + .walkTopDown() + .filter { it.isFile && it.extension == "class" } + .forEach { file -> + val reader = ClassReader(file.readBytes()) + val methods = reader.methodsWithUnnamedParameters() + if (methods.isNotEmpty()) { + val writer = ClassWriter(reader, 0) + reader.accept( + object : ClassVisitor(Opcodes.ASM9, writer) { + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array?, + ): MethodVisitor? { + val next = super.visitMethod(access, name, descriptor, signature, exceptions) + if (name + descriptor !in methods) { + return next + } + return object : MethodVisitor(Opcodes.ASM9, next) { + override fun visitParameter( + name: String?, + access: Int, + ) = Unit + } + } + }, + 0, + ) + file.writeBytes(writer.toByteArray()) + } + } + } + + private fun ClassReader.methodsWithUnnamedParameters(): Set { + val methods = mutableSetOf() + accept( + object : ClassVisitor(Opcodes.ASM9) { + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array?, + ): MethodVisitor = + object : MethodVisitor(Opcodes.ASM9) { + override fun visitParameter( + parameterName: String?, + access: Int, + ) { + if (parameterName == null) { + methods.add(name + descriptor) + } + } + } + }, + // no SKIP_DEBUG, ASM counts MethodParameters as debug info + ClassReader.SKIP_CODE, + ) + return methods + } +}