Shopify / Shopify/react-native-skia
fix(🎨): Multiple SkCanvas methods crash or drop arguments for optional/null parameters (drawPatch, drawAtlas, drawImageRect)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 8.6k
- Forks
- 647
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 35
Description
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:
-
canvas.drawPatchcrashes withSIGSEGV(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;checkscount >= 4to accessarguments[4]. When 4 arguments are passed (count == 4), index 4 is read out-of-bounds on the JSI argument array. - When
paintis omitted or passed asnull(count < 5),paintisnullptr. The code then calls_canvas->drawPatch(..., *paint), which unconditionally dereferences*paint, immediately crashing the app with a segmentation fault (SIGSEGV). - If
modeis omitted (count < 4),arguments[3].asNumber()throws an unhandled JS exception becausearguments[3]is undefined. - On Web (
JsiSkCanvas.ts), CanvasKit's underlying C++ binding takesconst SkPaint&, so Emscripten attempts to readpaint.Fd. Whenpaintis omitted or null, it throwsTypeError: Cannot read properties of undefined (reading 'Fd').
- The TypeScript API declares
-
canvas.drawAtlassilently ignoresblendModewhen 5 arguments are passed (whencolorsis omitted):- The TypeScript signature is
drawAtlas(atlas, srcs, dsts, paint, blendMode?, colors?, sampling?). - In
packages/skia/cpp/api/JsiSkCanvas.h:auto blendMode = count > 5 && !arguments[4].isUndefined() ? static_cast<SkBlendMode>(arguments[4].asNumber()) : SkBlendMode::kDstOver; blendModeis at index 4 (the 5th argument). If a developer callscanvas.drawAtlas(atlas, srcs, dsts, paint, BlendMode.SrcOver),countis 5. Because5 > 5evaluates tofalse, the passedblendModeis completely ignored and falls back toSkBlendMode::kDstOver. It is only respected if at least 6 arguments (i.e.colors) are also passed.
- The TypeScript signature is
-
canvas.drawImageRectthrows when optionalpaintis omitted or passed asnull:- The TypeScript signature declares
paint?: SkPaint | null. JsiSkCanvas.hunconditionally callsauto paint = JsiSkPaint::fromValue(runtime, arguments[3]);without checkingcount >= 4or whetherarguments[3]is null/undefined. Passingnullor omittingpaintthrowsValue is null, expected an Object.- On Web, passing
null/undefinedalso fails in Emscripten'stoWireTypeonundefined.Fd.
- The TypeScript signature declares
-
canvas.drawPointsthrows on empty points array instead of no-oping:JsiSkCanvas.hunconditionally throwsstd::invalid_argument("Points array must not be empty")ifpoints.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 inJsiSkCanvasit aborts execution.
React Native Skia Version
2.1.1+ (main branch)
React Native Version
0.81.5
Using New Architecture
- Enabled
Steps to Reproduce
- Call
canvas.drawPatch(cubics, colors)without passing the 5th argumentpaint:- On iOS / Android, the process terminates immediately with
EXC_BAD_ACCESS/SIGSEGVdue to*paintwherepaint == nullptr. - On Web, it throws
TypeError: Cannot read properties of undefined (reading 'Fd').
- On iOS / Android, the process terminates immediately with
- Call
canvas.drawAtlas(atlas, rects, transforms, paint, BlendMode.SrcOver):- On native,
count == 5. The conditioncount > 5evaluates to false, soBlendMode.SrcOveris dropped andkDstOveris drawn instead.
- On native,
- Call
canvas.drawImageRect(image, src, dest, null):- Throws
Value is null, expected an Object.
- Throws
- Call
canvas.drawPoints(PointMode.Points, [], paint):- Throws
Points array must not be empty.
- Throws
Snack, Code Example, Screenshot, or Link to Repository
Minimal reproduction code:
// 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
drawPatchparameters withcount >= 4andcount >= 5, provide defaultSkPaintfallback whenpaintisnullptrinstead of dereferencing*paint, and defaultblendModetocolors.empty() ? SkBlendMode::kSrcOver : SkBlendMode::kDstOver. - In
drawAtlas, updatecount > 5tocount >= 5 && !arguments[4].isNull() && !arguments[4].isUndefined(). - In
drawImageanddrawImageRect, guardarguments[3]withcount >= 4 && !arguments[3].isNull() && !arguments[3].isUndefined(). - In
drawPoints, return earlyif (points.empty()) return;.
- Guard
- In
JsiSkCanvas.ts(Web):- Provide fallback
CanvasKit.PaintindrawPatchanddrawImageRectwhenpaintis omitted/null to satisfy Emscripten's wire-type requirement. - Return early
if (points.length === 0) return;indrawPoints.
- Provide fallback
- In
Canvas.ts(Types):- Allow
paint?: SkPaint | nullacrossdrawImage,drawImageRect,drawPatch, andsaveLayer.
- Allow
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the affected entry points in packages/skia/cpp/api/JsiSkCanvas.h, JsiSkCanvas.ts, and Canvas.ts, then run the four reproduction calls from the issue on native and Web. Done means omitted or null paints no longer crash, drawAtlas preserves its fifth-argument blend mode, and empty drawPoints calls no-op consistently.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, react-native, typescript
- Domain
- api, computer-graphics, mobile-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 56/100