package dev.openrs2.deob.transform import com.github.michaelbull.logging.InlineLogger import dev.openrs2.asm.classpath.ClassPath import dev.openrs2.asm.classpath.Library import dev.openrs2.asm.transform.Transformer import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode import javax.inject.Singleton /** * A [Transformer] that replaces `INVOKESPECIAL` instructions in static methods * with `INVOKEVIRTUAL` equivalents. * * The client contains some methods in `final` classes that use `INVOKESPECIAL` * to invoke non-`` methods on objects of exactly the same type as the * containing class. Furthermore, these `INVOKESPECIAL` instructions are * sometimes present in `static` methods. * * As the containing class is `final` and as the method reference is not a * superclass of the containing class, these calls actually end up being * treated in the exact same way as an `INVOKEVIRTRUAL` call. * * While these calls are unusual (and probably never generated by a standard * Java compiler, as there is no way to express them in Java source code), they * are permitted by the * [JVM specification](https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-6.html#jvms-6.5.invokespecial). * * It is likely that this unusual use of `INVOKESPECIAL` is an optimization * (`INVOKESPECIAL` was originally used as a more generic `INVOKENONVIRTUAL` * instruction, and non-virtual function calls are cheaper than virtual * function calls) or an obfuscation technique. * * The [StaticScramblingTransformer] moves static methods containing these * `INVOKESPECIAL` instructions to new classes. This change is still permitted * by the JVM specification, which does not place any restrictions on the class * referenced by the `INVOKESPECIAL` instruction. * * However, the * [verifier](https://github.com/openjdk/jdk11u/blob/3789983e89c9de252ef546a1b98a732a7d066650/src/java.base/share/native/libverify/check_code.c#L1342) * in modern JVMs is stricter and considers non-`` `INVOKESPECIAL` * instructions referencing classes that are not the containing class or its * direct superclass illegal. * * This transformer replaces `INVOKESPECIAL` with equivalent `INVOKEVIRTUAL` * instructions where possible, allowing the [StaticScramblingTransformer] to * produce verifiable output. */ @Singleton class InvokeSpecialTransformer : Transformer() { private var invokeSpecialsReplaced = 0 override fun preTransform(classPath: ClassPath) { invokeSpecialsReplaced = 0 } override fun transformClass(classPath: ClassPath, library: Library, clazz: ClassNode): Boolean { require(clazz.access and (Opcodes.ACC_SUPER or Opcodes.ACC_INTERFACE) != 0) return false } override fun transformCode(classPath: ClassPath, library: Library, clazz: ClassNode, method: MethodNode): Boolean { if ((clazz.access and Opcodes.ACC_FINAL) == 0) { return false } for (insn in method.instructions) { if (insn !is MethodInsnNode || insn.opcode != Opcodes.INVOKESPECIAL) { continue } else if (insn.name == "") { continue } else if (insn.owner != clazz.name) { continue } insn.opcode = Opcodes.INVOKEVIRTUAL invokeSpecialsReplaced++ } return false } override fun postTransform(classPath: ClassPath) { logger.info { "Replaced $invokeSpecialsReplaced INVOKESPECIALs with INVOKEVIRTUAL" } } companion object { private val logger = InlineLogger() } }