wave17: 562/240 — vim/qemu/tcl/nodejs/bun/httpd-0002/systemd/emacs

This commit is contained in:
russell@unturf.com 2026-03-27 20:28:18 -04:00
parent cce7ec653a
commit dde5ec97fb
13 changed files with 887 additions and 4 deletions

View file

@ -0,0 +1,68 @@
# bun-0001 — CWE-407 in dirInfoUncached bin_folders dedup
**Severity:** MEDIUM
**File:** `src/resolver/resolver.zig`
**Symbol:** `dirInfoUncached` — `for (bin_folders.constSlice()) |existing_folder|`
## Defect
During module resolution, `dirInfoCachedMaybeLog` walks the directory tree from
the target path up to the filesystem root, calling `dirInfoUncached` for each
uncached directory level (up to D levels).
Inside `dirInfoUncached`, when a `.bin` directory is found under `node_modules`,
the path is deduplicated against a shared `bin_folders` array using a linear
scan before appending:
```zig
// Called inside while (queue_slice.len > 0) loop — D iterations
fn dirInfoUncached(...) {
// ...
for (bin_folders.constSlice()) |existing_folder| { // O(B) scan
if (strings.eql(existing_folder, bin_path)) {
break :append_bin_dir;
}
}
bin_folders.append(...); // B grows up to D
}
```
`bin_folders` is a shared array that accumulates all `.bin` folder paths found
across the entire directory tree traversal. Each call to `dirInfoUncached`
performs an O(B) scan where B grows with each new `.bin` directory found.
With D directory levels each contributing one `.bin` entry, total dedup cost is
**O(D²)**.
In a monorepo with deep nesting (D=50 directories, each with `node_modules/.bin`),
this generates ~1,250 comparisons per `require()` call instead of ~50.
## Fix
Replace `bin_folders` linear scan with a `std.StringHashMap` or
`std.BufSet` for O(1) membership:
```zig
var bin_folders_set = std.StringHashMap(void).init(allocator);
defer bin_folders_set.deinit();
// In the dedup check:
const gop = try bin_folders_set.getOrPut(bin_path);
if (!gop.found_existing) {
bin_folders.append(stored_path) catch {};
}
```
## Complexity
| Scenario | Before | After |
|---|---|---|
| D=50 nested node_modules dirs | O(D²) = ~1,250 ops | O(D) = 50 ops |
| D=200 deep monorepo | O(D²) = ~20,000 ops | O(D) = 200 ops |
| Ratio at D=200 | ~100× overhead | 1× |
## Source location
`src/resolver/resolver.zig` — `fn dirInfoUncached`, in the
`if (r.care_about_bin_folder)` / `append_bin_dir` blocks (~lines 4024–4073),
called from within the `while (queue_slice.len > 0)` loop in
`dirInfoCachedMaybeLog` (~line 2956).

View file

@ -0,0 +1,164 @@
package unit;
import java.util.*;
/**
* Unit test for bun-0001: CWE-407 in dirInfoUncached bin_folders dedup.
*
* bun-0001 (MEDIUM):
* File: src/resolver/resolver.zig ~lines 4041-4073
* Symbol: dirInfoUncached — for (bin_folders.constSlice()) |existing_folder|
* Defect: Module resolution walks D directory levels. For each level that
* contains a node_modules/.bin directory, the path is deduplicated against a
* shared bin_folders array using a linear scan: O(B) per call where B grows.
* With D levels each contributing one entry: O(D²) total dedup cost.
* Fix: Use a HashSet for O(1) membership: O(D) total.
*
* Modeled here in Java:
* Zig slice linear scan ≡ List<String>.contains() (defective)
* Zig StringHashMap ≡ java.util.Set<String> (fixed)
* Comparison counts tracked at the membership-test site.
*
* Expected at D=200 directory levels:
* defective comparisons ~ D*(D-1)/2 ≈ 19,900
* fixed comparisons ~ D = 200
* ratio > 50×
*/
public class BunBinFoldersTest {
// =========================================================================
// Model: dirInfoUncached bin_folders dedup
// =========================================================================
/**
* Defective: bin_folders is a plain List; dedup is a linear scan per entry.
*
* Simulates: dirInfoCachedMaybeLog calls dirInfoUncached D times, once per
* directory level. Each call finds a unique .bin path and deduplicates it
* against the growing bin_folders accumulator.
*/
static long deduplicateDefective(int D) {
long comparisons = 0;
List<String> binFolders = new ArrayList<>();
for (int level = 0; level < D; level++) {
String binPath = "/project/level_" + level + "/node_modules/.bin";
// for (bin_folders.constSlice()) |existing| — O(binFolders.size())
boolean found = false;
for (String existing : binFolders) {
comparisons++;
if (existing.equals(binPath)) { found = true; break; }
}
if (!found) {
binFolders.add(binPath);
}
}
return comparisons;
}
/**
* Fixed: HashSet companion for O(1) membership; same accumulated list.
*/
static long deduplicateFixed(int D) {
long comparisons = 0;
List<String> binFolders = new ArrayList<>();
Set<String> binFoldersSet = new HashSet<>();
for (int level = 0; level < D; level++) {
String binPath = "/project/level_" + level + "/node_modules/.bin";
// binFoldersSet.contains(binPath) — O(1)
comparisons++;
if (!binFoldersSet.contains(binPath)) {
binFoldersSet.add(binPath);
binFolders.add(binPath);
}
}
return comparisons;
}
// =========================================================================
// Tests
// =========================================================================
/** Test 1 — Correctness: both produce the same accumulated bin_folders list. */
static void testCorrectnessMatch() {
int D = 20;
List<String> defList = new ArrayList<>();
List<String> fixList = new ArrayList<>();
Set<String> fixSet = new HashSet<>();
for (int level = 0; level < D; level++) {
String binPath = "/project/level_" + level + "/node_modules/.bin";
if (!defList.contains(binPath)) defList.add(binPath);
if (!fixSet.contains(binPath)) { fixSet.add(binPath); fixList.add(binPath); }
}
assert defList.size() == D
: "expected " + D + " bin folders; got " + defList.size();
assert new HashSet<>(defList).equals(fixSet)
: "defective and fixed bin_folders sets differ";
System.out.println("PASS testCorrectnessMatch");
}
/** Test 2 — bun-0001: defective is O(D²), fixed is O(D). */
static void testRatioAtScale() {
int D = 200;
long defComp = deduplicateDefective(D);
long fixComp = deduplicateFixed(D);
double ratio = (double) defComp / fixComp;
// Defective: all D paths are unique; accumulator grows 0, 1, ..., D-1.
// Sum of scans: 0+1+...+(D-1) = D*(D-1)/2.
long expectedDef = (long) D * (D - 1) / 2;
assert defComp == expectedDef
: "defective comparisons should be D*(D-1)/2=" + expectedDef
+ "; got " + defComp;
// Fixed: exactly D probes (one Set.contains per level).
assert fixComp == D
: "fixed comparisons should be D=" + D + "; got " + fixComp;
assert ratio >= 50.0
: "ratio should be >=50x at D=200; got " + ratio;
System.out.printf(
"PASS testRatioAtScale (defective=%d, fixed=%d, ratio=%.1fx)%n",
defComp, fixComp, ratio);
}
/**
* Test 3 — Duplicate bin paths (same .bin dir encountered multiple times):
* dedup correctly suppresses re-adding the same path in both implementations.
*/
static void testWithDuplicateBinPaths() {
int D = 40; // 40 levels but only 20 unique .bin dirs (every 2 levels share one)
List<String> defList = new ArrayList<>();
Set<String> fixSet = new HashSet<>();
List<String> fixList = new ArrayList<>();
for (int level = 0; level < D; level++) {
String binPath = "/project/level_" + (level / 2) + "/node_modules/.bin";
if (!defList.contains(binPath)) defList.add(binPath);
if (!fixSet.contains(binPath)) { fixSet.add(binPath); fixList.add(binPath); }
}
int expectedUnique = D / 2;
assert defList.size() == expectedUnique
: "expected " + expectedUnique + " unique bin dirs; got " + defList.size();
assert new HashSet<>(defList).equals(fixSet)
: "duplicate-input bin_folders sets differ";
System.out.println("PASS testWithDuplicateBinPaths");
}
// =========================================================================
public static void main(String[] args) {
testCorrectnessMatch();
testRatioAtScale();
testWithDuplicateBinPaths();
System.out.println("All bun-0001 tests passed.");
}
}