fix(otel-thread-ctx): don't assert a record is valid when growing it - #392
Merged
Merged
Conversation
Append's reallocate path asserted that the record it had just copied was
valid:
memcpy(new_rec.get(), self->record_, ...);
...
assert(new_rec->valid == 1);
invalidate() sets that byte to 0, and appending afterwards is supported —
there is a test for it — so the assert fires on any append too large to fit
in place:
Assertion `new_rec->valid == 1' failed.
Aborted (exit 134)
This is not debug-only. NDEBUG is never defined for this addon, so assert()
is live in Release too; both configurations abort. Reproduced through the
public API on Linux with invalidate() followed by a 200-byte attribute.
Only the reallocate path is affected: an append that fits the current
capacity is written in place and never copies the header. A fresh record has
36 bytes of attrs_data capacity, so an attribute over ~34 bytes on a fresh
record is enough. The existing 'appendAttributes after invalidate' test
appends 6 bytes, takes the in-place path, and so never reached the copy —
right behaviour, wrong size.
Assert what the check was actually for — that the memcpy carried the header
across intact — by capturing the source's valid byte first and comparing
against that. Still catches a genuine copy bug, such as shortening the memcpy
so it no longer covers the header, and is correct whether the record is valid
or not.
The regression test forks, since the failure is an abort that would otherwise
take the whole mocha run down. Verified it bites: against the pre-fix binding
it reports signal=SIGABRT with the assertion above, and passes after.
Reported by @nsavoire on #391.
szegedi
requested review from
IlyasShabi,
nsavoire and
r1viollet
as code owners
August 11, 2026 08:41
Overall package sizeSelf size: 2.49 MB Dependency sizes| name | version | self size | total size | |------|---------|-----------|------------| | pprof-format | 2.3.1 | 504.33 kB | 504.33 kB | | source-map | 0.8.0 | 185.66 kB | 185.66 kB | | node-gyp-build | 4.8.4 | 13.86 kB | 13.86 kB |🤖 This report was automatically generated by heaviest-objects-in-the-universe |
nsavoire
approved these changes
Aug 11, 2026
This was referenced Aug 11, 2026
Merged
szegedi
added a commit
that referenced
this pull request
Aug 11, 2026
…392) Append's reallocate path asserted that the record it had just copied was valid: memcpy(new_rec.get(), self->record_, ...); ... assert(new_rec->valid == 1); invalidate() sets that byte to 0, and appending afterwards is supported — there is a test for it — so the assert fires on any append too large to fit in place: Assertion `new_rec->valid == 1' failed. Aborted (exit 134) This is not debug-only. NDEBUG is never defined for this addon, so assert() is live in Release too; both configurations abort. Reproduced through the public API on Linux with invalidate() followed by a 200-byte attribute. Only the reallocate path is affected: an append that fits the current capacity is written in place and never copies the header. A fresh record has 36 bytes of attrs_data capacity, so an attribute over ~34 bytes on a fresh record is enough. The existing 'appendAttributes after invalidate' test appends 6 bytes, takes the in-place path, and so never reached the copy — right behaviour, wrong size. Assert what the check was actually for — that the memcpy carried the header across intact — by capturing the source's valid byte first and comparing against that. Still catches a genuine copy bug, such as shortening the memcpy so it no longer covers the header, and is correct whether the record is valid or not. The regression test forks, since the failure is an abort that would otherwise take the whole mocha run down. Verified it bites: against the pre-fix binding it reports signal=SIGABRT with the assertion above, and passes after. Reported by @nsavoire on #391. (cherry picked from commit cfa8cd1)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reported by @nsavoire on #391. Confirmed, and more serious than "fires in debug builds".
The bug
Append's reallocate path asserts that the record it just copied is valid:invalidate()sets that byte to 0, and appending afterwards is supported — there is a test asserting exactly that — so the memcpy faithfully copies a 0 and the assert fires.It is not debug-only.
NDEBUGis never defined for this addon (nothing indd_pprof.target.mkdefines it), soassert()is live in Release as well. Both configurations abort. Reproduced through the public API on Linux:Only the reallocate path is affected — an append that fits the current capacity is written in place and never copies the header. A fresh record has 36 bytes of attrs_data capacity (
64 - sizeof(header)), so an attribute over ~34 bytes on a fresh record is enough to trigger it.Why the existing test missed it
appendAttributes after invalidate mutates attrs_data but leaves valid=0appends'late'— 6 bytes. That fits the initial capacity, takes the in-place path, and never reaches the copy. Right behaviour under test, wrong size.The fix
Assert what the check was actually for — that the memcpy carried the header across intact — rather than a value the API legitimately changes:
Still catches a genuine copy bug (shortening the memcpy so it no longer covers the header) and is correct whether the record is valid or not.
Verification
The regression test forks, since the failure is an abort that would otherwise take the whole mocha run down — same pattern as the teardown test in this file. Built the pre-fix C++ against it to confirm it bites:
exited with code=null signal=SIGABRT+ the assertionnpm testnpm testtest:js-asanclang-format -Werrorclean,gts check0 errors.Follow-up
The same code is vendored in
custom-labelsjs/addon.cppand needs the same fix; happy to port once this lands.Worth considering separately:
NDEBUGnot being defined means everyassert()in these bindings is live in production. That is arguably fine as a fail-fast policy, but it should be a deliberate choice — this one was a wrong assertion shipping as a release-build abort.