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/MonitorTransformer.kt

143 lines
5.1 KiB

package dev.openrs2.deob.transform
import com.github.michaelbull.logging.InlineLogger
import dev.openrs2.asm.InsnMatcher
import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library
import dev.openrs2.asm.nextReal
import dev.openrs2.asm.previousReal
import dev.openrs2.asm.transform.Transformer
import org.objectweb.asm.Opcodes
import org.objectweb.asm.commons.JSRInlinerAdapter
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.InsnNode
import org.objectweb.asm.tree.JumpInsnNode
import org.objectweb.asm.tree.LabelNode
import org.objectweb.asm.tree.MethodNode
import javax.inject.Singleton
/**
* A [Transformer] that rewrites `synchronized` blocks produced by older
* versions of the Java compiler (1.3 and older) into a more modern format.
* This is required for compatibility with Fernflower, which does not
* understand the older format.
*
* This transformer depends on [JSRInlinerAdapter].
*
* It makes three changes:
*
* - Inlines `MONITOREXIT` subroutines. [JSRInlinerAdapter] only replaces
* `JSR`/`RET` calls with `GOTO`. Fernflower needs the actual body of the
* subroutine to be inlined.
*
* - Extends exception handler ranges to cover the `MONITOREXIT` instruction.
*
* - Replaces `ASTORE ALOAD MONITORENTER` sequences with `DUP MONITORENTER`,
* which prevents Fernflower from emitting a redundant variable declaration.
*
* There is one final difference that this transformer does not deal with:
* modern versions of the Java compiler add a second exception handler range to
* each synchronized block covering the `MONITOREXIT` sequence, with the
* handler pointing to the same `MONITOREXIT` sequence. Adding this isn't
* necessary for Fernflower compatibility.
*/
@Singleton
class MonitorTransformer : Transformer() {
private var subroutinesInlined = 0
private var tryRangesExtended = 0
private var loadsReplaced = 0
override fun preTransform(classPath: ClassPath) {
subroutinesInlined = 0
tryRangesExtended = 0
loadsReplaced = 0
}
override fun transformCode(classPath: ClassPath, library: Library, clazz: ClassNode, method: MethodNode): Boolean {
inlineSubroutines(method)
extendTryRanges(method)
replaceLoadWithDup(method)
return false
}
private fun inlineSubroutines(method: MethodNode) {
val subroutines = mutableMapOf<AbstractInsnNode, List<AbstractInsnNode>>()
for (match in SUBROUTINE_MATCHER.match(method.instructions)) {
subroutines[match[0]] = match
}
for (match in JSR_MATCHER.match(method.instructions)) {
val jsr = match[1] as JumpInsnNode
val subroutine = subroutines[jsr.label.nextReal] ?: continue
val ret = subroutine[3] as JumpInsnNode
if (ret.label.nextReal != jsr.nextReal) {
continue
}
val clonedLabels = emptyMap<LabelNode, LabelNode>()
method.instructions.set(match[0], subroutine[1].clone(clonedLabels))
method.instructions.set(match[1], subroutine[2].clone(clonedLabels))
subroutinesInlined++
}
}
private fun extendTryRanges(method: MethodNode) {
for (tryCatch in method.tryCatchBlocks) {
if (tryCatch.type != null) {
continue
}
val monitorenter = tryCatch.start.previousReal ?: continue
if (monitorenter.opcode != Opcodes.MONITORENTER) {
continue
}
// extend the try to cover the ALOAD and MONTITOREXIT instructions
val aload = tryCatch.end.nextReal ?: continue
if (aload.opcode != Opcodes.ALOAD) {
continue
}
val monitorexit = aload.nextReal ?: continue
if (monitorexit.opcode != Opcodes.MONITOREXIT) {
continue
}
val end = monitorexit.nextReal ?: continue
val label = LabelNode()
method.instructions.insertBefore(end, label)
tryCatch.end = label
tryRangesExtended++
}
}
private fun replaceLoadWithDup(method: MethodNode) {
for (match in LOAD_MATCHER.match(method.instructions)) {
method.instructions.insertBefore(match[0], InsnNode(Opcodes.DUP))
method.instructions.remove(match[1])
loadsReplaced++
}
}
override fun postTransform(classPath: ClassPath) {
logger.info { "Inlined $subroutinesInlined MONITOREXIT subroutines" }
logger.info { "Extended $tryRangesExtended try ranges to cover MONITOREXIT instructions" }
logger.info { "Replaced $loadsReplaced ASTORE ALOAD sequences with DUP ASTORE" }
}
private companion object {
private val logger = InlineLogger()
// these regexes rely on JSRInlinerAdapter running first
private val JSR_MATCHER = InsnMatcher.compile("ACONST_NULL GOTO")
private val SUBROUTINE_MATCHER = InsnMatcher.compile("ASTORE ALOAD MONITOREXIT GOTO")
private val LOAD_MATCHER = InsnMatcher.compile("ASTORE ALOAD MONITORENTER")
}
}