wave17: 562/240 — vim/qemu/tcl/nodejs/bun/httpd-0002/systemd/emacs
This commit is contained in:
parent
cce7ec653a
commit
dde5ec97fb
13 changed files with 887 additions and 4 deletions
173
defects/nodejs/unit/NodejsResolvePathsTest.java
Normal file
173
defects/nodejs/unit/NodejsResolvePathsTest.java
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
package unit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Unit test for nodejs-0001: CWE-407 in Module._resolveFilename paths dedup.
|
||||
*
|
||||
* nodejs-0001 (MEDIUM):
|
||||
* File: lib/internal/modules/cjs/loader.js ~line 1408
|
||||
* Symbol: Module._resolveFilename — ArrayPrototypeIncludes(paths, lookupPaths[j])
|
||||
* Defect: When require.resolve(id, { paths: [...] }) is called with P explicit
|
||||
* paths, each generating L lookup paths, the dedup accumulator is a plain Array.
|
||||
* Every ArrayPrototypeIncludes() scans the entire growing accumulator:
|
||||
* O(P*L) per check × P*L checks = O(P²×L²) total.
|
||||
* Fix: Use a Set for the accumulator dedup: O(1) per check, O(P*L) total.
|
||||
*
|
||||
* Modeled here in Java:
|
||||
* JS Array + includes ≡ List<String> + contains (defective)
|
||||
* JS Set + has ≡ java.util.Set + contains (fixed)
|
||||
* Comparison counts tracked at the membership-test site.
|
||||
*
|
||||
* Expected at P=50, L=10:
|
||||
* defective comparisons ~ P²×L²/2 ≈ 125,000
|
||||
* fixed comparisons ~ P×L = 500
|
||||
* ratio > 50×
|
||||
*/
|
||||
public class NodejsResolvePathsTest {
|
||||
|
||||
// =========================================================================
|
||||
// Model: _resolveFilename paths dedup
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Defective: accumulator is a plain List; every dedup check is a linear scan.
|
||||
*
|
||||
* Simulates: for each of P input paths, generate L synthetic lookup paths.
|
||||
* Dedup into accumulator with List.contains() (O(size) per call).
|
||||
* All P*L generated paths are unique (worst-case: no early exit).
|
||||
*/
|
||||
static long deduplicateDefective(int P, int L) {
|
||||
long comparisons = 0;
|
||||
List<String> paths = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < P; i++) {
|
||||
// Simulate _resolveLookupPaths: L unique paths per input
|
||||
for (int j = 0; j < L; j++) {
|
||||
String candidate = "path_" + i + "_" + j;
|
||||
// ArrayPrototypeIncludes(paths, candidate) — O(paths.size()) scan
|
||||
boolean found = false;
|
||||
for (String existing : paths) {
|
||||
comparisons++;
|
||||
if (existing.equals(candidate)) { found = true; break; }
|
||||
}
|
||||
if (!found) {
|
||||
paths.add(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
return comparisons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed: companion Set for O(1) membership; same result, far fewer comparisons.
|
||||
*/
|
||||
static long deduplicateFixed(int P, int L) {
|
||||
long comparisons = 0;
|
||||
List<String> paths = new ArrayList<>();
|
||||
Set<String> pathsSet = new HashSet<>();
|
||||
|
||||
for (int i = 0; i < P; i++) {
|
||||
for (int j = 0; j < L; j++) {
|
||||
String candidate = "path_" + i + "_" + j;
|
||||
// Set.contains(candidate) — O(1)
|
||||
comparisons++;
|
||||
if (!pathsSet.contains(candidate)) {
|
||||
pathsSet.add(candidate);
|
||||
paths.add(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
return comparisons;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Tests
|
||||
// =========================================================================
|
||||
|
||||
/** Test 1 — Correctness: both produce the same deduplicated path list. */
|
||||
static void testCorrectnessMatch() {
|
||||
int P = 10, L = 5;
|
||||
List<String> defPaths = new ArrayList<>();
|
||||
List<String> fixPaths = new ArrayList<>();
|
||||
Set<String> fixSet = new HashSet<>();
|
||||
|
||||
// Rebuild lists without counting (just for correctness check)
|
||||
for (int i = 0; i < P; i++) {
|
||||
for (int j = 0; j < L; j++) {
|
||||
String s = "path_" + i + "_" + j;
|
||||
if (!defPaths.contains(s)) defPaths.add(s);
|
||||
if (!fixSet.contains(s)) { fixSet.add(s); fixPaths.add(s); }
|
||||
}
|
||||
}
|
||||
|
||||
assert defPaths.size() == P * L
|
||||
: "expected " + (P * L) + " unique paths; got " + defPaths.size();
|
||||
assert new HashSet<>(defPaths).equals(fixSet)
|
||||
: "defective and fixed path sets differ";
|
||||
|
||||
System.out.println("PASS testCorrectnessMatch");
|
||||
}
|
||||
|
||||
/** Test 2 — nodejs-0001: defective is O(P²×L²), fixed is O(P×L). */
|
||||
static void testRatioAtScale() {
|
||||
int P = 50;
|
||||
int L = 10;
|
||||
|
||||
long defComp = deduplicateDefective(P, L);
|
||||
long fixComp = deduplicateFixed(P, L);
|
||||
double ratio = (double) defComp / fixComp;
|
||||
|
||||
// Defective: accumulator grows 0, 1, 2, ..., (P*L - 1) — sum = P*L*(P*L-1)/2
|
||||
long total = (long) P * L;
|
||||
long expectedDef = total * (total - 1) / 2;
|
||||
assert defComp == expectedDef
|
||||
: "defective comparisons should be P*L*(P*L-1)/2=" + expectedDef
|
||||
+ "; got " + defComp;
|
||||
|
||||
// Fixed: exactly P*L probes (one Set.contains per candidate)
|
||||
assert fixComp == total
|
||||
: "fixed comparisons should be P*L=" + total + "; got " + fixComp;
|
||||
|
||||
assert ratio >= 50.0
|
||||
: "ratio should be >=50x at P=50, L=10; got " + ratio;
|
||||
|
||||
System.out.printf(
|
||||
"PASS testRatioAtScale (defective=%d, fixed=%d, ratio=%.1fx)%n",
|
||||
defComp, fixComp, ratio);
|
||||
}
|
||||
|
||||
/** Test 3 — Duplicate input paths: dedup is idempotent (same output either way). */
|
||||
static void testWithDuplicateInputPaths() {
|
||||
// P paths but only P/2 unique base dirs — half of the lookupPaths overlap
|
||||
int P = 20, L = 5;
|
||||
List<String> defPaths = new ArrayList<>();
|
||||
Set<String> fixSet = new HashSet<>();
|
||||
List<String> fixPaths = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < P; i++) {
|
||||
int base = i % (P / 2); // duplicate base dirs
|
||||
for (int j = 0; j < L; j++) {
|
||||
String s = "path_" + base + "_" + j;
|
||||
if (!defPaths.contains(s)) defPaths.add(s);
|
||||
if (!fixSet.contains(s)) { fixSet.add(s); fixPaths.add(s); }
|
||||
}
|
||||
}
|
||||
|
||||
assert new HashSet<>(defPaths).equals(fixSet)
|
||||
: "duplicate-input path sets differ";
|
||||
assert defPaths.size() == (P / 2) * L
|
||||
: "expected " + ((P / 2) * L) + " unique paths with duplicates; got " + defPaths.size();
|
||||
|
||||
System.out.println("PASS testWithDuplicateInputPaths");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
|
||||
public static void main(String[] args) {
|
||||
testCorrectnessMatch();
|
||||
testRatioAtScale();
|
||||
testWithDuplicateInputPaths();
|
||||
System.out.println("All nodejs-0001 tests passed.");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue