Skip to content

Commit f2d2f3f

Browse files
Fix Sentry kmp stack trace crash on ios (#434)
* Fix dropCommonAddresses to handle edge cases and improve index tracking Co-authored-by: giancarlo.buenaflor <[email protected]> * Add comprehensive tests for stack trace filtering and address dropping Co-authored-by: giancarlo.buenaflor <[email protected]> * Update * Clean up tests * Update CHANGELOG * Simplify impl * Simplify impl even more * Simplify impl even more --------- Co-authored-by: Cursor Agent <[email protected]>
1 parent 2ac88e0 commit f2d2f3f

File tree

3 files changed

+133
-2
lines changed

3 files changed

+133
-2
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
# Changelog
22

3+
## Unreleased
4+
35
## Features
46

57
- Add `proguardUuid` option to `SentryOptions` ([#436](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/436))
68
- This will propagate the `proguardUuid` value to Sentry Android
79

10+
### Fixes
11+
12+
- Fix stack trace crash on Apple targets ([#434](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/434))
13+
814
## 0.17.1
915

1016
### Fixes

sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/nsexception/Throwable.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ internal fun List<Long>.dropCommonAddresses(
7777
): List<Long> {
7878
var i = commonAddresses.size
7979
if (i == 0) return this
80+
8081
return dropLastWhile {
81-
i-- >= 0 && commonAddresses[i] == it
82+
i-- > 0 && commonAddresses[i] == it
8283
}
8384
}

sentry-kotlin-multiplatform/src/appleTest/kotlin/io/sentry/kotlin/multiplatform/nsexception/CommonAddressesTests.kt

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import kotlin.test.assertEquals
1919
import kotlin.test.assertSame
2020

2121
class CommonAddressesTests {
22-
2322
@Test
2423
fun testDropCommon() {
2524
val commonAddresses = listOf<Long>(5, 4, 3, 2, 1, 0)
@@ -28,17 +27,142 @@ class CommonAddressesTests {
2827
assertEquals(listOf<Long>(8, 7, 6), withoutCommonAddresses)
2928
}
3029

30+
@Test
31+
fun testDropCommonPartialMatch() {
32+
val commonAddresses = listOf<Long>(3, 2, 1)
33+
val addresses = listOf<Long>(9, 8, 7, 2, 1)
34+
val withoutCommonAddresses = addresses.dropCommonAddresses(commonAddresses)
35+
assertEquals(listOf<Long>(9, 8, 7), withoutCommonAddresses)
36+
}
37+
38+
@Test
39+
fun testDropCommonNoMatch() {
40+
val commonAddresses = listOf<Long>(5, 4, 3)
41+
val addresses = listOf<Long>(9, 8, 7, 6)
42+
val withoutCommonAddresses = addresses.dropCommonAddresses(commonAddresses)
43+
assertEquals(addresses, withoutCommonAddresses)
44+
}
45+
46+
@Test
47+
fun testDropCommonSingleElementMatch() {
48+
val commonAddresses = listOf<Long>(1)
49+
val addresses = listOf<Long>(5, 4, 3, 1)
50+
val withoutCommonAddresses = addresses.dropCommonAddresses(commonAddresses)
51+
assertEquals(listOf<Long>(5, 4, 3), withoutCommonAddresses)
52+
}
53+
3154
@Test
3255
fun testDropCommonEmptyCommon() {
3356
val addresses = listOf<Long>(0, 1, 2)
3457
val withoutCommonAddresses = addresses.dropCommonAddresses(emptyList())
3558
assertSame(addresses, withoutCommonAddresses)
3659
}
3760

61+
@Test
62+
fun testDropCommonEmptyAddresses() {
63+
val commonAddresses = listOf<Long>(1, 2, 3)
64+
val addresses = emptyList<Long>()
65+
val withoutCommonAddresses = addresses.dropCommonAddresses(commonAddresses)
66+
assertSame(addresses, withoutCommonAddresses)
67+
}
68+
3869
@Test
3970
fun testDropCommonSameAddresses() {
4071
val addresses = listOf<Long>(0, 1, 2)
4172
val withoutCommonAddresses = addresses.dropCommonAddresses(addresses)
4273
assertEquals(emptyList(), withoutCommonAddresses)
4374
}
75+
76+
@Test
77+
fun testDropCommonLargerCommonList() {
78+
val commonAddresses = listOf<Long>(5, 4, 3, 2, 1, 0)
79+
val addresses = listOf<Long>(2, 1, 0)
80+
val withoutCommonAddresses = addresses.dropCommonAddresses(commonAddresses)
81+
assertEquals(emptyList(), withoutCommonAddresses)
82+
}
83+
84+
@Test
85+
fun testDropCommonLargerAddressList() {
86+
val commonAddresses = listOf<Long>(2, 1, 0)
87+
val addresses = listOf<Long>(9, 8, 7, 6, 5, 4, 2, 1, 0)
88+
val withoutCommonAddresses = addresses.dropCommonAddresses(commonAddresses)
89+
assertEquals(listOf<Long>(9, 8, 7, 6, 5, 4), withoutCommonAddresses)
90+
}
91+
92+
@Test
93+
fun testDropCommonNoIndexOutOfBounds_LargeCommonList() {
94+
// This test specifically targets the original bug where i-- could become -1
95+
val commonAddresses = (0L..26L).toList().reversed() // 27 elements: [26, 25, ..., 1, 0]
96+
val addresses = listOf<Long>(30, 29, 28, 2, 1, 0)
97+
98+
// This should not throw IndexOutOfBoundsException
99+
val withoutCommonAddresses = addresses.dropCommonAddresses(commonAddresses)
100+
assertEquals(listOf<Long>(30, 29, 28), withoutCommonAddresses)
101+
}
102+
103+
@Test
104+
fun testDropCommonNoIndexOutOfBounds_ExactSizeMatch() {
105+
// Test when commonAddresses.size equals the number of matching elements
106+
val commonAddresses = listOf<Long>(4, 3, 2, 1, 0)
107+
val addresses = listOf<Long>(9, 8, 7, 4, 3, 2, 1, 0)
108+
109+
val withoutCommonAddresses = addresses.dropCommonAddresses(commonAddresses)
110+
assertEquals(listOf<Long>(9, 8, 7), withoutCommonAddresses)
111+
}
112+
113+
@Test
114+
fun testDropCommonNoIndexOutOfBounds_OffByOneScenario() {
115+
// Test the exact scenario that caused the original crash
116+
val commonAddresses = (1L..27L).toList() // size: 27
117+
val addresses = listOf<Long>(100L, 99L, 98L) + commonAddresses.takeLast(3)
118+
119+
val withoutCommonAddresses = addresses.dropCommonAddresses(commonAddresses)
120+
assertEquals(listOf<Long>(100L, 99L, 98L), withoutCommonAddresses)
121+
}
122+
123+
@Test
124+
fun testDropCommonRepeatedElements() {
125+
val commonAddresses = listOf<Long>(1, 1, 1, 0, 0)
126+
val addresses = listOf<Long>(5, 4, 1, 1, 0)
127+
128+
val withoutCommonAddresses = addresses.dropCommonAddresses(commonAddresses)
129+
assertEquals(listOf<Long>(5, 4, 1, 1), withoutCommonAddresses)
130+
}
131+
132+
@Test
133+
fun testDropCommonMaintainsOrder() {
134+
val commonAddresses = listOf<Long>(3, 2, 1)
135+
val addresses = listOf<Long>(9, 8, 7, 6, 5, 3, 2, 1)
136+
137+
val withoutCommonAddresses = addresses.dropCommonAddresses(commonAddresses)
138+
assertEquals(listOf<Long>(9, 8, 7, 6, 5), withoutCommonAddresses)
139+
}
140+
141+
@Test
142+
fun testDropCommonOutOfSequenceMatch() {
143+
// Common addresses are in different order than in the target list
144+
val commonAddresses = listOf<Long>(1, 3, 2) // Note: 3 and 2 are swapped
145+
val addresses = listOf<Long>(9, 8, 7, 6, 2)
146+
147+
val withoutCommonAddresses = addresses.dropCommonAddresses(commonAddresses)
148+
assertEquals(listOf<Long>(9, 8, 7, 6), withoutCommonAddresses) // Should drop 2
149+
}
150+
151+
@Test
152+
fun testDropCommonMixedPositiveNegative() {
153+
val commonAddresses = listOf<Long>(1, 0, -1)
154+
val addresses = listOf<Long>(5, 4, 3, 1, 0, -1)
155+
156+
val withoutCommonAddresses = addresses.dropCommonAddresses(commonAddresses)
157+
assertEquals(listOf<Long>(5, 4, 3), withoutCommonAddresses)
158+
}
159+
160+
@Test
161+
fun testDropCommonLargeNumbers() {
162+
val commonAddresses = listOf(Long.MAX_VALUE - 1, Long.MAX_VALUE - 2)
163+
val addresses = listOf(Long.MAX_VALUE, Long.MAX_VALUE - 1, Long.MAX_VALUE - 2)
164+
165+
val withoutCommonAddresses = addresses.dropCommonAddresses(commonAddresses)
166+
assertEquals(listOf(Long.MAX_VALUE), withoutCommonAddresses)
167+
}
44168
}

0 commit comments

Comments
 (0)