Skip to content

[memory-leak] SKStreamAsset leaked by ToHarfBuzzBlob copy branch (streams without a native memory base) #4813

Description

@github-actions

🤖 AI-generated finding. Produced by the memory-leak-fixer agentic workflow + skill. Focus area 0 — Undisposed native handle. Empirically proven with a red→green managed test (details below).

The leak

BlobExtensions.ToHarfBuzzBlob(this SKStreamAsset asset) has two paths:

  • memory-base branchnew Blob(memoryBase, size, MemoryMode.ReadOnly, () => asset.Dispose()) — correctly disposes the owned asset when the Blob is released.
  • copy (else) branchnew Blob(ptr, size, MemoryMode.ReadOnly, () => Marshal.FreeCoTaskMem(ptr)) — only frees the copied buffer and never disposes asset.

So for any stream that reports no native memory base (e.g. SKManagedStream), the owned native SKStreamAsset is leaked — its handle is only reclaimed by the finalizer, non-deterministically and late.

Retention/ownership path

  • source/SkiaSharp.HarfBuzz/SkiaSharp.HarfBuzz/BlobExtensions.cs:21asset.GetMemoryBase() returns IntPtr.Zero for managed/non-memory streams → copy branch.
  • source/SkiaSharp.HarfBuzz/SkiaSharp.HarfBuzz/BlobExtensions.cs:28-30 — copies into ptr, but the release delegate frees only ptr; asset (owned) is never disposed.
  • Reached in the wild from source/SkiaSharp.HarfBuzz/SkiaSharp.HarfBuzz/SKShaper.cs:20Typeface.OpenStream(out index).ToHarfBuzzBlob(), where SKTypeface.OpenStream (binding/SkiaSharp/SKTypeface.cs:350) mints an owned SKStreamAsset.

Evidence (red→green)

Added tests/Tests/SkiaSharp/BlobExtensionsTest.cs: builds a SKManagedStream (no memory base → copy branch), converts it with ToHarfBuzzBlob(), then disposes the Blob and asserts the asset is disposed.

  • Before fix: Assert.True(asset.IsDisposed) FAILS (Actual: False) — asset leaked.
  • After fix: PASSES. Neighbouring *Shaper* (16) and HBBlobTest (4) tests remain green.

Scope note

  • Framework bug (not a caller footgun): the two branches of the same helper have asymmetric ownership handling.
  • Empirically proven via managed disposal test, not just static reasoning.
  • Managed-C# fix, in source/** only. No public signature changed — ABI stable.

Labels: tenet/performance, perf/memory-leak.

Generated by Fixer - Memory Leak · opus48 · 531.1 AIC · ⌖ 27.8 AIC · ⊞ 12K ·

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    partner/agentic-workflowsIssues and PRs created by SkiaSharp agentic workflows.perf/memory-leakUnbounded memory growth: leaked native handles or undisposed objects. Implies tenet/performance.tenet/performancePerformance related issues

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions