Skip to content

Commit 1a73956

Browse files
authored
Add exception serializing fallback that only considers message, stackTrace, and cause (#2324)
Add exception serializing fallback that only considers `classId`, `message`, `stackTrace`, and `cause`
1 parent 117f841 commit 1a73956

File tree

2 files changed

+66
-22
lines changed

2 files changed

+66
-22
lines changed

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/JavaSerializerWrapper.kt

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,82 @@ package org.utbot.instrumentation.util
22

33
import com.esotericsoftware.kryo.kryo5.Kryo
44
import com.esotericsoftware.kryo.kryo5.KryoException
5+
import com.esotericsoftware.kryo.kryo5.Serializer
56
import com.esotericsoftware.kryo.kryo5.io.Input
6-
import com.esotericsoftware.kryo.kryo5.serializers.JavaSerializer
7-
import org.utbot.framework.plugin.api.util.utContext
7+
import com.esotericsoftware.kryo.kryo5.io.Output
8+
import com.jetbrains.rd.util.getLogger
9+
import com.jetbrains.rd.util.warn
10+
import org.utbot.framework.plugin.api.ClassId
11+
import org.utbot.framework.plugin.api.util.id
12+
import org.utbot.framework.plugin.api.util.jClass
13+
import java.io.ByteArrayInputStream
14+
import java.io.ByteArrayOutputStream
815
import java.io.InputStream
916
import java.io.ObjectInputStream
17+
import java.io.ObjectOutputStream
1018
import java.io.ObjectStreamClass
19+
import java.lang.RuntimeException
1120

12-
/**
13-
* This ad-hoc solution for ClassNotFoundException
14-
*/
15-
class JavaSerializerWrapper : JavaSerializer() {
16-
override fun read(kryo: Kryo, input: Input?, type: Class<*>?): Any? {
17-
return try {
18-
val graphContext = kryo.graphContext
19-
var objectStream = graphContext.get<Any>(this) as ObjectInputStream?
20-
if (objectStream == null) {
21-
objectStream = IgnoringUidWrappingObjectInputStream(input, kryo)
22-
graphContext.put(this, objectStream)
21+
class ThrowableSerializer : Serializer<Throwable>() {
22+
companion object {
23+
private val loggedUnserializableExceptionClassIds = mutableSetOf<ClassId>()
24+
private val logger = getLogger<ThrowableSerializer>()
25+
}
26+
27+
private class ThrowableModel(
28+
val classId: ClassId,
29+
val message: String?,
30+
val stackTrace: Array<StackTraceElement>,
31+
val cause: ThrowableModel?,
32+
val serializedException: ByteArray,
33+
)
34+
35+
override fun write(kryo: Kryo, output: Output, throwable: Throwable?) {
36+
fun Throwable.toModel(): ThrowableModel = ThrowableModel(
37+
classId = this::class.java.id,
38+
message = message,
39+
stackTrace = stackTrace,
40+
cause = cause?.toModel(),
41+
serializedException = ByteArrayOutputStream().use { byteOutputStream ->
42+
val objectOutputStream = ObjectOutputStream(byteOutputStream)
43+
objectOutputStream.writeObject(this)
44+
objectOutputStream.flush()
45+
byteOutputStream.toByteArray()
46+
}
47+
)
48+
kryo.writeObject(output, throwable?.toModel())
49+
}
50+
51+
override fun read(kryo: Kryo, input: Input, type: Class<out Throwable>): Throwable? {
52+
fun ThrowableModel.toThrowable(): Throwable = try {
53+
ByteArrayInputStream(this.serializedException).use { byteInputStream ->
54+
val objectInputStream = IgnoringUidWrappingObjectInputStream(byteInputStream, kryo.classLoader)
55+
objectInputStream.readObject() as Throwable
56+
}
57+
} catch (e: Throwable) {
58+
if (loggedUnserializableExceptionClassIds.add(this.classId)) {
59+
logger.warn { "Failed to deserialize ${this.classId} from bytes, cause: $e" }
60+
logger.warn { "Falling back to constructing throwable instance from ThrowableModel" }
61+
}
62+
63+
val cause = cause?.toThrowable()
64+
when {
65+
RuntimeException::class.java.isAssignableFrom(classId.jClass) -> RuntimeException(message, cause)
66+
Error::class.java.isAssignableFrom(classId.jClass) -> Error(message, cause)
67+
else -> Exception(message, cause)
68+
}.also {
69+
it.stackTrace = stackTrace
2370
}
24-
objectStream.readObject()
25-
} catch (ex: java.lang.Exception) {
26-
throw KryoException("Error during Java deserialization.", ex)
2771
}
72+
73+
return kryo.readObject(input, ThrowableModel::class.java)?.toThrowable()
2874
}
2975
}
3076

31-
class IgnoringUidWrappingObjectInputStream(iss : InputStream?, private val kryo: Kryo) : ObjectInputStream(iss) {
77+
class IgnoringUidWrappingObjectInputStream(iss : InputStream?, private val classLoader: ClassLoader) : ObjectInputStream(iss) {
3278
override fun resolveClass(type: ObjectStreamClass): Class<*>? {
3379
return try {
34-
Class.forName(type.name, false, kryo.classLoader)
80+
Class.forName(type.name, false, classLoader)
3581
} catch (ex: ClassNotFoundException) {
3682
try {
3783
return Kryo::class.java.classLoader.loadClass(type.name)
@@ -52,7 +98,7 @@ class IgnoringUidWrappingObjectInputStream(iss : InputStream?, private val kryo:
5298

5399
// the class in the local JVM that this descriptor represents.
54100
val localClass: Class<*> = try {
55-
kryo.classLoader.loadClass(resultClassDescriptor.name)
101+
classLoader.loadClass(resultClassDescriptor.name)
56102
} catch (e: ClassNotFoundException) {
57103
return resultClassDescriptor
58104
}

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/KryoHelper.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
package org.utbot.instrumentation.util
22

33
import com.esotericsoftware.kryo.kryo5.Kryo
4-
import com.esotericsoftware.kryo.kryo5.Serializer
54
import com.esotericsoftware.kryo.kryo5.SerializerFactory
65
import com.esotericsoftware.kryo.kryo5.io.Input
76
import com.esotericsoftware.kryo.kryo5.io.Output
87
import com.esotericsoftware.kryo.kryo5.objenesis.instantiator.ObjectInstantiator
9-
import com.esotericsoftware.kryo.kryo5.objenesis.strategy.InstantiatorStrategy
108
import com.esotericsoftware.kryo.kryo5.objenesis.strategy.StdInstantiatorStrategy
119
import com.esotericsoftware.kryo.kryo5.util.DefaultInstantiatorStrategy
1210
import com.jetbrains.rd.util.lifetime.Lifetime
@@ -109,7 +107,7 @@ internal class TunedKryo : Kryo() {
109107

110108
// Kryo cannot (at least, the current used version) deserialize stacktraces that are required for SARIF reports.
111109
// TODO: JIRA:1492
112-
addDefaultSerializer(java.lang.Throwable::class.java, JavaSerializerWrapper())
110+
addDefaultSerializer(java.lang.Throwable::class.java, ThrowableSerializer())
113111

114112
val factory = object : SerializerFactory.FieldSerializerFactory() {}
115113
factory.config.ignoreSyntheticFields = true

0 commit comments

Comments
 (0)