Skip to content

fix(🎨): fix crashes and dropped arguments in SkCanvas methods (#4062) - #4063

Open
Pcmhacker-piro wants to merge 7 commits into
Shopify:mainfrom
Pcmhacker-piro:fix-canvas-optional-arguments
Open

Pcmhacker-piro wants to merge 7 commits into
Shopify:mainfrom
Pcmhacker-piro:fix-canvas-optional-arguments

Conversation

@Pcmhacker-piro

@Pcmhacker-piro Pcmhacker-piro commented Sep 13, 2026

Copy link
Copy Markdown

What does this PR do?

This PR resolves several critical crash bugs, null pointer dereferences, and dropped optional arguments across SkCanvas JSI C++, Web CanvasKit (JsiSkCanvas.ts), and TypeScript declarations.

Specifically:

  • drawPatch Crash & Default BlendMode: In the native C++ layer, arguments[4] (paint) was accessed when count >= 4 instead of count >= 5, causing an out-of-bounds array access on the JSI argument list and a hard null pointer dereference (*paint) whenever paint was omitted or passed as null. Additionally, drawPatch blend mode defaults to SkBlendMode::kModulate when omitted (matching Web CanvasKit). On Web CanvasKit, withPaint safely manages temporary fallback Paint allocation and cleanup without leaking on the WASM heap.
  • drawAtlas Dropped Argument: Passing 5 arguments to drawAtlas (e.g. canvas.drawAtlas(image, sprites, transforms, paint, blendMode)) silently dropped blendMode when colors was omitted because the argument check was strictly count > 5.
  • drawImage and drawImageRect Type Inconsistency & Crashes: The TypeScript interface marked paint as required or non-nullable, but CanvasKit threw when null was passed.
  • saveLayer Null Dereference & Bounds Handling: Passing null for the paint argument resulted in an unguarded dereference. Plain-object bounds { x, y, width, height } are properly accepted.
  • drawPoints Inconsistent Exception: Passing an empty points array ([]) threw an unexpected std::invalid_argument instead of safely no-opping like Skia core.

Fixes: #4062

Changes proposed in this pull request:

Native C++ JSI Layer (packages/skia/cpp/api/JsiSkCanvas.h)

  • drawPatch Guard & Fallback: Guarded paint argument with count >= 5 && !arguments[4].isNull() && !arguments[4].isUndefined(). Provided a stack-allocated fallback SkPaint defaultPaint to prevent null pointer dereferences when paint is omitted. Default blend mode resolves to SkBlendMode::kModulate when omitted.
  • drawAtlas Argument Count Fix: Updated the blendMode check from count > 5 to count >= 5 && !arguments[4].isNull() && !arguments[4].isUndefined() so callers omitting colors retain their custom blendMode. Added null guards for colors and sampling.
  • drawImage & drawImageRect Null/Undefined Guards: Added count >= 4 && !arguments[3].isNull() && !arguments[3].isUndefined() guards before parsing paint.
  • saveLayer Guard: Added !arguments[0].isNull() check to avoid dereferencing null paints.
  • drawPoints Early Return: Replaced throwing std::invalid_argument with an early return (if (points.empty()) return;).

Web CanvasKit Layer (packages/skia/src/skia/web/JsiSkCanvas.ts)

  • CanvasKit Paint Marshalling (withPaint): Wrapped paint usage in withPaint(paint, draw) to safely allocate a temporary fallback CanvasKit Paint and delete it in a finally block to prevent leaks on the WASM heap when paint is null or omitted.
  • drawPoints Early Return: Added if (points.length === 0) return; to cleanly no-op on empty point arrays.

TypeScript Definitions (packages/skia/src/skia/types/Canvas.ts)

  • Updated method signatures to allow paint?: SkPaint | null across drawImage, drawImageRect, drawPatch, and saveLayer.

Automated Unit & E2E Tests

  • packages/skia/src/skia/__tests__/ZeroValues.spec.ts: Added unit tests verifying drawAtlas, drawPatch, and drawPoints with omitted/zero arguments.
  • packages/skia/src/renderer/__tests__/e2e/Canvas.spec.ts: Added comprehensive end-to-end offscreen snapshot tests verifying all canvas methods against live surfaces and matching CanvasKit defaults. Passed enums via ctx to ensure compatibility with remote device eval on Hermes.

How to test this:

  1. Run the test suite:
    yarn --cwd packages/skia test ZeroValues.spec.ts Canvas.spec.ts --verbose
  2. Verify TypeScript types and ESLint:
    yarn tsc
    yarn lint
  3. Run the full workspace test suite:
    yarn test

…y#4062)

- Guard drawPatch against out-of-bounds access and null dereference on paint
- Guard drawPatch blendMode and provide default based on colors presence
- Fix drawAtlas blendMode argument count check from count > 5 to count >= 5
- Allow paint to be optional/null in drawImage, drawImageRect, and saveLayer
- Allow empty points array in drawPoints without throwing
- Provide fallback Paint on Web for drawPatch and drawImageRect
- Add unit and e2e tests covering all cases
@Pcmhacker-piro
Pcmhacker-piro force-pushed the fix-canvas-optional-arguments branch from be3f74c to 0d8233e Compare September 13, 2026 20:00
@Pcmhacker-piro

Copy link
Copy Markdown
Author

I have signed the CLA!

@Pcmhacker-piro

Copy link
Copy Markdown
Author

Hi @wcandillon, @chrfalch 👋

Could you please take a look at this PR when you get a chance?

Quick Context:

This PR resolves #4062 by addressing parameter crashes and dropped arguments across SkCanvas methods (drawPatch, drawAtlas, drawImage, drawImageRect, saveLayer, and drawPoints):

  1. Native C++ JSI (JsiSkCanvas.h):

    • Fixed out-of-bounds argument indexing and null-pointer dereference on paint (*paint) in drawPatch by adding bounds checks and providing a stack-allocated fallback SkPaint.
    • Fixed drawAtlas blend mode logic from count > 5 to count >= 5 so blendMode is respected when colors is omitted.
    • Added guards for nullable/omitted paints in drawImage, drawImageRect, and saveLayer.
    • Changed drawPoints with an empty array to safely early-return rather than throwing an exception.
  2. Web CanvasKit (web/JsiSkCanvas.ts):

    • Handled CanvasKit's Emscripten requirement for const SkPaint& handles by instantiating and properly disposing a temporary Paint when omitted, preventing runtime WASM marshalling errors (undefined.Fd).
    • Added empty point array early return.
  3. TypeScript & Tests:

    • Updated Canvas typings to accept paint?: SkPaint | null.
    • Added unit and e2e test suites covering all edge cases.
    • All 98 test suites (812 tests) and yarn tsc / yarn lint pass cleanly with 0 errors.

The Shopify CLA has been signed, and a terminal demo GIF along with verification screenshots are embedded in the PR description for easy review.

Looking forward to your feedback and happy to make any adjustments! Thank you for your time.

@wcandillon
wcandillon self-requested a review September 14, 2026 11:14
@Pcmhacker-piro

Copy link
Copy Markdown
Author

Thanks @wcandillon for adding the snapshot tests and the withPaint RAII pattern! 🙌

I noticed the CI runs (build-test-ios, build-test-ios-graphite, and test-android) failed in Canvas.spec.ts with:

Property 'types_1' doesn't exist

Cause & Fix:

In remote e2e execution (RemoteSurface.drawOffscreen), the test callback is serialized via fn.toString() and sent over WebSocket to execute in Hermes on the simulator/device via eval(code). Because BlendMode and PointMode were accessed directly inside the callback body, Babel's CommonJS transform rewrote them to types_1.BlendMode and types_1.PointMode, which Hermes threw on since types_1 is not in scope on the device.

In commit f18a9aa, I updated Canvas.spec.ts to pass { BlendMode } and { PointMode } via the context argument (ctx.BlendMode.*, ctx.PointMode.*), matching the convention used across other e2e suites (e.g. LightingImageFilters.spec.tsx, Picture.spec.tsx).

All unit and e2e tests pass cleanly locally (yarn test ZeroValues.spec.ts Canvas.spec.ts) along with yarn tsc and yarn lint. Ready for another CI run whenever you get a chance!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants