@@ -32,13 +32,29 @@ internal fun <E : Throwable> tryCopyException(exception: E): E? {
32
32
33
33
private fun <E : Throwable > createConstructor (clz : Class <E >): Ctor {
34
34
val nullResult: Ctor = { null } // Pre-cache class
35
- // Skip reflective copy if an exception has additional fields (that are usually populated in user-defined constructors)
35
+ // Skip reflective copy if an exception has additional fields (that are typically populated in user-defined constructors)
36
36
if (throwableFields != clz.fieldsCountOrDefault(0 )) return nullResult
37
37
/*
38
- * Try to reflectively find constructor(), constructor(message, cause), constructor(cause) or constructor(message).
39
- * Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace.
40
- */
41
- val constructors = clz.constructors.sortedByDescending { it.parameterTypes.size }
38
+ * Try to reflectively find constructor(message, cause), constructor(message), constructor(cause), or constructor()
39
+ * Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace.
40
+ *
41
+ * By default, Java's reflection iterates over ctors in the source-code order and the sorting is stable,
42
+ * so in comparator we are picking the message ctor over the cause one to break such implcit dependency on the source code.
43
+ */
44
+ val constructors = clz.constructors.sortedByDescending {
45
+ // (m, c) -> 3
46
+ // (m) -> 2
47
+ // (c) -> 1
48
+ // () -> 0
49
+ val params = it.parameterTypes // cloned array
50
+ when (params.size) {
51
+ 2 -> 3
52
+ 1 -> if (params[0 ] == String ::class .java) 2 else 1
53
+ 0 -> 0
54
+ else -> - 1
55
+ }
56
+
57
+ }
42
58
for (constructor in constructors) {
43
59
val result = createSafeConstructor(constructor )
44
60
if (result != null ) return result
@@ -66,8 +82,17 @@ private fun createSafeConstructor(constructor: Constructor<*>): Ctor? {
66
82
}
67
83
}
68
84
69
- private inline fun safeCtor (crossinline block : (Throwable ) -> Throwable ): Ctor =
70
- { e -> runCatching { block(e) }.getOrNull() }
85
+ private inline fun safeCtor (crossinline block : (Throwable ) -> Throwable ): Ctor = { e ->
86
+ runCatching {
87
+ val result = block(e)
88
+ /*
89
+ * Verify that the new exception has the same message as the original one (bail out if not, see #1631)
90
+ * or if the new message complies the contract from `Throwable(cause).message` contract.
91
+ */
92
+ if (e.message != result.message && result.message != e.toString()) null
93
+ else result
94
+ }.getOrNull()
95
+ }
71
96
72
97
private fun Class <* >.fieldsCountOrDefault (defaultValue : Int ) =
73
98
kotlin.runCatching { fieldsCount() }.getOrDefault(defaultValue)
0 commit comments