Skip to content

Commit

Permalink
✨ Add classloader and fix bugs; must use lazy
Browse files Browse the repository at this point in the history
  • Loading branch information
yhs0602 committed May 15, 2024
1 parent 0e0c276 commit 645f1d6
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 24 deletions.
2 changes: 1 addition & 1 deletion src/main/kotlin/dex/ShortyDescriptor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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(")")
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/vm/Environment.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -59,6 +60,7 @@ class Environment(
private val staticFields = mutableMapOf<Pair<TypeId, Int>, Array<RegisterValue>>()
private val initializedClasses: MutableSet<TypeId> = mutableSetOf()
private val typeIdToCustomClass: MutableMap<TypeId, Class<*>> = mutableMapOf()
private val classLoader = DexClassLoader(dexFiles, mockedClasses)

init {
dexFiles.forEach { dexFile ->
Expand Down
6 changes: 4 additions & 2 deletions src/main/kotlin/vm/classloader/DescriptorExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ fun Method.methodTableEntry(): MethodTableEntry {
parameters = this@methodTableEntry.parameterTypes.map {
TypeId(it.descriptorString())
}
}
},
method = this
)
}

Expand All @@ -85,6 +86,7 @@ fun Constructor<*>.methodTableEntry(): MethodTableEntry {
parameters = this@methodTableEntry.parameterTypes.map {
TypeId(it.descriptorString())
}
}
},
null
)
}
6 changes: 4 additions & 2 deletions src/main/kotlin/vm/classloader/DexClassLoader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
15 changes: 11 additions & 4 deletions src/main/kotlin/vm/classloader/DexDefinedType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -23,6 +22,7 @@ class DexDefinedType(
private val _virtualTable: MutableMap<MethodTableEntry, MethodTableEntry> = mutableMapOf()
override val methods = mutableMapOf<MethodTableEntry, MethodWrapper>()
override val constructors = mutableMapOf<MethodTableEntry, MethodWrapper>()
override val staticMethods = mutableMapOf<MethodTableEntry, MethodWrapper>()
override val clazz by lazy {
makeClazz()
}
Expand All @@ -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)
}
}
}
Expand All @@ -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}"
Expand Down
35 changes: 28 additions & 7 deletions src/main/kotlin/vm/classloader/MockedType.kt
Original file line number Diff line number Diff line change
@@ -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<*>,
Expand All @@ -16,6 +19,7 @@ class MockedType(
private val _virtualTable: MutableMap<MethodTableEntry, MethodTableEntry> = mutableMapOf()
override val methods = mutableMapOf<MethodTableEntry, MethodWrapper>()
override val constructors = mutableMapOf<MethodTableEntry, MethodWrapper>()
override val staticMethods = mutableMapOf<MethodTableEntry, MethodWrapper>()

init {
// populate v-table and i-table of the class
Expand All @@ -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) {
Expand All @@ -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)}"
)
}
}
}
}
Expand Down
16 changes: 12 additions & 4 deletions src/main/kotlin/vm/classloader/ObjectType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -19,17 +20,24 @@ data object ObjectType : Type() {
private val _virtualTable: MutableMap<MethodTableEntry, MethodTableEntry> = mutableMapOf()
override val descriptor: String = "Ljava/lang/Object;"
override val methods = mutableMapOf<MethodTableEntry, MethodWrapper>()
override val staticMethods = mutableMapOf<MethodTableEntry, MethodWrapper>()
override val constructors = mutableMapOf<MethodTableEntry, MethodWrapper>()
override val clazz: Class<*> = java.lang.Object::class.java

init {
// 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()
Expand Down
3 changes: 3 additions & 0 deletions src/main/kotlin/vm/classloader/PrimitiveType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ class Primitive(override val descriptor: String) : Type() {
override val interfaceTable: Map<MethodTableEntry, MethodTableEntry> = emptyMap()

// no v table for primitive types
// TODO: java.lang.xxx.TYPE
override val virtualTable: Map<MethodTableEntry, MethodTableEntry> = emptyMap()
override val methods = emptyMap<MethodTableEntry, MethodWrapper>()
override val constructors = emptyMap<MethodTableEntry, MethodWrapper>()
override val staticMethods = emptyMap<MethodTableEntry, MethodWrapper>()

override val clazz: Class<*> by lazy {
when (descriptor) {
"Z" -> java.lang.Boolean.TYPE
Expand Down
32 changes: 30 additions & 2 deletions src/main/kotlin/vm/classloader/Type.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -16,6 +37,7 @@ sealed class Type {
abstract val virtualTable: Map<MethodTableEntry, MethodTableEntry>
abstract val descriptor: String
abstract val methods: Map<MethodTableEntry, MethodWrapper>
abstract val staticMethods: Map<MethodTableEntry, MethodWrapper>
abstract val constructors: Map<MethodTableEntry, MethodWrapper>
abstract val clazz: Class<*>

Expand Down Expand Up @@ -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)
}
8 changes: 6 additions & 2 deletions src/test/kotlin/AdvancedTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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),
)
)
}
Expand All @@ -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),
)
)
}
Expand Down

0 comments on commit 645f1d6

Please sign in to comment.