Skip to content

Commit ffe1117

Browse files
romtsnclaude
andcommitted
fix(replay): Fix VerifyError in Compose masking under DexGuard/R8 obfuscation
ComposeViewHierarchyNode.boundsInWindow returned an android.graphics.Rect while the surrounding code carried it as androidx.compose.ui.geometry.Rect, mixing the two Rect types in the same method. Under aggressive obfuscation (DexGuard 9.13.2 / R8 full mode) this could be rejected at class load with a VerifyError, crashing Replay when traversing the Compose tree. Make boundsInWindow return androidx.compose.ui.geometry.Rect throughout and add a Rect.toRect() extension to convert to android.graphics.Rect only at the boundary where the view-hierarchy node needs it. Fixes #5497 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b936425 commit ffe1117

2 files changed

Lines changed: 24 additions & 19 deletions

File tree

sentry-android-replay/src/main/java/io/sentry/android/replay/util/Nodes.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
package io.sentry.android.replay.util
44

5-
import android.graphics.Rect
65
import androidx.compose.ui.geometry.Offset
6+
import androidx.compose.ui.geometry.Rect
77
import androidx.compose.ui.graphics.Color
88
import androidx.compose.ui.graphics.ColorProducer
99
import androidx.compose.ui.graphics.painter.Painter
@@ -176,7 +176,7 @@ internal fun LayoutCoordinates.boundsInWindow(rootCoordinates: LayoutCoordinates
176176
val boundsBottom = bounds.bottom.fastCoerceIn(0f, rootHeight)
177177

178178
if (boundsLeft == boundsRight || boundsTop == boundsBottom) {
179-
return Rect()
179+
return Rect(0.0f, 0.0f, 0.0f, 0.0f)
180180
}
181181

182182
val topLeft = root.localToWindow(Offset(boundsLeft, boundsTop))
@@ -200,5 +200,9 @@ internal fun LayoutCoordinates.boundsInWindow(rootCoordinates: LayoutCoordinates
200200
val top = fastMinOf(topLeftY, topRightY, bottomLeftY, bottomRightY)
201201
val bottom = fastMaxOf(topLeftY, topRightY, bottomLeftY, bottomRightY)
202202

203-
return Rect(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())
203+
return Rect(left, top, right, bottom)
204+
}
205+
206+
internal fun Rect.toRect(): android.graphics.Rect {
207+
return android.graphics.Rect(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())
204208
}

sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import io.sentry.android.replay.util.findPainter
2727
import io.sentry.android.replay.util.findTextColor
2828
import io.sentry.android.replay.util.isMaskable
2929
import io.sentry.android.replay.util.toOpaque
30+
import io.sentry.android.replay.util.toRect
3031
import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode.GenericViewHierarchyNode
3132
import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode.ImageViewHierarchyNode
3233
import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode.TextViewHierarchyNode
@@ -150,8 +151,8 @@ internal object ComposeViewHierarchyNode {
150151
// If we're unable to retrieve the semantics configuration
151152
// we should play safe and mask the whole node.
152153
return GenericViewHierarchyNode(
153-
x = visibleRect.left.toFloat(),
154-
y = visibleRect.top.toFloat(),
154+
x = visibleRect.left,
155+
y = visibleRect.top,
155156
width = node.width,
156157
height = node.height,
157158
elevation = (parent?.elevation ?: 0f),
@@ -161,17 +162,17 @@ internal object ComposeViewHierarchyNode {
161162
isImportantForContentCapture = false, // will be set by children
162163
isVisible =
163164
!SentryLayoutNodeHelper.isTransparent(node) &&
164-
visibleRect.height() > 0 &&
165-
visibleRect.width() > 0,
166-
visibleRect = visibleRect,
165+
visibleRect.height > 0 &&
166+
visibleRect.width > 0,
167+
visibleRect = visibleRect.toRect(),
167168
)
168169
}
169170

170171
val isVisible =
171172
!SentryLayoutNodeHelper.isTransparent(node) &&
172173
(semantics == null || !semantics.contains(SemanticsProperties.InvisibleToUser)) &&
173-
visibleRect.height() > 0 &&
174-
visibleRect.width() > 0
174+
visibleRect.height > 0 &&
175+
visibleRect.width > 0
175176
val isEditable =
176177
semantics?.contains(SemanticsActions.SetText) == true ||
177178
semantics?.contains(SemanticsProperties.EditableText) == true
@@ -206,8 +207,8 @@ internal object ComposeViewHierarchyNode {
206207
null
207208
},
208209
dominantColor = textColor?.toArgb()?.toOpaque(),
209-
x = visibleRect.left.toFloat(),
210-
y = visibleRect.top.toFloat(),
210+
x = visibleRect.left,
211+
y = visibleRect.top,
211212
width = node.width,
212213
height = node.height,
213214
elevation = (parent?.elevation ?: 0f),
@@ -216,7 +217,7 @@ internal object ComposeViewHierarchyNode {
216217
shouldMask = shouldMask,
217218
isImportantForContentCapture = true,
218219
isVisible = isVisible,
219-
visibleRect = visibleRect,
220+
visibleRect = visibleRect.toRect(),
220221
)
221222
}
222223
else -> {
@@ -226,8 +227,8 @@ internal object ComposeViewHierarchyNode {
226227

227228
parent?.setImportantForCaptureToAncestors(true)
228229
ImageViewHierarchyNode(
229-
x = visibleRect.left.toFloat(),
230-
y = visibleRect.top.toFloat(),
230+
x = visibleRect.left,
231+
y = visibleRect.top,
231232
width = node.width,
232233
height = node.height,
233234
elevation = (parent?.elevation ?: 0f),
@@ -236,7 +237,7 @@ internal object ComposeViewHierarchyNode {
236237
isVisible = isVisible,
237238
isImportantForContentCapture = true,
238239
shouldMask = shouldMask && painter.isMaskable(),
239-
visibleRect = visibleRect,
240+
visibleRect = visibleRect.toRect(),
240241
)
241242
} else {
242243
val shouldMask = isVisible && semantics.shouldMask(isImage = false, options)
@@ -245,8 +246,8 @@ internal object ComposeViewHierarchyNode {
245246
// TODO: traverse the ViewHierarchyNode here again. For now we can recommend
246247
// TODO: using custom modifiers to obscure the entire node if it's sensitive
247248
GenericViewHierarchyNode(
248-
x = visibleRect.left.toFloat(),
249-
y = visibleRect.top.toFloat(),
249+
x = visibleRect.left,
250+
y = visibleRect.top,
250251
width = node.width,
251252
height = node.height,
252253
elevation = (parent?.elevation ?: 0f),
@@ -255,7 +256,7 @@ internal object ComposeViewHierarchyNode {
255256
shouldMask = shouldMask,
256257
isImportantForContentCapture = false, // will be set by children
257258
isVisible = isVisible,
258-
visibleRect = visibleRect,
259+
visibleRect = visibleRect.toRect(),
259260
)
260261
}
261262
}

0 commit comments

Comments
 (0)