Skip to content

Commit

Permalink
[instrumentation-serialization]
Browse files Browse the repository at this point in the history
attempt #1, stucked on cyclic references and random primitive
serialization bugs
  • Loading branch information
Domonion committed Jul 4, 2022
1 parent 53b1558 commit fe3c4b1
Show file tree
Hide file tree
Showing 46 changed files with 1,318 additions and 552 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ buildscript {
dependencies {
classpath group: 'org.jetbrains.kotlin', name: 'kotlin-gradle-plugin', version: kotlin_version
classpath group: 'org.jetbrains.kotlin', name: 'kotlin-allopen', version: kotlin_version
classpath group: 'org.jetbrains.kotlin', name: 'kotlin-serialization', version: kotlin_version
}
}

Expand Down
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ javacpp_version=1.5.3
jsoup_version=1.7.2
djl_api_version=0.17.0
pytorch_native_version=1.9.1
# soot also depends on asm, so there could be two different versions
# soot also depends on asm, so there could be two different versions
org.gradle.jvmargs=-Xmx4096m
4 changes: 4 additions & 0 deletions gradle/include/jvm-project.gradle
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'kotlinx-serialization'

dependencies {
implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutines_version
implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-collections-immutable-jvm', version: collections_version
implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlin_version
implementation group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_version

implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-serialization-json', version: '1.0.1'
implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-serialization-core', version: '1.0.1'

testImplementation("org.junit.jupiter:junit-jupiter:$junit5_version"){
force = true
}
Expand Down
40 changes: 40 additions & 0 deletions utbot-core/src/main/kotlin/org/utbot/common/Serializers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.utbot.common

import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.descriptors.element
import kotlinx.serialization.encoding.*

object IntRangeSerializer: KSerializer<IntRange> {
override val descriptor: SerialDescriptor
get() = buildClassSerialDescriptor("asd") {
element<Int>("first")
element<Int>("last")
}

override fun deserialize(decoder: Decoder): IntRange {
return decoder.decodeStructure(descriptor) {
var first = -1
var last = -1

while (true) {
when(val index = decodeElementIndex(descriptor)) {
0 -> first = decodeIntElement(descriptor, 0)
1 -> last = decodeIntElement(descriptor, 1)
CompositeDecoder.DECODE_DONE -> break
else -> error("Unexpected index: $index")
}
}

IntRange(first, last)
}
}

override fun serialize(encoder: Encoder, value: IntRange) {
encoder.encodeStructure(descriptor) {
encodeIntElement(descriptor, 0, value.first)
encodeIntElement(descriptor, 1, value.last)
}
}
}
238 changes: 238 additions & 0 deletions utbot-framework-api/src/main/kotlin/org/utbot/framework/Serializers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
package org.utbot.framework

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.serializer
import org.utbot.common.IntRangeSerializer
import org.utbot.framework.plugin.api.*
import org.utbot.framework.plugin.api.util.*

private val classIdSerializer = serializer<ClassId>()

object UtContextThrowableSerializer : KSerializer<Throwable> {
override val descriptor: SerialDescriptor
get() = TODO("Not yet implemented")

override fun deserialize(decoder: Decoder): Throwable {
TODO("Not yet implemented")
}

override fun serialize(encoder: Encoder, value: Throwable) {
TODO("Not yet implemented")
}

}

object UtContextClassSerializer : KSerializer<Class<*>> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("utClass", PrimitiveKind.STRING)

override fun deserialize(decoder: Decoder): Class<*> {
val name = decoder.decodeString()

return utContext.classLoader.loadClass(name)
}

override fun serialize(encoder: Encoder, value: Class<*>) {
encoder.encodeString(value.name)
}
}

object UtContextNullableClassSerializer : KSerializer<Class<*>?> {
override fun deserialize(decoder: Decoder): Class<*>? {
val decoded = decoder.decodeString()

return if (decoded == "1") null else utContext.classLoader.loadClass(decoded)
}

override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("utClassNullable", PrimitiveKind.STRING)

override fun serialize(encoder: Encoder, value: Class<*>?) {
encoder.encodeString(value?.name ?: "1") // 1 because classes cannot be named with numbers
}
}

private val surrogateBuiltinClassIdSerializer = serializer<SurrogateBuiltinClassId>()

object BuiltinClassIdSerializer : KSerializer<BuiltinClassId> {
@InternalSerializationApi
@ExperimentalSerializationApi
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("BuiltinClassId") {
element<SurrogateBuiltinClassId>("surrogate")
element("outerClass", UtContextNullableClassSerializer.descriptor)
}

@InternalSerializationApi
@ExperimentalSerializationApi
override fun deserialize(decoder: Decoder): BuiltinClassId {
return decoder.decodeStructure(descriptor) {
var surrogate: SurrogateBuiltinClassId? = null
var outerClass: Class<*>? = null

while (true) {
val index = decodeElementIndex(descriptor)

when (index) {
0 -> surrogate = decodeSerializableElement(descriptor, 0, surrogateBuiltinClassIdSerializer)
1 -> outerClass = decodeSerializableElement(descriptor, 1, UtContextNullableClassSerializer)
CompositeDecoder.DECODE_DONE -> break
else -> error("unknown element $index")
}
}

return BuiltinClassId(
surrogate!!.name,
surrogate.canonicalName,
surrogate.simpleName,
surrogate.simpleNameWithEnclosings,
surrogate.isPublic,
surrogate.isProtected,
surrogate.isPrivate,
surrogate.isFinal,
surrogate.isStatic,
surrogate.isAbstract,
surrogate.isAnonymous,
surrogate.isLocal,
surrogate.isInner,
surrogate.isNested,
surrogate.isSynthetic,
surrogate.allMethods,
surrogate.allConstructors,
surrogate.outerClass
)
}
}

@InternalSerializationApi
@ExperimentalSerializationApi
override fun serialize(encoder: Encoder, value: BuiltinClassId) {
encoder.encodeStructure(descriptor) {
encodeSerializableElement(
descriptor,
0,
surrogateBuiltinClassIdSerializer,
value as SurrogateBuiltinClassId
)
encodeSerializableElement(descriptor, 1, UtContextNullableClassSerializer, value.outerClass)
}
}
}

private fun <T : Enum<T>> safeValueOf(enumType: Class<T>, type: String): T {
return java.lang.Enum.valueOf(enumType, type)
}

object UtEnumConstantModelSerializer : KSerializer<UtEnumConstantModel> {
@InternalSerializationApi
@ExperimentalSerializationApi
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("UtEnumConstantModel") {
element("classId", classIdSerializer.descriptor)
element<String>("enum")
}

@InternalSerializationApi
@ExperimentalSerializationApi
override fun deserialize(decoder: Decoder): UtEnumConstantModel {
return decoder.decodeStructure(descriptor) {
var classId: ClassId? = null
var enum: String? = null

while (true) {
when (val index = decodeElementIndex(IntRangeSerializer.descriptor)) {
0 -> classId = decodeSerializableElement(descriptor, 0, classIdSerializer)
1 -> enum = decodeStringElement(descriptor, 1)
CompositeDecoder.DECODE_DONE -> break
else -> error("Unexpected token $index")
}
}


@Suppress("UNCHECKED_CAST")
// suppose that UtEnumConstantModel.classId is underlying enum class
UtEnumConstantModel(classId!!, safeValueOf(classId.jClass as Class<out Enum<*>>, enum!!))
}
}

@InternalSerializationApi
@ExperimentalSerializationApi
override fun serialize(encoder: Encoder, value: UtEnumConstantModel) {
encoder.encodeStructure(descriptor) {
encodeSerializableElement(descriptor, 0, classIdSerializer, value.classId)
encodeStringElement(descriptor, 1, value.value.name)
}
}

}

private fun String.parseToPrimitiveTypeJvmName(primitiveType: Char): Any {
return when (primitiveType) {
'V' -> Unit
'Z' -> toBoolean()
'B' -> toByte()
'C' -> single()
'S' -> toShort()
'I' -> toInt()
'J' -> toLong()
'F' -> toFloat()
'D' -> toDouble()
else -> error("Primitive expected here, but got: $primitiveType")
}
}

object UtPrimitiveModelSerializer : KSerializer<UtPrimitiveModel> {
// todo kononov: possible speed up - char+string instead to remove string concatenation, it is just premilinary version
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UtPrimitiveModel", PrimitiveKind.STRING)

override fun deserialize(decoder: Decoder): UtPrimitiveModel {
val decodedString = decoder.decodeString()

return UtPrimitiveModel(decodedString.substring(1).parseToPrimitiveTypeJvmName(decodedString.first()))
}

override fun serialize(encoder: Encoder, value: UtPrimitiveModel) {
// because name `value` comes from KSerializer interface,
// so that name collision is kept to confront calling this function with named parameters
@Suppress("UnnecessaryVariable")
val primitiveModel = value

if (!primitiveModel.classId.isPrimitive || primitiveModelValueToClassId(primitiveModel.value) != primitiveModel.classId) {
throw IllegalStateException("PrimitiveModel invariant corrupted: has ${primitiveModel.classId} for ${primitiveModel.value}")
}

val jvmTypeName = primitiveModel.classId.primitiveTypeJvmNameOrNull()!!
val valueString = primitiveModel.value.toString()

encoder.encodeString(jvmTypeName + valueString)
}
}

object UtClassRefModelSerializer : KSerializer<UtClassRefModel> {
@InternalSerializationApi
@ExperimentalSerializationApi
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("UtClassRefModel") {
element("classId", classIdSerializer.descriptor)
}

override fun deserialize(decoder: Decoder): UtClassRefModel {
val classId = decoder.decodeSerializableValue(classIdSerializer)

// if serialization succeeded - expecting deserialization be succeeded
return UtClassRefModel(classId, classId.jClass)
}

override fun serialize(encoder: Encoder, value: UtClassRefModel) {
// because name `value` comes from KSerializer interface,
// so that name collision is kept to confront calling this function with named parameters
@Suppress("UnnecessaryVariable")
val classRefModel = value
val underlyingClassId = classRefModel.value.id

if (underlyingClassId != classRefModel.classId) {
throw IllegalStateException("classRefModel invariant corrupted: has ${classRefModel.classId} for $underlyingClassId")
}

encoder.encodeSerializableValue(classIdSerializer, classRefModel.classId)
}
}
Loading

0 comments on commit fe3c4b1

Please sign in to comment.