mirror of
https://github.com/revanced/revanced-patcher.git
synced 2025-05-01 13:44:25 +02: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
e6e468fbb5
commit
6626014ef3
@ -1,5 +1,6 @@
|
|||||||
package app.revanced.patcher.util
|
package app.revanced.patcher.util
|
||||||
|
|
||||||
|
import app.revanced.patcher.writer.SafeClassWriter
|
||||||
import org.objectweb.asm.ClassReader
|
import org.objectweb.asm.ClassReader
|
||||||
import org.objectweb.asm.ClassWriter
|
import org.objectweb.asm.ClassWriter
|
||||||
import org.objectweb.asm.tree.ClassNode
|
import org.objectweb.asm.tree.ClassNode
|
||||||
@ -78,7 +79,9 @@ internal class Io(
|
|||||||
jos.putNextEntry(JarEntry(patchedClass.name + ".class"))
|
jos.putNextEntry(JarEntry(patchedClass.name + ".class"))
|
||||||
|
|
||||||
// parse the patched class to a byte array and write it to the output stream
|
// 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)
|
patchedClass.accept(cw)
|
||||||
jos.write(cw.toByteArray())
|
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…
x
Reference in New Issue
Block a user