fix(): Multiple SkCanvas methods crash or drop arguments for optional/null parameters (drawPatch, drawAtlas, drawImageRect)

Author: Pcmhacker-piroCreated Sep 13, 2026Updated Sep 13, 2026

Description

When using the imperative SkCanvas drawing API (canvas.drawPatch, canvas.drawAtlas, canvas.drawImageRect, and canvas.drawPoints), several methods crash with native segmentation faults (SIGSEGV), throw unhandled exceptions, or silently drop passed parameters when optional or nullable arguments are omitted or passed as null.

Specifically:

  1. canvas.drawPatch crashes with SIGSEGV (null pointer dereference) on Native, and throws on Web:

    • The TypeScript API declares drawPatch(cubics, colors?, texs?, mode?, paint?).
    • On native (JsiSkCanvas.h), auto paint = count >= 4 ? JsiSkPaint::fromValue(runtime, arguments[4]) : nullptr; checks count >= 4 to access arguments[4]. When 4 arguments are passed (count == 4), index 4 is read out-of-bounds on the JSI argument array.
    • When paint is omitted or passed as null (count < 5), paint is nullptr. The code then calls _canvas->drawPatch(..., *paint), which unconditionally dereferences *paint, immediately crashing the app with a segmentation fault (SIGSEGV).
    • If mode is omitted (count < 4), arguments[3].asNumber() throws an unhandled JS exception because arguments[3] is undefined.
    • On Web (JsiSkCanvas.ts), CanvasKit's underlying C++ binding takes const SkPaint&, so Emscripten attempts to read paint.Fd. When paint is omitted or null, it throws TypeError: Cannot read properties of undefined (reading 'Fd').
  2. canvas.drawAtlas silently ignores blendMode when 5 arguments are passed (when colors is omitted):

    • The TypeScript signature is drawAtlas(atlas, srcs, dsts, paint, blendMode?, colors?, sampling?).
    • In packages/skia/cpp/api/JsiSkCanvas.h:
      cpp
      auto blendMode = count > 5 && !arguments[4].isUndefined()
                           ? static_cast<SkBlendMode>(arguments[4].asNumber())
                           : SkBlendMode::kDstOver;
    • blendMode is at index 4 (the 5th argument). If a developer calls canvas.drawAtlas(atlas, srcs, dsts, paint, BlendMode.SrcOver), count is 5. Because 5 > 5 evaluates to false, the passed blendMode is completely ignored and falls back to SkBlendMode::kDstOver. It is only respected if at least 6 arguments (i.e. colors) are also passed.
  3. canvas.drawImageRect throws when optional paint is omitted or passed as null:

    • The TypeScript signature declares paint?: SkPaint | null.
    • JsiSkCanvas.h unconditionally calls auto paint = JsiSkPaint::fromValue(runtime, arguments[3]); without checking count >= 4 or whether arguments[3] is null/undefined. Passing null or omitting paint throws Value is null, expected an Object.
    • On Web, passing null/undefined also fails in Emscripten's toWireType on undefined.Fd.
  4. canvas.drawPoints throws on empty points array instead of no-oping:

    • JsiSkCanvas.h unconditionally throws std::invalid_argument("Points array must not be empty") if points.empty(). In graphic routines (e.g. dynamic/particle rendering or empty buffers), drawing an empty array is standard behavior and a no-op across Skia and the Skia recorder, but in JsiSkCanvas it aborts execution.

React Native Skia Version

2.1.1+ (main branch)

React Native Version

0.81.5

Using New Architecture

  • Enabled

Steps to Reproduce

  1. Call canvas.drawPatch(cubics, colors) without passing the 5th argument paint:
    • On iOS / Android, the process terminates immediately with EXC_BAD_ACCESS / SIGSEGV due to *paint where paint == nullptr.
    • On Web, it throws TypeError: Cannot read properties of undefined (reading 'Fd').
  2. Call canvas.drawAtlas(atlas, rects, transforms, paint, BlendMode.SrcOver):
    • On native, count == 5. The condition count > 5 evaluates to false, so BlendMode.SrcOver is dropped and kDstOver is drawn instead.
  3. Call canvas.drawImageRect(image, src, dest, null):
    • Throws Value is null, expected an Object.
  4. Call canvas.drawPoints(PointMode.Points, [], paint):
    • Throws Points array must not be empty.

Snack, Code Example, Screenshot, or Link to Repository

Minimal reproduction code:

typescript
// 1. drawPatch crash
const surface = Skia.Surface.Make(100, 100);
const canvas = surface.getCanvas();
const cubics = Array.from({ length: 12 }, (_, i) => ({ x: i, y: i }));
const colors = [
  Skia.Color("red"),
  Skia.Color("green"),
  Skia.Color("blue"),
  Skia.Color("white"),
];

// Crashes with SIGSEGV on iOS/Android due to null pointer dereference (*paint)
canvas.drawPatch(cubics, colors);

// 2. drawAtlas dropped blendMode
const img = surface.makeImageSnapshot();
const paint = Skia.Paint();
// BlendMode.SrcOver is silently dropped and replaced with DstOver because count == 5
canvas.drawAtlas(
  img,
  [Skia.XYWHRect(0, 0, 8, 8)],
  [Skia.RSXform(1, 0, 0, 0)],
  paint,
  BlendMode.SrcOver
);

// 3. drawImageRect with null paint throws
canvas.drawImageRect(img, Skia.XYWHRect(0, 0, 8, 8), Skia.XYWHRect(0, 0, 8, 8), null);

// 4. drawPoints empty array throws
canvas.drawPoints(PointMode.Points, [], paint);

Proposed Fix

  • In JsiSkCanvas.h:
    • Guard drawPatch parameters with count >= 4 and count >= 5, provide default SkPaint fallback when paint is nullptr instead of dereferencing *paint, and default blendMode to colors.empty() ? SkBlendMode::kSrcOver : SkBlendMode::kDstOver.
    • In drawAtlas, update count > 5 to count >= 5 && !arguments[4].isNull() && !arguments[4].isUndefined().
    • In drawImage and drawImageRect, guard arguments[3] with count >= 4 && !arguments[3].isNull() && !arguments[3].isUndefined().
    • In drawPoints, return early if (points.empty()) return;.
  • In JsiSkCanvas.ts (Web):
    • Provide fallback CanvasKit.Paint in drawPatch and drawImageRect when paint is omitted/null to satisfy Emscripten's wire-type requirement.
    • Return early if (points.length === 0) return; in drawPoints.
  • In Canvas.ts (Types):
    • Allow paint?: SkPaint | null across drawImage, drawImageRect, drawPatch, and saveLayer.

Source: Shopify/react-native-skia