164 lines
6.3 KiB
Java
164 lines
6.3 KiB
Java
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.");
|
||
}
|
||
}
|