|
| 1 | +package io.sentry.gradle |
| 2 | + |
| 3 | +import org.gradle.api.Action |
| 4 | +import org.gradle.api.Task |
| 5 | +import org.gradle.api.tasks.compile.JavaCompile |
| 6 | +import org.objectweb.asm.ClassReader |
| 7 | +import org.objectweb.asm.ClassVisitor |
| 8 | +import org.objectweb.asm.ClassWriter |
| 9 | +import org.objectweb.asm.MethodVisitor |
| 10 | +import org.objectweb.asm.Opcodes |
| 11 | + |
| 12 | +/** |
| 13 | + * Removes the |
| 14 | + * [MethodParameters](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24) |
| 15 | + * attribute from the methods of a JavaCompile task that have a parameter without a name. |
| 16 | + * |
| 17 | + * Since JDK 21 (JDK-8292275) javac writes MethodParameters for every method with synthetic or |
| 18 | + * mandated parameters, e.g. bridge methods, even without `-parameters`. Those entries carry flags |
| 19 | + * but no name, and the D8 of AGP 8.0 and older fails the build while dexing. See the |
| 20 | + * [R8 fix](https://r8.googlesource.com/r8/+/9da58a26e22f195d8187492fac350a90e3315e92) for more |
| 21 | + * details. An alternative would be adding the `-parameters` compile flag, but this could lead to |
| 22 | + * other binary incompatibility issues along the road. |
| 23 | + * |
| 24 | + * `parameters_count` must match the parameters of the method descriptor, so a single entry cannot |
| 25 | + * be removed. The attribute goes away for the whole method instead, as soon as one of its entries |
| 26 | + * has no name. Methods whose entries all have a name, e.g. the canonical constructor of a record |
| 27 | + * or anything compiled with `-parameters`, keep the attribute and their names. |
| 28 | + */ |
| 29 | +internal class StripMethodParameters : Action<Task> { |
| 30 | + override fun execute(task: Task) { |
| 31 | + val classesDir = (task as JavaCompile).destinationDirectory.get().asFile |
| 32 | + classesDir |
| 33 | + .walkTopDown() |
| 34 | + .filter { it.isFile && it.extension == "class" } |
| 35 | + .forEach { file -> |
| 36 | + val reader = ClassReader(file.readBytes()) |
| 37 | + val methods = reader.methodsWithUnnamedParameters() |
| 38 | + if (methods.isNotEmpty()) { |
| 39 | + val writer = ClassWriter(reader, 0) |
| 40 | + reader.accept(MethodParametersStripper(writer, methods), 0) |
| 41 | + file.writeBytes(writer.toByteArray()) |
| 42 | + } |
| 43 | + } |
| 44 | + } |
| 45 | + |
| 46 | + private class MethodParametersStripper(next: ClassVisitor, private val methods: Set<String>) : |
| 47 | + ClassVisitor(Opcodes.ASM9, next) { |
| 48 | + |
| 49 | + override fun visitMethod( |
| 50 | + access: Int, |
| 51 | + name: String?, |
| 52 | + descriptor: String?, |
| 53 | + signature: String?, |
| 54 | + exceptions: Array<out String>?, |
| 55 | + ): MethodVisitor? { |
| 56 | + val next = super.visitMethod(access, name, descriptor, signature, exceptions) |
| 57 | + // every other method keeps the byte for byte copy ASM does for an untouched method |
| 58 | + if (name + descriptor !in methods) { |
| 59 | + return next |
| 60 | + } |
| 61 | + return object : MethodVisitor(Opcodes.ASM9, next) { |
| 62 | + override fun visitParameter(name: String?, access: Int) = Unit |
| 63 | + } |
| 64 | + } |
| 65 | + } |
| 66 | +} |
0 commit comments