[iOS] SIGABRT in makeImageSnapshot when canvas size is -1 — MTLTextureDescriptor width (uint64)-1 assertion

Author: woosanggyuCreated Aug 25, 2026Updated Aug 25, 2026

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:

cpp
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

Source: Shopify/react-native-skia