Repository navigation
Conversation
`Parser::parse` scanned the object for every line: two scans for `key = value`, four for `key[] = value`, and one for each segment of a `[section]` header. A file of N distinct keys took N^2 key compares. `parse` now looks the key up once per line. `KeyIndex` scans an object of up to 32 properties, as before. For a larger object it keeps a hash table of the positions of the keys, with the hash of each key. It reads the table from the object's own property list, so it also has the keys of a JSON value that a later `[section]` adds to. A debug build checks the table against a scan for an object of up to 512 properties. `open_section` walks the segments of a header with its lookups through `KeyIndex`. It applies the rules of `E::Object::get_or_put_object`, which the parser does not call any more.
`resolve_url_auth` scanned every credential line for each line: up to four `rfind` passes to collapse a key, one pass over `install.url_auth` to skip a key that has its entry, and one pass over the registries for each empty `_auth` line. `Collapsed` keeps the last line of each `(key, option)` in a hash table when there are more than 32 credential lines, with a set of the keys of the lines so far. More than 32 registries get a set of their keys. Up to 32, each is scanned as before.
`UrlAuth::find_entry` scanned every credential key for each key of the walk of a URL. `Options::load` calls it for each registry, so many registries with many credential keys took their product in compares. `UrlAuthList` holds the keys. With more than 32 of them, it finds a key through a hash table of the first entry of each key. Up to 32, it scans them as before.
The INI parser was the last caller of `E::Object::get_or_put_object` that can run. The other caller, `Template::write_to_package_json` in `bun init`, cannot run: `exec` returns for every React template before it reaches that call. Delete the walker with `E::SetError` and `Rope::next_ref`. Delete `write_to_package_json` with `Template::is_react` and `Template::scripts`, which only it used.
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Indexed INI property and section parsing src/ast/e.rs, src/ini/lib.rs, test/js/bun/ini/ini.test.ts, test/cli/install/npmrc.test.ts |
The parser uses an index for property lookups in larger objects and updates existing properties in place. Section headers traverse or create nested objects. Tests cover duplicate keys, arrays, nested and reopened sections, key counts around the index threshold, and large inputs. |
Credential resolution and URL-auth lookup src/ini/lib.rs, src/install/npm.rs, src/install/PackageManager/PackageManagerOptions.rs, test/cli/install/npmrc.test.ts, test/js/bun/ini/ini.test.ts |
Credential resolution collapses duplicate (registry URL, option) entries to the last value and avoids repeated registry-key work. UrlAuthList indexes entries for larger lists and is used by package manager lookups. Tests cover credential precedence, registry key matching, diagnostics, and performance. |
React template initialization
| Layer / File(s) | Summary |
|---|---|
React template package.json handling src/runtime/cli/init_command.rs |
The React-specific helper path that added template names and scripts to package.json was removed, along with template script definitions. |
Suggested reviewers: jarred-sumner, dylan-conway
Priority:
Merge Risk: 
Minimal · up to e1b16
-
The change makes .npmrc and INI parsing scale linearly on large files while preserving existing results. The removed
bun initcode was unreachable, so React template output is unchanged. No merge-blocking risk was found.
Pre-merge checks: 
4
-
Full details: Title check
-
Explanation
The title names the main change: a .npmrc is read in linear time, using hash tables once a collection passes 32 keys. The changeset matches this. It adds a per-object key index in the INI parser and a hash-indexed UrlAuthList for credential lookups, and it removes the get_or_put_object path that the title does not mention.
Full details: Description check
-
Explanation
The description is complete and directly addresses the PR purpose, implementation, verification results, performance impact, limitations, and platform coverage. It does not use the template headings "What does this PR do?" and "How did you verify your code works?", but the required information is present in the Problem, Fix, Tests, and related sections.
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands.
|
Updated 5:48 PM PT - Oct 25th, 2026
✅ @robobun, your commit e1b16db906e475026f5bb9bbf1bfd1e8c0120bd7 passed in 🧪 To try this PR locally: bunx bun-pr 46932That installs a local version of the PR into your bun-46932 --bun |
|
Status: reproduced, and the fix is in this PR (#46932). How I reproduced it, on release builds of main 179664e (linux x64):
|
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline nit, I traced the rewritten key/value path in Parser::parse against the old get/put sequence for every combination of key[] vs plain key and no-existing / existing-scalar / existing-array value, and open_section against the deleted get_or_put_object (array-last-element descent, empty array, type clobber, created-object loc and flags) — the results and property order match. The deleted bun init branch is unreachable: the React templates return at init_command.rs:624 before the is_react() call. The >32 dedupe via seen in resolve_url_auth yields the same url_auth list as the scan because credentials_of_key depends only on the collapsed map and the key.
Extended reasoning...
The PR rewrites key lookup in the INI parser (src/ini/lib.rs) and .npmrc credential collapse and UrlAuth lookup (src/install/npm.rs, PackageManagerOptions.rs), adding hash-table paths past 32 entries, and deletes get_or_put_object plus a dead bun init branch. It touches registry credential resolution, which is auth-sensitive, and the fast/slow split means two code paths must agree exactly. I found no behavior difference between the scan and table paths or against the base branch, and no new unsafe code, but the parser rewrite is large enough and credential-adjacent enough that a human look is still worthwhile rather than an approval.
The rows pass under the default timeout. A row takes at most 3.3 s in a debug build with ASAN, and about 0.1 s in a release build.
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline finding, I also diffed the new open_section/per-line key path in src/ini/lib.rs against the deleted get_or_put_object and Object::put (first-match replacement, array promotion, array-last-element descent, __proto__ flags via append_property) and found them equivalent; the seen map in resolve_url_auth and the first-entry index in UrlAuthList match the scan paths they replace; and the deleted bun init branch is unreachable because React templates return at src/runtime/cli/init_command.rs:622-625.
Extended reasoning...
The change rewrites key lookup in the INI parser and .npmrc credential resolution to use hash tables past 32 entries, touching credential handling (url_auth lookup by registry URL) but keeping the same first/last-entry semantics as the scans it replaces. The refactor-equivalence checks above were done by reading the old and new code side by side; the only reported finding concerns CPU-time bounds in the new tests on slow lanes.
Problem
.npmrctakes time quadratic in its distinct keys.bun pm binbeside 32,000 lines ofkey<i>=valueuses 1,793 ms of CPU.Parser::parse(src/ini/lib.rs:371-396),resolve_url_auth(:1289) andUrlAuth::find_entry(src/install/npm.rs:621).Fix
parselooks a key up once per line.KeyIndexscans an object of up to 32 properties and keeps a hash table for a larger one.CollapsedandUrlAuthListdo the same for credential lines and credential keys.open_sectionreplacesE::Object::get_or_put_object, which is deleted with its last caller, abun initbranch that cannot run.test/js/bun/ini/ini.test.tsandtest/cli/install/npmrc.test.tsfail on main. Output matches main on 9,000 random files. Self-reviewed: 34 concerns raised, 29 addressed (see Notes).Background
E::Object, the AST node of an object literal. Its keys are a list, so a lookup is a scan.[section]can reopen an object that the JSON parser built, so the table reads the property list.bun_ast(one consumer) and an always-on map (2 allocations per file). A 5-line file makes the same allocator calls as on main.Downsides
.textgrows by 12,800 bytes (release, linux x64).Notes
All numbers: release builds of main 179664e and this branch, linux x64, unless a line says debug.
What this is. It removes accidental quadratic work, for a file that is much larger than a real one. It is not a fix for hostile input: a very large
.npmrcstill costs memory in proportion to its size, as on main. #37449 removes the same scan from the TOML parser with the same shape: a scan up to a limit, a hash table past it. The limit is 32 here (asDUP_LINEAR_MAXinsrc/parsers/json_stage2.rs) and 64 there, so one decision can settle both.bun pm binbeside a.npmrcof N lines, with HOME set to an empty directory: CPU ms of the process at N = 4,000 / 8,000 / 16,000 / 32,000 (best of 3, interleaved).npmrclineskey<i>=valuekey<i>[]=value[s<i>][a.b<i>][foo]adds keys//host<i>/:_authToken=//host<i>/:username=//host<i>/:_auth=(empty)@s<i>:registry=@s<i>:registry=and an empty_authfor another host@s<i>:registry=and_authTokenfor its host@s<i>:registry=and_authTokenfor another hostWithout a
.npmrcthe command uses about 2 ms. Nothing that ships reads a key under a[section]of a.npmrc:parse_npmrc_intoreads root keys only. The section rows are CPU that main spends to build output that only the test hook reads.The sizes of real files (best of 15, interleaved, noise about 0.2 ms)
The saving reaches 1 ms between 700 and 1,000 distinct keys.
Small files (each count is exact and repeats)
Parser::parseallocator calls (mi_heap_malloc / mi_malloc / mi_free), 5-line.npmrc: 20 / 2 / 10 → 20 / 2 / 10. 20 keys: 80 / 4 / 40 → 80 / 4 / 40. 8 lines with headers andkey[]: 30 / 5 / 13 → 30 / 5 / 13 (gdb breakpoint counters).load_npmrc_configallocator calls, 5-line.npmrcwith one token: 20 / 18 / 16 → 20 / 18 / 16. 8 lines with 3 credential lines: 32 / 26 / 26 → 32 / 26 / 26.Options::loadallocator calls (mi_malloc / mi_zalloc / mi_free), 5-line.npmrcwith one token: 21 / 1 / 12 → 21 / 1 / 12. 8 lines with 3 credential lines: 36 / 2 / 20 → 36 / 2 / 20.Parser::parseinstructions: 5 lines 32,817 → 32,392. 20 keys 64,419 → 55,157. 32 keys 106,557 → 84,233. 34 keys 115,259 → 95,046. 40 keys 141,270 → 102,882. 80 keys 386,913 → 170,678 (gdbstepi, without the vDSO clock read). For each added key from 40 to 80 keys: 6,141 → 1,695. On this branch from 80 to 160 keys: 1,683, so the cost of a key is constant.load_npmrc_configinstructions, 5-line.npmrcwith one token: 55,395 → 55,013, of which outsideParser::parse22,578 → 22,621. 8 lines with 3 credential lines: 65,389 → 64,525.Options::loadinstructions, 5-line.npmrcwith one token: 18,278 → 18,331. 8 lines with 3 credential lines: 24,872 → 24,914.KeyIndexfor each parse: 5 lines 0, 20 keys 0, 32 keys 0, 33 keys 0, 34 keys 1 (gdb breakpoint hit count)..text+12,800 bytes,bun_inisymbols +11,857 bytes,UrlAuthList::find_entry+763 bytes (sizeandnm -S).E::Object::get_or_put_object(972 bytes) is gone.Optionsgrows by oneStringHashMap<u32>, once per process.key<i>=value36 → 39 MB,_authTokenlines 66 → 70 MB.Slower than main
[s0]again on each of 256,000 lines: 19 → 22 ms of CPU. With[s39]on each line: 35 → 24 ms.Parser::parseinstructions for N[sec<i>]headers: 33 headers 76,094 → 76,047. 34 headers 78,392 → 83,935. 35 headers 80,739 → 85,140. The gap closes by about 1,140 for each header after that.Tests
bun pm binbeside a generated.npmrcand checks the CPU time of the child (proc.resourceUsage()) against a limit, astest/cli/format/format.test.tsdoes. Release: 64,000 lines, limit 1 s. These rows take 0.02 to 0.08 s on this branch and 4.9 to 29 s on main. Debug: 16,000 or 8,000 lines, limit 4 s. They take 0.3 to 1.1 s on this branch and 8 to 53 s on main. Release with ASAN and assertions (bun run build:asan, as on the x64-asan lane): 64,000 lines, limit 4 s. They take 0.2 to 0.7 s on this branch and 22 to 87 s on main.UrlAuthList: the row with registries under a path. One table for all objects, or one table at a time: the behaviour rows crash, or the row with two sections fails. The set of registry keys has no dependable row: its scan costs 3 ms at 4,000 lines.ini.test.ts: 15 cases, each with 0, 31, 32, 33 and 300 other keys in the object. 24 random files read with and without 33 more keys in each object. Two keys with the same hash. 4,000 keys and 4,000 sections. They check values and key order, and they pass on main too.npmrc.test.ts: 22 cases past the limits (last line wins across files and spellings, an empty value clears an earlier one, the diagnostic for an empty_authby each spelling of a key, a token among 4,000 credential lines, what a[section]header does to the options).iniInternals.parseon 6,600 random files, resolved credentials of 1,750 random files, the stderr ofbun pm binfor 400 random files, and theAuthorizationheader of 2,532 requests frombun installfor 390 random files. No difference.redacted-config-logs,config-precedence,public-hoist-pattern,bun-pm,bun-info,bun-publishandbun-install-pathname-trailing-slashundertest/cli/install/,test/cli/init/init.test.tsandtest/js/bun/toml/toml.test.ts.Deleted
E::Object::get_or_put_object,E::SetErrorandRope::next_refinsrc/ast/e.rs.open_sectionin the INI parser applies the same rules.Template::write_to_package_json,Template::is_reactandTemplate::scriptsinsrc/runtime/cli/init_command.rs.execreturns for each React template before the one call ofwrite_to_package_json. Remove dead code from bun init, bun create, bun_md, bun_install, and the socket and Postgres code #44102 deletes the same code and more.Self-review: not taken
test/harness.ts: 11 test files read the CPU time of a child inline.E::Rope: the functions that split a header have no tests yet.Options: it costs about 40 ns more for each tarball URL of a small file.KeyIndexbuilds on the second lookup: more state to save one build.Not in this change
Rope::appendwalks to the tail for each segment of a header. The cap of 512 segments bounds it.expand_env_varsis quadratic in the length of one quoted value.pnpm.rscopy_into_rootandyarn_berry.rscallE::Object::putfor each entry of a migration..npmrcline//h.example/:_authtoken=(an option in the wrong case with an empty value) makesbun pm binpanic withint cast: TryFromIntError(NegOverflow)on main and on this branch.no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/cli/install/npmrc.test.ts