Description
We are seeing a production iOS crash (SIGABRT) in RNSkView::makeImageSnapshot. When the Skia view's canvas size is -1 (view not yet laid out, or being detached), the size is passed without validation into the offscreen surface creation, and Metal aborts on texture descriptor validation:
failed assertion `Texture Descriptor Validation
MTLTextureDescriptor has width (18446744073709551615) greater than the maximum allowed size of 32768.
MTLTextureDescriptor has height (18446744073709551615) greater than the maximum allowed size of 32768.'
18446744073709551615 is (uint64)-1, i.e. the unlaid-out canvas size cast to unsigned.
Stack trace (symbolicated, production)
Metal -[MTLTextureDescriptorInternal validateWithDevice:] (failed assertion → abort)
AGXMetalG18P -[AGXTexture initWithDevice:desc:isSuballocDisabled:]
NOVA OffscreenRenderContext::OffscreenRenderContext (MetalContext.h:33)
NOVA MetalContext::MakeOffscreen (MetalContext.h:51)
NOVA RNSkia::RNSkOffscreenCanvasProvider::RNSkOffscreenCanvasProvider (RNSkView.h:77)
NOVA RNSkia::RNSkView::makeImageSnapshot (RNSkView.h:207)
NOVA lambda in RNSkJsiViewApi.h:241 (makeImageSnapshotAsync main-queue block)
libdispatch _dispatch_main_queue_drain
Analysis
- JS calls
canvasRef.makeImageSnapshotAsync(). RNSkJsiViewApi dispatches the actual snapshot to the main queue (RNSkJsiViewApi.h:241).
- Between the JS call and the main-queue block executing, the view's layout state can change (view detached / not yet laid out) — so JS-side guards (checking onLayout completion / mount state before calling) cannot fully prevent this; it is a native-side TOCTOU.
RNSkView::makeImageSnapshot (cpp/rnskia/RNSkView.h) constructs RNSkOffscreenCanvasProvider with _canvasProvider->getWidth()/getHeight() without any size validation, so -1 flows straight into makeOffscreenSurface.
No deterministic repro (production crash on app-store build; the window is a race between snapshot dispatch and view teardown/layout). Not found among existing issues.
Suggested fix
Early-return nullptr when the canvas size is not positive. nullptr already flows into the existing "Failed to make snapshot from view." promise rejection, which callers can handle gracefully:
sk_sp<SkImage> makeImageSnapshot(SkRect *bounds) {
if (_canvasProvider->getWidth() <= 0 || _canvasProvider->getHeight() <= 0) {
return nullptr;
}
...
We are running exactly this as a local patch on 2.10.1 and it degrades the crash into the already-handled rejection path. Happy to open a PR if useful.
Environment
- @shopify/react-native-skia: 2.10.1
- react-native: 0.86.0 (new arch), Expo SDK 57
- iOS (observed on iPhone / AGXMetalG18P), production build
Description
We are seeing a production iOS crash (SIGABRT) in
RNSkView::makeImageSnapshot. When the Skia view's canvas size is-1(view not yet laid out, or being detached), the size is passed without validation into the offscreen surface creation, and Metal aborts on texture descriptor validation:18446744073709551615is(uint64)-1, i.e. the unlaid-out canvas size cast to unsigned.Stack trace (symbolicated, production)
Analysis
canvasRef.makeImageSnapshotAsync().RNSkJsiViewApidispatches the actual snapshot to the main queue (RNSkJsiViewApi.h:241).RNSkView::makeImageSnapshot(cpp/rnskia/RNSkView.h) constructsRNSkOffscreenCanvasProviderwith_canvasProvider->getWidth()/getHeight()without any size validation, so-1flows straight intomakeOffscreenSurface.No deterministic repro (production crash on app-store build; the window is a race between snapshot dispatch and view teardown/layout). Not found among existing issues.
Suggested fix
Early-return
nullptrwhen the canvas size is not positive.nullptralready flows into the existing"Failed to make snapshot from view."promise rejection, which callers can handle gracefully:We are running exactly this as a local patch on 2.10.1 and it degrades the crash into the already-handled rejection path. Happy to open a PR if useful.
Environment