Open-source multiplayer game server compatible with the RuneScape client https://www.openrs2.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
openrs2/deob/src/main/java/dev/openrs2/deob/transform/InvokeSpecialTransformer.kt

92 lines
3.6 KiB

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-`<init>` 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-`<init>` `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 == "<init>") {
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" }
}
private companion object {
private val logger = InlineLogger()
}
}