forked from openrs2/openrs2
This is a prerequisite for the static scrambling transformer. As it moves methods/fields around, it requires their visibilities to be updated.master
parent
fda857963b
commit
caf7efdd6c
@ -0,0 +1,182 @@ |
|||||||
|
package dev.openrs2.deob.transform |
||||||
|
|
||||||
|
import com.github.michaelbull.logging.InlineLogger |
||||||
|
import com.google.common.collect.HashMultimap |
||||||
|
import com.google.common.collect.Multimap |
||||||
|
import dev.openrs2.asm.ClassForNameUtils |
||||||
|
import dev.openrs2.asm.MemberDesc |
||||||
|
import dev.openrs2.asm.MemberRef |
||||||
|
import dev.openrs2.asm.classpath.ClassPath |
||||||
|
import dev.openrs2.asm.classpath.Library |
||||||
|
import dev.openrs2.asm.transform.Transformer |
||||||
|
import dev.openrs2.common.collect.DisjointSet |
||||||
|
import org.objectweb.asm.Opcodes |
||||||
|
import org.objectweb.asm.Type |
||||||
|
import org.objectweb.asm.tree.ClassNode |
||||||
|
import org.objectweb.asm.tree.FieldInsnNode |
||||||
|
import org.objectweb.asm.tree.MethodInsnNode |
||||||
|
import org.objectweb.asm.tree.MethodNode |
||||||
|
|
||||||
|
class VisibilityTransformer : Transformer() { |
||||||
|
private lateinit var inheritedFieldSets: DisjointSet<MemberRef> |
||||||
|
private lateinit var inheritedMethodSets: DisjointSet<MemberRef> |
||||||
|
private val fieldReferences = HashMultimap.create<DisjointSet.Partition<MemberRef>, String>() |
||||||
|
private val methodReferences = HashMultimap.create<DisjointSet.Partition<MemberRef>, String>() |
||||||
|
private val publicCtorClasses = mutableSetOf<String>() |
||||||
|
|
||||||
|
override fun preTransform(classPath: ClassPath) { |
||||||
|
inheritedFieldSets = classPath.createInheritedFieldSets() |
||||||
|
inheritedMethodSets = classPath.createInheritedMethodSets() |
||||||
|
fieldReferences.clear() |
||||||
|
methodReferences.clear() |
||||||
|
publicCtorClasses.clear() |
||||||
|
publicCtorClasses.addAll(DEFAULT_PUBLIC_CTOR_CLASSES) |
||||||
|
} |
||||||
|
|
||||||
|
override fun transformCode(classPath: ClassPath, library: Library, clazz: ClassNode, method: MethodNode): Boolean { |
||||||
|
for (name in ClassForNameUtils.findClassNames(method)) { |
||||||
|
val loadedClass = classPath[name] |
||||||
|
if (loadedClass != null && !loadedClass.dependency) { |
||||||
|
publicCtorClasses.add(name) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (insn in method.instructions) { |
||||||
|
when (insn) { |
||||||
|
is FieldInsnNode -> addReference(fieldReferences, inheritedFieldSets, MemberRef(insn), clazz.name) |
||||||
|
is MethodInsnNode -> addReference(methodReferences, inheritedMethodSets, MemberRef(insn), clazz.name) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
private fun getVisibility( |
||||||
|
classPath: ClassPath, |
||||||
|
references: Multimap<DisjointSet.Partition<MemberRef>, String>, |
||||||
|
disjointSet: DisjointSet<MemberRef>, |
||||||
|
member: MemberRef, |
||||||
|
access: Int |
||||||
|
): Int { |
||||||
|
val method = Type.getType(member.desc).sort == Type.METHOD |
||||||
|
if (method) { |
||||||
|
if (member.name == "<clinit>") { |
||||||
|
// the visibility flags don't really matter - we use package-private to match javac |
||||||
|
return 0 |
||||||
|
} else if (member.owner in publicCtorClasses && member.name == "<init>") { |
||||||
|
// constructors invoked with reflection (including applets) must be public |
||||||
|
return Opcodes.ACC_PUBLIC |
||||||
|
} else if (member.name in PUBLIC_METHODS) { |
||||||
|
// methods invoked with reflection must also be public |
||||||
|
return Opcodes.ACC_PUBLIC |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val partition = disjointSet[member]!! |
||||||
|
|
||||||
|
val overridable = method && member.name != "<init>" |
||||||
|
val hasOverride = overridable && partition.count { classPath[it.owner]!!.methods.contains(MemberDesc(it)) } > 1 |
||||||
|
val abstract = method && access and Opcodes.ACC_ABSTRACT != 0 |
||||||
|
val partitionReferences = references[partition] |
||||||
|
val partitionOwners = partition.map(MemberRef::owner).toSet() |
||||||
|
|
||||||
|
// pick the weakest access level based on references in our own code |
||||||
|
val visibility = when { |
||||||
|
partitionReferences.all { it == member.owner } && !hasOverride && !abstract -> Opcodes.ACC_PRIVATE |
||||||
|
partitionReferences.all { partitionOwners.contains(it) } -> Opcodes.ACC_PROTECTED |
||||||
|
else -> Opcodes.ACC_PUBLIC |
||||||
|
} |
||||||
|
|
||||||
|
return if (overridable) { |
||||||
|
// reduce it to the weakest level required to override a dependency's method |
||||||
|
partition.filter { classPath[it.owner]!!.dependency } |
||||||
|
.mapNotNull { classPath[it.owner]!!.getAccess(MemberDesc(it)) } |
||||||
|
.fold(visibility, ::weakestVisibility) |
||||||
|
} else { |
||||||
|
visibility |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun postTransform(classPath: ClassPath) { |
||||||
|
logger.info { "Identified constructors invoked with reflection $publicCtorClasses" } |
||||||
|
|
||||||
|
var classesChanged = 0 |
||||||
|
var fieldsChanged = 0 |
||||||
|
var methodsChanged = 0 |
||||||
|
|
||||||
|
for (library in classPath.libraries) { |
||||||
|
for (clazz in library) { |
||||||
|
val classAccess = clazz.access |
||||||
|
clazz.access = (classAccess and VISIBILITY_FLAGS.inv()) or Opcodes.ACC_PUBLIC |
||||||
|
if (clazz.access != classAccess) { |
||||||
|
classesChanged++ |
||||||
|
} |
||||||
|
|
||||||
|
for (field in clazz.fields) { |
||||||
|
val access = field.access |
||||||
|
|
||||||
|
val visibility = getVisibility( |
||||||
|
classPath, |
||||||
|
fieldReferences, |
||||||
|
inheritedFieldSets, |
||||||
|
MemberRef(clazz, field), |
||||||
|
access |
||||||
|
) |
||||||
|
field.access = (access and VISIBILITY_FLAGS.inv()) or visibility |
||||||
|
|
||||||
|
if (field.access != access) { |
||||||
|
fieldsChanged++ |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (method in clazz.methods) { |
||||||
|
val access = method.access |
||||||
|
|
||||||
|
val visibility = getVisibility( |
||||||
|
classPath, |
||||||
|
methodReferences, |
||||||
|
inheritedMethodSets, |
||||||
|
MemberRef(clazz, method), |
||||||
|
access |
||||||
|
) |
||||||
|
method.access = (access and VISIBILITY_FLAGS.inv()) or visibility |
||||||
|
|
||||||
|
if (method.access != access) { |
||||||
|
methodsChanged++ |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
logger.info { |
||||||
|
"Updated visibility of $classesChanged classes, $fieldsChanged fields and $methodsChanged methods" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
private val logger = InlineLogger() |
||||||
|
private const val VISIBILITY_FLAGS = Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE |
||||||
|
private val DEFAULT_PUBLIC_CTOR_CLASSES = setOf("client", "loader") |
||||||
|
private val PUBLIC_METHODS = setOf("main", "providesignlink") |
||||||
|
|
||||||
|
private fun addReference( |
||||||
|
references: Multimap<DisjointSet.Partition<MemberRef>, String>, |
||||||
|
disjointSet: DisjointSet<MemberRef>, |
||||||
|
member: MemberRef, |
||||||
|
className: String |
||||||
|
) { |
||||||
|
val partition = disjointSet[member] ?: return |
||||||
|
references.put(partition, className) |
||||||
|
} |
||||||
|
|
||||||
|
private fun weakestVisibility(a: Int, b: Int): Int { |
||||||
|
return when { |
||||||
|
a and Opcodes.ACC_PUBLIC != 0 || b and Opcodes.ACC_PUBLIC != 0 -> Opcodes.ACC_PUBLIC |
||||||
|
// map package-private to public |
||||||
|
a and VISIBILITY_FLAGS == 0 || b and VISIBILITY_FLAGS == 0 -> Opcodes.ACC_PUBLIC |
||||||
|
a and Opcodes.ACC_PROTECTED != 0 || b and Opcodes.ACC_PROTECTED != 0 -> Opcodes.ACC_PROTECTED |
||||||
|
else -> Opcodes.ACC_PRIVATE |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue