Skip to content

fix: copy decodeAudioData input before async decoding - #1432

Open
choi138 wants to merge 1 commit into
software-mansion:mainfrom
choi138:fix/decoder-buffer-ownership
Open

choi138 wants to merge 1 commit into
software-mansion:mainfrom
choi138:fix/decoder-buffer-ownership

Conversation

@choi138

@choi138 choi138 commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

Closes #1431

⚠️ Breaking changes ⚠️

  • None

Introduced changes

  • AudioDecoderHostObject::decodeWithMemoryBlock now copies the encoded bytes into the async decode job instead of capturing the raw ArrayBuffer data pointer. Before this, the worker could read freed memory if the JS buffer was collected while decoding.
  • Uses the same capture as AudioFileUtilsHostObject::probeDuration (bytes = std::vector<uint8_t>(data, data + size)).
  • Cost: one extra copy of the encoded input, held for the duration of the decode.

Checklist

  • Linked relevant issue
  • Updated relevant documentation (no API change)
  • Added/Conducted relevant tests
  • Performed self-review of the code
  • Updated Web Audio API coverage (n/a)
  • Added support for web (n/a, native only)
  • Updated old arch android spec file (n/a)

Tests: the test CMake excludes HostObjects/, so there's no unit test here. I ran the following instead:

  • clang++ -std=c++20 -fsyntax-only on the changed file, with React Native 0.86.3 JSI headers: passes.
  • clang-format --dry-run --Werror and cpplint with the flags from scripts/cpplint.sh: clean.
  • The Hermes + AddressSanitizer probe from the issue: the raw-pointer capture reports heap-use-after-free; the copied capture passes.
  • Not yet run: a device build with this exact commit. An equivalent patch on 0.13.2 is going through our app's review.

decodeWithMemoryBlock captured the raw ArrayBuffer data pointer in the
async decode job. JSI only guarantees that pointer during the host call,
so the worker could read freed memory once the buffer was collected.

Copy the bytes into the job, matching AudioFileUtilsHostObject::probeDuration.
@github-actions

Copy link
Copy Markdown

WPT non-regression comparison

PASS — no regressions · 0 improved section(s) · overall 3430 → 3430 (0)

Unchanged sections (28)
Spec section Base pass Head pass Delta
Processing model 1 1 0
Other 56 56 0
AnalyserNode 138 138 0
AudioBuffer 142 142 0
AudioBufferSourceNode 220 220 0
AudioContext 58 58 0
AudioNode 261 261 0
AudioParam 680 680 0
BiquadFilterNode 275 275 0
ChannelMergerNode 30 30 0
ChannelSplitterNode 7 7 0
ConstantSourceNode 64 64 0
ConvolverNode 179 179 0
DelayNode 127 127 0
DestinationNode 0 0 0
DynamicsCompressorNode 4 4 0
GainNode 15 15 0
IIRFilterNode 87 87 0
MediaElementAudioSourceNode 0 0 0
MediaStreamAudioDestinationNode 1 1 0
MediaStreamAudioSourceNode 0 0 0
OfflineAudioContext 35 35 0
OscillatorNode 104 104 0
PannerNode 727 727 0
PeriodicWave 33 33 0
ScriptProcessorNode 0 0 0
StereoPannerNode 104 104 0
WaveShaperNode 82 82 0

Baseline: 13e66ef2e294f305205500558840a9637f043605 · Candidate: 206738420ad30563033f9d8069a4d76a3bae9a0c

Workflow run · this comment is updated on every push.

This branch has not been deployed

No deployments
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.

decodeAudioData(ArrayBuffer) can read freed memory: async decode job captures raw ArrayBuffer pointer

1 participant