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.contains() (defective) * Zig StringHashMap ≡ java.util.Set (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 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 binFolders = new ArrayList<>(); Set 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 defList = new ArrayList<>(); List fixList = new ArrayList<>(); Set 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 defList = new ArrayList<>(); Set fixSet = new HashSet<>(); List 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."); } }