You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Filed from review on #431. This is deliberately a design question rather than another point fix — it is the sixth memory finding on requireUnambiguousJSON, and the pattern is more informative than any one of them.
The immediate finding
Reported by Codex on internal/sbom/strict.go:194: the name-byte bound is checked afterReadToken has already unquoted and retained the name, so a single name larger than the bound is retained once before being refused.
Measured: a 200 MiB member name retains 200 MiB — 1.00× input.
Two reasons, both recorded in the code beside the bound:
No library option exists.jsontext.Options offers AllowDuplicateNames, AllowInvalidUTF8, and formatting options. Nothing bounds a token's size.
The only remaining route is prohibited. Sizing the name before the decoder reads it means scanning raw bytes for the string's closing quote with escape handling — mirroring the library's tokenizer, which this project's delegation rule names explicitly as an anti-pattern.
The residual is also the floor, not a multiplier: the name is bounded by an input already capped at 256 MiB and already resident. That is qualitatively different from what the earlier fixes removed.
The pattern, which is the actual point
Six findings against one ~40-line function across #431's review rounds:
#
Shape
Worst case
Fix
1
One very wide object
6.7× input
bound the widest object
2
Nested objects, each under that bound
2.5 GiB from 411 MiB
bound the aggregate across open objects
3
Aggregate names within bounds, but long
900 MiB from 194 MiB
bound name bytes, not count
4
Large values, which no name bound reaches
447 MiB from 150 MiB
read in place (*bytes.Buffer)
5
Byte bound counted indentation (my regression)
6.4× over-count
measure the token, not the span
6
One name larger than the bound
1.00× input
open — see above
Two observations worth carrying:
The two fixes that closed whole classes were replacements, not tunings. (4) and (5) removed an approximation — a generic reader instead of the in-place path, offset arithmetic instead of the token. The three that added or adjusted bounds each left a next shape.
Every doc comment written about this function's memory was wrong until measured. "Constant memory", then a bound on the wrong quantity, then an estimate off by 40%. The current comment states measured figures only.
The question to decide
Whether a separate hand-instrumented preflight is the right shape at all. It exists because ADR-0039 wants exactly two guarantees (duplicate names, invalid UTF-8) without the other behavior changes that decoding through encoding/json/v2 would bring — case-sensitive field matching in particular, which the ADR explicitly does not claim.
Alternatives worth costing:
Keep it, accept the residual. Document that preflight peak is bounded by input size. Cheapest, and arguably already true.
Decode through json/v2 for real, and validate what the case-sensitivity change actually does to SPDX and CycloneDX fixtures. Removes the hand-instrumentation entirely; needs the behavior study ADR-0039 declined to do.
Ask upstream for a token-size option. A jsontext.MaxTokenSize-style option would close (6) properly and is plausibly useful beyond Bomly.
Context
internal/sbom/strict.go — the function, its bounds, and the recorded reason for the open residual.
Filed from review on #431. This is deliberately a design question rather than another point fix — it is the sixth memory finding on
requireUnambiguousJSON, and the pattern is more informative than any one of them.The immediate finding
Reported by Codex on
internal/sbom/strict.go:194: the name-byte bound is checked afterReadTokenhas already unquoted and retained the name, so a single name larger than the bound is retained once before being refused.Measured: a 200 MiB member name retains 200 MiB — 1.00× input.
Why it was not fixed in #431
Two reasons, both recorded in the code beside the bound:
jsontext.OptionsoffersAllowDuplicateNames,AllowInvalidUTF8, and formatting options. Nothing bounds a token's size.The residual is also the floor, not a multiplier: the name is bounded by an input already capped at 256 MiB and already resident. That is qualitatively different from what the earlier fixes removed.
The pattern, which is the actual point
Six findings against one ~40-line function across #431's review rounds:
*bytes.Buffer)Two observations worth carrying:
The question to decide
Whether a separate hand-instrumented preflight is the right shape at all. It exists because ADR-0039 wants exactly two guarantees (duplicate names, invalid UTF-8) without the other behavior changes that decoding through
encoding/json/v2would bring — case-sensitive field matching in particular, which the ADR explicitly does not claim.Alternatives worth costing:
jsontext.MaxTokenSize-style option would close (6) properly and is plausibly useful beyond Bomly.Context
internal/sbom/strict.go— the function, its bounds, and the recorded reason for the open residual.TestADocumentTooLargeToCheckIsRefused,TestNestedObjectsCannotAccumulatePastTheBound,TestSiblingObjectsDoNotAccumulate,TestLongMemberNamesAreBoundedByTheirBytes,TestClosedObjectsReleaseTheirNameBytes,TestOrdinaryNamesDoNotApproachTheByteBound,TestPrettyPrintingDoesNotCountAgainstTheByteBound,TestStrictPreflightReadsInPlace.