diff --git a/src/main/kotlin/dex/ShortyDescriptor.kt b/src/main/kotlin/dex/ShortyDescriptor.kt index ae65ce9..f5ab00f 100644 --- a/src/main/kotlin/dex/ShortyDescriptor.kt +++ b/src/main/kotlin/dex/ShortyDescriptor.kt @@ -8,7 +8,7 @@ data class ShortyDescriptor(val shorty: String) { buildString { append(names[returnType] ?: returnType) append("(") - append(parameterTypes.joinToString("") { names[it] ?: it }) + append(parameterTypes.joinToString(",") { names[it] ?: it }) append(")") } } diff --git a/src/main/kotlin/vm/Environment.kt b/src/main/kotlin/vm/Environment.kt index 7b506ef..d49f52e 100644 --- a/src/main/kotlin/vm/Environment.kt +++ b/src/main/kotlin/vm/Environment.kt @@ -1,6 +1,7 @@ package com.yhs0602.vm import com.yhs0602.dex.* +import com.yhs0602.vm.classloader.DexClassLoader import com.yhs0602.vm.instance.DictionaryBackedInstance import com.yhs0602.vm.instance.MockedInstance import com.yhs0602.vm.instance.unmarshalArgument @@ -59,6 +60,7 @@ class Environment( private val staticFields = mutableMapOf, Array>() private val initializedClasses: MutableSet = mutableSetOf() private val typeIdToCustomClass: MutableMap> = mutableMapOf() + private val classLoader = DexClassLoader(dexFiles, mockedClasses) init { dexFiles.forEach { dexFile -> diff --git a/src/main/kotlin/vm/classloader/DescriptorExtensions.kt b/src/main/kotlin/vm/classloader/DescriptorExtensions.kt index b1c2034..8d979ef 100644 --- a/src/main/kotlin/vm/classloader/DescriptorExtensions.kt +++ b/src/main/kotlin/vm/classloader/DescriptorExtensions.kt @@ -61,7 +61,8 @@ fun Method.methodTableEntry(): MethodTableEntry { parameters = this@methodTableEntry.parameterTypes.map { TypeId(it.descriptorString()) } - } + }, + method = this ) } @@ -85,6 +86,7 @@ fun Constructor<*>.methodTableEntry(): MethodTableEntry { parameters = this@methodTableEntry.parameterTypes.map { TypeId(it.descriptorString()) } - } + }, + null ) } diff --git a/src/main/kotlin/vm/classloader/DexClassLoader.kt b/src/main/kotlin/vm/classloader/DexClassLoader.kt index 0eeaff6..b85b4da 100644 --- a/src/main/kotlin/vm/classloader/DexClassLoader.kt +++ b/src/main/kotlin/vm/classloader/DexClassLoader.kt @@ -62,13 +62,15 @@ class DexClassLoader( } val superClassTypeId = parsedClass.classDef.superClassTypeId val superClassType = if (superClassTypeId != null) { - loadedTypes[superClassTypeId] ?: error("Superclass ${superClassTypeId.descriptor} not loaded") + loadedTypes[superClassTypeId] ?: error( + "Superclass ${superClassTypeId.descriptor} for ${parsedClass.classDef.typeId} not loaded" + ) } else { null } val interfaces = parsedClass.classDef.interfaces val interfaceTypes = interfaces.map { - loadedTypes[it] ?: error("Interface ${it.descriptor} not loaded") + loadedTypes[it] ?: error("Interface ${it.descriptor} for ${parsedClass.classDef.typeId} not loaded") } val loadedType = DexDefinedType( parsedClass, diff --git a/src/main/kotlin/vm/classloader/DexDefinedType.kt b/src/main/kotlin/vm/classloader/DexDefinedType.kt index 4c5ced8..605f238 100644 --- a/src/main/kotlin/vm/classloader/DexDefinedType.kt +++ b/src/main/kotlin/vm/classloader/DexDefinedType.kt @@ -3,7 +3,6 @@ package com.yhs0602.vm.classloader import com.yhs0602.dex.ParsedClass import com.yhs0602.vm.MethodWrapper import net.bytebuddy.ByteBuddy -import net.bytebuddy.dynamic.DynamicType import net.bytebuddy.dynamic.loading.ClassLoadingStrategy import net.bytebuddy.implementation.MethodDelegation @@ -23,6 +22,7 @@ class DexDefinedType( private val _virtualTable: MutableMap = mutableMapOf() override val methods = mutableMapOf() override val constructors = mutableMapOf() + override val staticMethods = mutableMapOf() override val clazz by lazy { makeClazz() } @@ -40,20 +40,26 @@ class DexDefinedType( val methodId = method.methodId val methodTableEntry = MethodTableEntry( methodId.name, - methodId.protoId + methodId.protoId, + null ) _virtualTable[methodTableEntry] = methodTableEntry + methods[methodTableEntry] = MethodWrapper.Encoded(method) } for (method in it.directMethods) { val methodId = method.methodId val methodTableEntry = MethodTableEntry( methodId.name, - methodId.protoId + methodId.protoId, + null ) - if (method.accessFlags.isConstructor) { + if (method.accessFlags.isStatic) { + staticMethods[methodTableEntry] = MethodWrapper.Encoded(method) + } else if (method.accessFlags.isConstructor) { constructors[methodTableEntry] = MethodWrapper.Encoded(method) } else { _virtualTable[methodTableEntry] = methodTableEntry + methods[methodTableEntry] = MethodWrapper.Encoded(method) } } } @@ -65,6 +71,7 @@ class DexDefinedType( if (_virtualTable.containsKey(methodID)) { _interfaceTable[method.key] = methodID } else { + // TODO: Handle default methods throw IllegalStateException( "Required interface method ${method.key} not implemented in" + " ${classDef.classDef.typeId.descriptor}" diff --git a/src/main/kotlin/vm/classloader/MockedType.kt b/src/main/kotlin/vm/classloader/MockedType.kt index a017a5f..340e045 100644 --- a/src/main/kotlin/vm/classloader/MockedType.kt +++ b/src/main/kotlin/vm/classloader/MockedType.kt @@ -1,6 +1,9 @@ package com.yhs0602.vm.classloader import com.yhs0602.vm.MethodWrapper +import com.yhs0602.vm.makeMockedConstructor +import com.yhs0602.vm.makeMockedMethod +import java.lang.reflect.Modifier class MockedType( override val clazz: Class<*>, @@ -16,6 +19,7 @@ class MockedType( private val _virtualTable: MutableMap = mutableMapOf() override val methods = mutableMapOf() override val constructors = mutableMapOf() + override val staticMethods = mutableMapOf() init { // populate v-table and i-table of the class @@ -26,12 +30,19 @@ class MockedType( } // update the v-table with the current class's methods for (method in clazz.methods) { - val methodId = method.methodId() - val methodTableEntry = MethodTableEntry( - methodId.name, - methodId.protoId - ) - _virtualTable[methodTableEntry] = methodTableEntry + val methodId = method.methodTableEntry() + // if this is not a static method + if (Modifier.isStatic(method.modifiers)) { + staticMethods[methodId] = MethodWrapper.Mocked(makeMockedMethod(clazz, method)) + } else { + _virtualTable[methodId] = methodId + methods[methodId] = MethodWrapper.Mocked(makeMockedMethod(clazz, method)) + } + } + // Update the constructors + for (constructor in clazz.constructors) { + val methodTableEntry = constructor.methodTableEntry() + constructors[methodTableEntry] = MethodWrapper.Mocked(makeMockedConstructor(clazz, constructor)) } // update the i-table with the current class's interfaces for (interfaceTypeId in interfaces) { @@ -41,7 +52,17 @@ class MockedType( if (_virtualTable.containsKey(methodID)) { _interfaceTable[method.key] = methodID } else { - throw IllegalStateException("Required interface method ${method.key} not implemented in ${clazz.name}") + if (method.key.isDefault()) { + _virtualTable[methodID] = methodID + _interfaceTable[method.key] = methodID + } else { + throw IllegalStateException( + "Required interface method ${method.key} not implemented in ${clazz.name}," + + " required by ${interfaceTypeId.clazz.name}, ${method.key.method?.declaringClass?.isInterface}" + + " ${!Modifier.isAbstract(method.key.method?.modifiers ?: 0)}" + + " ${!Modifier.isStatic(method.key.method?.modifiers ?: 0)}" + ) + } } } } diff --git a/src/main/kotlin/vm/classloader/ObjectType.kt b/src/main/kotlin/vm/classloader/ObjectType.kt index 811374a..4d31700 100644 --- a/src/main/kotlin/vm/classloader/ObjectType.kt +++ b/src/main/kotlin/vm/classloader/ObjectType.kt @@ -3,6 +3,7 @@ package com.yhs0602.vm.classloader import com.yhs0602.vm.MethodWrapper import com.yhs0602.vm.makeMockedConstructor import com.yhs0602.vm.makeMockedMethod +import java.lang.reflect.Modifier data object ObjectType : Type() { override fun isAssignableTo(other: Type): Boolean { @@ -19,6 +20,7 @@ data object ObjectType : Type() { private val _virtualTable: MutableMap = mutableMapOf() override val descriptor: String = "Ljava/lang/Object;" override val methods = mutableMapOf() + override val staticMethods = mutableMapOf() override val constructors = mutableMapOf() override val clazz: Class<*> = java.lang.Object::class.java @@ -26,10 +28,16 @@ data object ObjectType : Type() { // populate v-table and i-table of Object for (method in java.lang.Object::class.java.methods) { val methodId = method.methodTableEntry() - _virtualTable[methodId] = methodId - methods[methodId] = MethodWrapper.Mocked( - makeMockedMethod(java.lang.Object::class.java, method) - ) + if (Modifier.isStatic(method.modifiers)) { + staticMethods[methodId] = MethodWrapper.Mocked( + makeMockedMethod(java.lang.Object::class.java, method) + ) + } else { + _virtualTable[methodId] = methodId + methods[methodId] = MethodWrapper.Mocked( + makeMockedMethod(java.lang.Object::class.java, method) + ) + } } for (constructorMethod in java.lang.Object::class.java.constructors) { val methodId = constructorMethod.methodTableEntry() diff --git a/src/main/kotlin/vm/classloader/PrimitiveType.kt b/src/main/kotlin/vm/classloader/PrimitiveType.kt index 697ac82..413a9c0 100644 --- a/src/main/kotlin/vm/classloader/PrimitiveType.kt +++ b/src/main/kotlin/vm/classloader/PrimitiveType.kt @@ -14,9 +14,12 @@ class Primitive(override val descriptor: String) : Type() { override val interfaceTable: Map = emptyMap() // no v table for primitive types + // TODO: java.lang.xxx.TYPE override val virtualTable: Map = emptyMap() override val methods = emptyMap() override val constructors = emptyMap() + override val staticMethods = emptyMap() + override val clazz: Class<*> by lazy { when (descriptor) { "Z" -> java.lang.Boolean.TYPE diff --git a/src/main/kotlin/vm/classloader/Type.kt b/src/main/kotlin/vm/classloader/Type.kt index 67192ae..7853157 100644 --- a/src/main/kotlin/vm/classloader/Type.kt +++ b/src/main/kotlin/vm/classloader/Type.kt @@ -2,11 +2,32 @@ package com.yhs0602.vm.classloader import com.yhs0602.dex.ProtoId import com.yhs0602.vm.MethodWrapper +import java.lang.reflect.Method +import java.lang.reflect.Modifier data class MethodTableEntry( val name: String, - val protoId: ProtoId -) + val protoId: ProtoId, + val method: Method? +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MethodTableEntry + + if (name != other.name) return false + if (protoId != other.protoId) return false + + return true + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + protoId.hashCode() + return result + } +} sealed class Type { @@ -16,6 +37,7 @@ sealed class Type { abstract val virtualTable: Map abstract val descriptor: String abstract val methods: Map + abstract val staticMethods: Map abstract val constructors: Map abstract val clazz: Class<*> @@ -64,4 +86,10 @@ sealed class Type { return false } +} + +fun MethodTableEntry.isDefault(): Boolean { + // Check if the method is a default method in the interface + if (method == null) return false + return method.declaringClass.isInterface && !Modifier.isAbstract(method.modifiers) && !Modifier.isStatic(method.modifiers) } \ No newline at end of file diff --git a/src/test/kotlin/AdvancedTest.kt b/src/test/kotlin/AdvancedTest.kt index cc80ec3..bfd5a58 100644 --- a/src/test/kotlin/AdvancedTest.kt +++ b/src/test/kotlin/AdvancedTest.kt @@ -82,7 +82,10 @@ class AdvancedTest { GeneralMockedClass(System::class.java), GeneralMockedClass(Intrinsics::class.java), GeneralMockedClass(Object::class.java), - GeneralMockedClass(PrintStream::class.java) + GeneralMockedClass(PrintStream::class.java), + GeneralMockedClass(kotlin.jvm.internal.Lambda::class.java), + GeneralMockedClass(kotlin.jvm.functions.Function0::class.java), + GeneralMockedClass(kotlin.jvm.functions.Function2::class.java), ) ) } @@ -102,7 +105,8 @@ class AdvancedTest { GeneralMockedClass(PrintStream::class.java), GeneralMockedClass(Math::class.java), GeneralMockedClass(File::class.java), - GeneralMockedClass(java.lang.Double::class.java) + GeneralMockedClass(java.lang.Double::class.java), + GeneralMockedClass(kotlin.jvm.internal.Lambda::class.java), ) ) }