From ca6b94d943b7067aae87a4e282cfb323811c0462 Mon Sep 17 00:00:00 2001 From: Lucaskyy Date: Wed, 23 Mar 2022 22:32:50 +0100 Subject: [PATCH] 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. --- .../kotlin/app/revanced/patcher/util/Io.kt | 5 +- .../patcher/writer/SafeClassWriter.kt | 140 ++++++++++++++++++ 2 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/app/revanced/patcher/writer/SafeClassWriter.kt diff --git a/src/main/kotlin/app/revanced/patcher/util/Io.kt b/src/main/kotlin/app/revanced/patcher/util/Io.kt index 3ced3a4..b51db35 100644 --- a/src/main/kotlin/app/revanced/patcher/util/Io.kt +++ b/src/main/kotlin/app/revanced/patcher/util/Io.kt @@ -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()) diff --git a/src/main/kotlin/app/revanced/patcher/writer/SafeClassWriter.kt b/src/main/kotlin/app/revanced/patcher/writer/SafeClassWriter.kt new file mode 100644 index 0000000..c200a2e --- /dev/null +++ b/src/main/kotlin/app/revanced/patcher/writer/SafeClassWriter.kt @@ -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) + } +} \ No newline at end of file