Skip to content

Commit adb1852

Browse files
HayesGordonHayesGordon
andcommitted
fix(android): crash on artboard resizing (#11176) e3c08f895c
* fix(android): crash on artboard resizing * test: add test to verify the crash Co-authored-by: Gordon <pggordonhayes@gmail.com>
1 parent 09fc7f3 commit adb1852

File tree

3 files changed

+75
-2
lines changed

3 files changed

+75
-2
lines changed

.rive_head

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
05630acdb1b896f9736456ec1450436bed2850b5
1+
e3c08f895ca85a58f1584fce1949a1d315ce952f

kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveArtboardRendererTest.kt

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.junit.Test
1111
import org.junit.runner.RunWith
1212
import java.util.concurrent.CountDownLatch
1313
import java.util.concurrent.TimeUnit
14+
import java.util.concurrent.atomic.AtomicReference
1415
import java.util.concurrent.locks.ReentrantLock
1516

1617
@RunWith(AndroidJUnit4::class)
@@ -255,4 +256,72 @@ class RiveArtboardRendererTest {
255256

256257
drawThread.join(timeout)
257258
}
259+
260+
/**
261+
* Tests that the renderer can be safely deleted while resizeArtboard() is executing.
262+
* The fix adds a hasCppObject check at the start of resizeArtboard() to prevent
263+
* accessing disposed C++ objects when accessing width/height properties.
264+
*/
265+
@Test
266+
fun deleteRendererDuringResizeArtboard() {
267+
val timeout = 1000L
268+
// Latch to signal we've entered resizeArtboard()
269+
val duringResizeLatch = CountDownLatch(1)
270+
// Latch to block until we have deleted the renderer
271+
val afterDeleteLatch = CountDownLatch(1)
272+
273+
val controller = RiveFileController()
274+
controller.fit = Fit.LAYOUT // This sets requireArtboardResize to true
275+
controller.isActive = true
276+
277+
// Create a custom renderer that overrides resizeArtboard() to add blocking,
278+
// simulating the race condition where the renderer is deleted during resize
279+
val latchingRenderer = object : RiveArtboardRenderer(controller = controller) {
280+
override fun resizeArtboard() {
281+
// Signal we've entered resizeArtboard()
282+
duringResizeLatch.countDown()
283+
284+
// Block until the renderer is deleted - this simulates the race where
285+
// the renderer could be deleted while resizeArtboard() is executing
286+
afterDeleteLatch.await(timeout, TimeUnit.MILLISECONDS)
287+
288+
// Call the parent's resizeArtboard() which will check hasCppObject
289+
// (the fix) and return early if disposed, preventing a crash
290+
super.resizeArtboard()
291+
}
292+
}
293+
294+
latchingRenderer.make()
295+
296+
// Capture any exception thrown in the background thread
297+
val exceptionRef = AtomicReference<Throwable>()
298+
299+
// Start draw() in a background thread
300+
val drawThread = Thread {
301+
try {
302+
latchingRenderer.draw()
303+
} catch (e: Throwable) {
304+
exceptionRef.set(e)
305+
}
306+
}
307+
drawThread.start()
308+
309+
// Wait for resizeArtboard() to be entered
310+
duringResizeLatch.await(timeout, TimeUnit.MILLISECONDS)
311+
312+
// Delete the renderer while resizeArtboard() is blocked
313+
latchingRenderer.delete()
314+
315+
// Let resizeArtboard() continue - the hasCppObject check should prevent a crash
316+
afterDeleteLatch.countDown()
317+
318+
drawThread.join(timeout)
319+
320+
// Verify no exception was thrown - the fix should prevent the crash
321+
val exception = exceptionRef.get()
322+
assert(exception == null) {
323+
"Expected no exception when renderer is deleted during resizeArtboard(). " +
324+
"Got: ${exception?.javaClass?.simpleName}: ${exception?.message}"
325+
}
326+
}
258327
}

kotlin/src/main/java/app/rive/runtime/kotlin/renderers/RiveArtboardRenderer.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package app.rive.runtime.kotlin.renderers
22

3+
import androidx.annotation.VisibleForTesting
34
import androidx.annotation.WorkerThread
45
import app.rive.runtime.kotlin.controllers.RiveFileController
56
import app.rive.runtime.kotlin.core.Fit
@@ -31,7 +32,10 @@ open class RiveArtboardRenderer(
3132
}
3233

3334
@WorkerThread
34-
private fun resizeArtboard() {
35+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
36+
open fun resizeArtboard() {
37+
if (!hasCppObject) return
38+
3539
if (fit == Fit.LAYOUT) {
3640
val newWidth = width / scaleFactor
3741
val newHeight = height / scaleFactor

0 commit comments

Comments
 (0)