mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2024-11-10 09:08:04 +01:00
feat: add SafeClassWriter
the standard ClassWriter implementation uses the ClassLoader to find a common superclass. this won't work for us since we are not loading the JAR into the classpath. using this SafeClassWriter should fix that issue.
This commit is contained in:
parent
ab6453ca8a
commit
ca6b94d943
2 changed files with 144 additions and 1 deletions
|
@ -1,5 +1,6 @@
|
|||
package app.revanced.patcher.util
|
||||
|
||||
import app.revanced.patcher.writer.SafeClassWriter
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassWriter
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
|
@ -78,7 +79,9 @@ internal class Io(
|
|||
jos.putNextEntry(JarEntry(patchedClass.name + ".class"))
|
||||
|
||||
// parse the patched class to a byte array and write it to the output stream
|
||||
val cw = ClassWriter(ClassWriter.COMPUTE_MAXS or ClassWriter.COMPUTE_FRAMES)
|
||||
val cw: ClassWriter = SafeClassWriter(
|
||||
ClassWriter.COMPUTE_FRAMES or ClassWriter.COMPUTE_MAXS
|
||||
)
|
||||
patchedClass.accept(cw)
|
||||
jos.write(cw.toByteArray())
|
||||
|
||||
|
|
140
src/main/kotlin/app/revanced/patcher/writer/SafeClassWriter.kt
Normal file
140
src/main/kotlin/app/revanced/patcher/writer/SafeClassWriter.kt
Normal file
|
@ -0,0 +1,140 @@
|
|||
package app.revanced.patcher.writer
|
||||
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassWriter
|
||||
import org.objectweb.asm.Opcodes
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* A ClassWriter that computes the common super class of two classes without
|
||||
* actually loading them with a ClassLoader.
|
||||
*
|
||||
* @author Eric Bruneton
|
||||
*/
|
||||
// TODO(Sculas): should we add the ClassReader parameter back?
|
||||
class SafeClassWriter(flags: Int) : ClassWriter(flags) {
|
||||
override fun getCommonSuperClass(type1: String, type2: String): String {
|
||||
try {
|
||||
val info1 = typeInfo(type1)
|
||||
val info2 = typeInfo(type2)
|
||||
if (info1.access and Opcodes.ACC_INTERFACE != 0) {
|
||||
return if (typeImplements(type2, info2, type1)) {
|
||||
type1
|
||||
} else {
|
||||
"java/lang/Object"
|
||||
}
|
||||
}
|
||||
if (info2.access and Opcodes.ACC_INTERFACE != 0) {
|
||||
return if (typeImplements(type1, info1, type2)) {
|
||||
type2
|
||||
} else {
|
||||
"java/lang/Object"
|
||||
}
|
||||
}
|
||||
val b1 = typeAncestors(type1, info1)
|
||||
val b2 = typeAncestors(type2, info2)
|
||||
var result = "java/lang/Object"
|
||||
var end1 = b1.length
|
||||
var end2 = b2.length
|
||||
while (true) {
|
||||
val start1 = b1.lastIndexOf(";", end1 - 1)
|
||||
val start2 = b2.lastIndexOf(";", end2 - 1)
|
||||
if (start1 != -1 && start2 != -1 && end1 - start1 == end2 - start2) {
|
||||
val p1 = b1.substring(start1 + 1, end1)
|
||||
val p2 = b2.substring(start2 + 1, end2)
|
||||
if (p1 == p2) {
|
||||
result = p1
|
||||
end1 = start1
|
||||
end2 = start2
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw RuntimeException(e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the internal names of the ancestor classes of the given type.
|
||||
*
|
||||
* @param _type
|
||||
* the internal name of a class or interface.
|
||||
* @param _info
|
||||
* the ClassReader corresponding to 'type'.
|
||||
* @return a StringBuilder containing the ancestor classes of 'type',
|
||||
* separated by ';'. The returned string has the following format:
|
||||
* ";type1;type2 ... ;typeN", where type1 is 'type', and typeN is a
|
||||
* direct subclass of Object. If 'type' is Object, the returned
|
||||
* string is empty.
|
||||
* @throws IOException
|
||||
* if the bytecode of 'type' or of some of its ancestor class
|
||||
* cannot be loaded.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun typeAncestors(_type: String, _info: ClassReader): StringBuilder {
|
||||
var type = _type
|
||||
var info = _info
|
||||
val b = StringBuilder()
|
||||
while ("java/lang/Object" != type) {
|
||||
b.append(';').append(type)
|
||||
type = info.superName
|
||||
info = typeInfo(type)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given type implements the given interface.
|
||||
*
|
||||
* @param _type
|
||||
* the internal name of a class or interface.
|
||||
* @param _info
|
||||
* the ClassReader corresponding to 'type'.
|
||||
* @param itf
|
||||
* the internal name of a interface.
|
||||
* @return true if 'type' implements directly or indirectly 'itf'
|
||||
* @throws IOException
|
||||
* if the bytecode of 'type' or of some of its ancestor class
|
||||
* cannot be loaded.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun typeImplements(_type: String, _info: ClassReader, itf: String): Boolean {
|
||||
var type = _type
|
||||
var info = _info
|
||||
while ("java/lang/Object" != type) {
|
||||
info.interfaces.forEach {
|
||||
if (it == itf) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
info.interfaces.forEach {
|
||||
if (typeImplements(it, typeInfo(it), itf)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
type = info.superName
|
||||
info = typeInfo(type)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ClassReader corresponding to the given class or interface.
|
||||
*
|
||||
* @param type
|
||||
* the internal name of a class or interface.
|
||||
* @return the ClassReader corresponding to 'type'.
|
||||
* @throws IOException
|
||||
* if the bytecode of 'type' cannot be loaded.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun typeInfo(type: String): ClassReader {
|
||||
val input = ClassLoader.getSystemClassLoader().getResourceAsStream("$type.class")
|
||||
?: throw IOException("Cannot create ClassReader for type $type")
|
||||
return input.use(::ClassReader)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue