undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
46
defects/webpack/patch/webpack-0001-hmr-outdated-set.patch
Normal file
46
defects/webpack/patch/webpack-0001-hmr-outdated-set.patch
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
diff --git a/lib/hmr/JavascriptHotModuleReplacement.runtime.js b/lib/hmr/JavascriptHotModuleReplacement.runtime.js
|
||||
index xxxxxxx..xxxxxxx 100644
|
||||
--- a/lib/hmr/JavascriptHotModuleReplacement.runtime.js
|
||||
+++ b/lib/hmr/JavascriptHotModuleReplacement.runtime.js
|
||||
@@ -26,7 +26,8 @@ module.exports = function () {
|
||||
function getAffectedModuleEffects(updateModuleId) {
|
||||
- var outdatedModules = [updateModuleId];
|
||||
+ // CWE-407 fix: use Set for O(1) membership checks in BFS traversal.
|
||||
+ var outdatedModulesSet = new Set([updateModuleId]);
|
||||
+ var outdatedModules = [updateModuleId]; // kept for ordered result only
|
||||
var outdatedDependencies = {};
|
||||
|
||||
var queue = outdatedModules.map(function (id) {
|
||||
@@ -70,9 +71,9 @@ module.exports = function () {
|
||||
for (var i = 0; i < module.parents.length; i++) {
|
||||
var parentId = module.parents[i];
|
||||
var parent = $moduleCache$[parentId];
|
||||
if (!parent) continue;
|
||||
if (parent.hot._declinedDependencies[moduleId]) {
|
||||
return { type: "declined", chain: chain.concat([parentId]),
|
||||
moduleId: moduleId, parentId: parentId };
|
||||
}
|
||||
- if (outdatedModules.indexOf(parentId) !== -1) continue;
|
||||
+ if (outdatedModulesSet.has(parentId)) continue;
|
||||
if (parent.hot._acceptedDependencies[moduleId]) {
|
||||
if (!outdatedDependencies[parentId])
|
||||
outdatedDependencies[parentId] = [];
|
||||
@@ -81,6 +82,7 @@ module.exports = function () {
|
||||
}
|
||||
delete outdatedDependencies[parentId];
|
||||
outdatedModules.push(parentId);
|
||||
+ outdatedModulesSet.add(parentId);
|
||||
queue.push({ chain: chain.concat([parentId]), id: parentId });
|
||||
}
|
||||
}
|
||||
@@ -96,11 +98,12 @@ module.exports = function () {
|
||||
}
|
||||
|
||||
function addAllToSet(a, b) {
|
||||
for (var i = 0; i < b.length; i++) {
|
||||
var item = b[i];
|
||||
- if (a.indexOf(item) === -1) a.push(item);
|
||||
+ if (!a._set) a._set = new Set(a);
|
||||
+ if (!a._set.has(item)) { a.push(item); a._set.add(item); }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
diff --git a/lib/hmr/HotModuleReplacement.runtime.js b/lib/hmr/HotModuleReplacement.runtime.js
|
||||
index xxxxxxx..xxxxxxx 100644
|
||||
--- a/lib/hmr/HotModuleReplacement.runtime.js
|
||||
+++ b/lib/hmr/HotModuleReplacement.runtime.js
|
||||
@@ -42,7 +42,8 @@ module.exports = function (parentHot) {
|
||||
hot = {
|
||||
// ...
|
||||
};
|
||||
module.parents = currentParents;
|
||||
- module.children = [];
|
||||
+ // CWE-407 fix: use Set-backed arrays for O(1) dedup in require() hot path.
|
||||
+ module.children = [];
|
||||
+ module._childrenSet = new Set();
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
@@ -57,10 +57,10 @@ module.exports = function (parentHot) {
|
||||
if (me.hot.active) {
|
||||
if (installedModules[request]) {
|
||||
var parents = installedModules[request].parents;
|
||||
- if (parents.indexOf(moduleId) === -1) {
|
||||
+ if (!installedModules[request]._parentsSet) installedModules[request]._parentsSet = new Set(parents);
|
||||
+ if (!installedModules[request]._parentsSet.has(moduleId)) {
|
||||
parents.push(moduleId);
|
||||
+ installedModules[request]._parentsSet.add(moduleId);
|
||||
}
|
||||
} else {
|
||||
currentParents = [moduleId];
|
||||
currentChildModule = request;
|
||||
}
|
||||
- if (me.children.indexOf(request) === -1) {
|
||||
+ if (!me._childrenSet.has(request)) {
|
||||
me.children.push(request);
|
||||
+ me._childrenSet.add(request);
|
||||
}
|
||||
}
|
||||
374
defects/webpack/unit/WebpackHmrTest.java
Normal file
374
defects/webpack/unit/WebpackHmrTest.java
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
package unit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Unit test for webpack-0001/0002/0003: CWE-407 in webpack HMR runtime.
|
||||
*
|
||||
* webpack-0001 (MEDIUM-HIGH):
|
||||
* File: lib/hmr/JavascriptHotModuleReplacement.runtime.js:74
|
||||
* Symbol: getAffectedModuleEffects — `outdatedModules.indexOf(parentId)`
|
||||
* Defect: BFS traversal over a module dependency graph uses an Array for the
|
||||
* outdatedModules visited set. For each queued module, for each parent,
|
||||
* indexOf scans the entire array: O(M) per check, O(M²) total.
|
||||
* Fix: Shadow with a Set; check becomes Set.has(), O(1).
|
||||
*
|
||||
* webpack-0002 (MEDIUM):
|
||||
* File: lib/hmr/JavascriptHotModuleReplacement.runtime.js:101
|
||||
* Symbol: addAllToSet — `a.indexOf(item)`
|
||||
* Defect: Result-merging helper deduplicates by scanning the accumulator
|
||||
* array on every insertion: O(N) per item, O(N²) total for N items.
|
||||
* Fix: Maintain a companion Set on the accumulator array; check is O(1).
|
||||
*
|
||||
* webpack-0003 (MEDIUM):
|
||||
* File: lib/hmr/HotModuleReplacement.runtime.js:60,67
|
||||
* Symbol: createRequire — `parents.indexOf(moduleId)`, `me.children.indexOf(request)`
|
||||
* Defect: Every require() call in the HMR hot path deduplicates parents and
|
||||
* children using indexOf on plain arrays: O(P) and O(C) per call.
|
||||
* Fix: Attach Set companions (_parentsSet, _childrenSet); O(1) per check.
|
||||
*
|
||||
* Modeled here in Java:
|
||||
* JS Array + indexOf ≡ List<Integer> + contains/indexOf (defective)
|
||||
* JS Set + has ≡ java.util.Set + contains (fixed)
|
||||
* Comparison counts tracked at the membership-test site.
|
||||
*
|
||||
* Expected at M=200:
|
||||
* defective BFS comparisons ≈ M² = 40 000
|
||||
* fixed BFS comparisons ≈ M = 200
|
||||
* ratio > 10×
|
||||
*/
|
||||
public class WebpackHmrTest {
|
||||
|
||||
// =========================================================================
|
||||
// webpack-0001 model: BFS with outdatedModules as Array vs Set
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Defective BFS: outdatedModules is a plain List.
|
||||
* For each (module, parent) pair we call list.contains(), which is O(size).
|
||||
* comparisons counts every element examined during indexOf scans.
|
||||
*/
|
||||
static class DefectiveBfs {
|
||||
long comparisons = 0;
|
||||
|
||||
/**
|
||||
* Simulate getAffectedModuleEffects: process a chain of M modules where
|
||||
* each module has one parent, and the parent is always "not yet in
|
||||
* outdatedModules" (worst-case: no early-exit, full scan every time).
|
||||
*
|
||||
* Graph: 0 → 1 → 2 → ... → M-1 (child → parent direction).
|
||||
* BFS starts from module 0, propagates up.
|
||||
*/
|
||||
List<Integer> run(int M) {
|
||||
List<Integer> outdatedModules = new ArrayList<>();
|
||||
outdatedModules.add(0);
|
||||
|
||||
// Queue: (moduleId). We process each and check its single parent.
|
||||
Queue<Integer> queue = new ArrayDeque<>();
|
||||
queue.add(0);
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
int moduleId = queue.poll();
|
||||
int parentId = moduleId + 1; // parent in linear chain
|
||||
if (parentId >= M) continue; // no parent beyond chain end
|
||||
|
||||
// outdatedModules.indexOf(parentId) — O(current size) scan
|
||||
boolean found = false;
|
||||
for (int x : outdatedModules) {
|
||||
comparisons++;
|
||||
if (x == parentId) { found = true; break; }
|
||||
}
|
||||
if (found) continue;
|
||||
|
||||
outdatedModules.add(parentId);
|
||||
queue.add(parentId);
|
||||
}
|
||||
return outdatedModules;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed BFS: shadow outdatedModules with a Set for O(1) membership.
|
||||
* comparisons counts one hash-probe per Set.contains() call.
|
||||
*/
|
||||
static class FixedBfs {
|
||||
long comparisons = 0;
|
||||
|
||||
List<Integer> run(int M) {
|
||||
List<Integer> outdatedModules = new ArrayList<>();
|
||||
Set<Integer> outdatedSet = new HashSet<>();
|
||||
outdatedModules.add(0);
|
||||
outdatedSet.add(0);
|
||||
|
||||
Queue<Integer> queue = new ArrayDeque<>();
|
||||
queue.add(0);
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
int moduleId = queue.poll();
|
||||
int parentId = moduleId + 1;
|
||||
if (parentId >= M) continue;
|
||||
|
||||
// Set.contains(parentId) — O(1)
|
||||
comparisons++;
|
||||
if (outdatedSet.contains(parentId)) continue;
|
||||
|
||||
outdatedModules.add(parentId);
|
||||
outdatedSet.add(parentId);
|
||||
queue.add(parentId);
|
||||
}
|
||||
return outdatedModules;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// webpack-0002 model: addAllToSet with Array indexOf vs Set companion
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Defective addAllToSet: accumulator is a plain List; every insertion
|
||||
* scans the list to deduplicate.
|
||||
* comparisons counts every element examined during indexOf scans.
|
||||
*/
|
||||
static long addAllToSetDefective(List<Integer> a, List<Integer> b,
|
||||
long[] comparisons) {
|
||||
for (int item : b) {
|
||||
// a.indexOf(item) — O(a.size()) scan
|
||||
boolean found = false;
|
||||
for (int x : a) {
|
||||
comparisons[0]++;
|
||||
if (x == item) { found = true; break; }
|
||||
}
|
||||
if (!found) a.add(item);
|
||||
}
|
||||
return comparisons[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed addAllToSet: companion Set maintained alongside the List.
|
||||
* comparisons counts one hash-probe per Set.contains() call.
|
||||
*/
|
||||
static long addAllToSetFixed(List<Integer> a, Set<Integer> aSet,
|
||||
List<Integer> b, long[] comparisons) {
|
||||
for (int item : b) {
|
||||
comparisons[0]++; // O(1) Set.contains
|
||||
if (!aSet.contains(item)) {
|
||||
a.add(item);
|
||||
aSet.add(item);
|
||||
}
|
||||
}
|
||||
return comparisons[0];
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// webpack-0003 model: parents/children dedup in require() hot path
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Defective require(): parents and children deduped via indexOf on plain arrays.
|
||||
* Simulates N require() calls where each call checks both parents and children.
|
||||
* comparisons counts every element examined during indexOf scans.
|
||||
*/
|
||||
static long simulateRequireDefective(int N) {
|
||||
long comparisons = 0;
|
||||
List<Integer> parents = new ArrayList<>();
|
||||
List<Integer> children = new ArrayList<>();
|
||||
|
||||
for (int moduleId = 0; moduleId < N; moduleId++) {
|
||||
int request = moduleId + 1000; // distinct child module IDs
|
||||
|
||||
// parents.indexOf(moduleId) — O(parents.size())
|
||||
boolean foundParent = false;
|
||||
for (int x : parents) {
|
||||
comparisons++;
|
||||
if (x == moduleId) { foundParent = true; break; }
|
||||
}
|
||||
if (!foundParent) parents.add(moduleId);
|
||||
|
||||
// me.children.indexOf(request) — O(children.size())
|
||||
boolean foundChild = false;
|
||||
for (int x : children) {
|
||||
comparisons++;
|
||||
if (x == request) { foundChild = true; break; }
|
||||
}
|
||||
if (!foundChild) children.add(request);
|
||||
}
|
||||
return comparisons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed require(): Set companions for O(1) dedup.
|
||||
* comparisons counts one hash-probe per check.
|
||||
*/
|
||||
static long simulateRequireFixed(int N) {
|
||||
long comparisons = 0;
|
||||
List<Integer> parents = new ArrayList<>();
|
||||
Set<Integer> parentsSet = new HashSet<>();
|
||||
List<Integer> children = new ArrayList<>();
|
||||
Set<Integer> childrenSet = new HashSet<>();
|
||||
|
||||
for (int moduleId = 0; moduleId < N; moduleId++) {
|
||||
int request = moduleId + 1000;
|
||||
|
||||
// parentsSet.contains(moduleId) — O(1)
|
||||
comparisons++;
|
||||
if (!parentsSet.contains(moduleId)) {
|
||||
parents.add(moduleId);
|
||||
parentsSet.add(moduleId);
|
||||
}
|
||||
|
||||
// childrenSet.contains(request) — O(1)
|
||||
comparisons++;
|
||||
if (!childrenSet.contains(request)) {
|
||||
children.add(request);
|
||||
childrenSet.add(request);
|
||||
}
|
||||
}
|
||||
return comparisons;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Tests
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Test 1 — Correctness: defective and fixed BFS produce identical module sets.
|
||||
*/
|
||||
static void testBfsCorrectnessMatch() {
|
||||
int M = 50;
|
||||
DefectiveBfs def = new DefectiveBfs();
|
||||
FixedBfs fix = new FixedBfs();
|
||||
|
||||
List<Integer> defResult = def.run(M);
|
||||
List<Integer> fixResult = fix.run(M);
|
||||
|
||||
Set<Integer> defSet = new HashSet<>(defResult);
|
||||
Set<Integer> fixSet = new HashSet<>(fixResult);
|
||||
|
||||
assert defSet.equals(fixSet)
|
||||
: "BFS output mismatch: defective=" + defSet + " fixed=" + fixSet;
|
||||
assert defResult.size() == M
|
||||
: "expected " + M + " outdated modules; got " + defResult.size();
|
||||
|
||||
System.out.println("PASS testBfsCorrectnessMatch");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 2 — webpack-0001: ratio of defective vs fixed BFS comparisons > 10x at M=200.
|
||||
*
|
||||
* Defective: each of M modules triggers a scan of the growing outdatedModules
|
||||
* list (size 0..M-1), summing to M*(M-1)/2 comparisons ~ O(M²).
|
||||
* Fixed: each module triggers exactly 1 Set probe, summing to M ~ O(M).
|
||||
*/
|
||||
static void testBfsRatioAtScale() {
|
||||
int M = 200;
|
||||
|
||||
DefectiveBfs def = new DefectiveBfs();
|
||||
FixedBfs fix = new FixedBfs();
|
||||
def.run(M);
|
||||
fix.run(M);
|
||||
|
||||
long defComp = def.comparisons;
|
||||
long fixComp = fix.comparisons;
|
||||
double ratio = (double) defComp / fixComp;
|
||||
|
||||
// Defective: sum 0+1+...+(M-1) = M*(M-1)/2
|
||||
long expectedDef = (long) M * (M - 1) / 2;
|
||||
assert defComp == expectedDef
|
||||
: "defective BFS comparisons should be M*(M-1)/2=" + expectedDef
|
||||
+ "; got " + defComp;
|
||||
|
||||
// Fixed: M-1 probes (one per module that has a parent to check;
|
||||
// the last module in the chain has no parent, so no probe is issued)
|
||||
assert fixComp == M - 1
|
||||
: "fixed BFS comparisons should be M-1=" + (M - 1) + "; got " + fixComp;
|
||||
|
||||
assert ratio > 10.0
|
||||
: "ratio should be >10x at M=200; got " + ratio;
|
||||
|
||||
System.out.printf(
|
||||
"PASS testBfsRatioAtScale (defective=%d, fixed=%d, ratio=%.1fx)%n",
|
||||
defComp, fixComp, ratio);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 3 — webpack-0002: addAllToSet defective is O(N²), fixed is O(N).
|
||||
*
|
||||
* Merge N unique items into an accumulator. Defective scans the accumulator
|
||||
* before each insert (size 0..N-1), costing N*(N-1)/2. Fixed costs N probes.
|
||||
*/
|
||||
static void testAddAllToSetRatio() {
|
||||
int N = 200;
|
||||
|
||||
// Build source list of N unique items
|
||||
List<Integer> source = new ArrayList<>();
|
||||
for (int i = 0; i < N; i++) source.add(i);
|
||||
|
||||
// Defective
|
||||
long[] defComp = {0};
|
||||
List<Integer> defAcc = new ArrayList<>();
|
||||
addAllToSetDefective(defAcc, source, defComp);
|
||||
|
||||
// Fixed
|
||||
long[] fixComp = {0};
|
||||
List<Integer> fixAcc = new ArrayList<>();
|
||||
Set<Integer> fixSet = new HashSet<>();
|
||||
addAllToSetFixed(fixAcc, fixSet, source, fixComp);
|
||||
|
||||
assert defAcc.size() == N && fixAcc.size() == N
|
||||
: "both accumulators should have " + N + " items";
|
||||
assert new HashSet<>(defAcc).equals(new HashSet<>(fixAcc))
|
||||
: "accumulator contents differ";
|
||||
|
||||
double ratio = (double) defComp[0] / fixComp[0];
|
||||
assert ratio > 10.0
|
||||
: "addAllToSet ratio should be >10x at N=200; got " + ratio;
|
||||
|
||||
System.out.printf(
|
||||
"PASS testAddAllToSetRatio (defective=%d, fixed=%d, ratio=%.1fx)%n",
|
||||
defComp[0], fixComp[0], ratio);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 4 — webpack-0003: require() hot path defective is O(N²), fixed is O(N).
|
||||
*
|
||||
* N require() calls, each adding a unique moduleId to parents and a unique
|
||||
* request to children. Defective: scans grow 0..N-1 for each; fixed: O(1) probes.
|
||||
*/
|
||||
static void testRequireHotPathRatio() {
|
||||
int N = 200;
|
||||
|
||||
long defComp = simulateRequireDefective(N);
|
||||
long fixComp = simulateRequireFixed(N);
|
||||
double ratio = (double) defComp / fixComp;
|
||||
|
||||
// Defective: two indexOf scans per call, sizes grow 0..N-1
|
||||
// Sum for parents: 0+1+...+(N-1) = N*(N-1)/2; same for children.
|
||||
long expectedDef = (long) N * (N - 1); // two arrays: 2 × N*(N-1)/2
|
||||
assert defComp == expectedDef
|
||||
: "defective require comparisons should be N*(N-1)=" + expectedDef
|
||||
+ "; got " + defComp;
|
||||
|
||||
// Fixed: exactly 2 probes per call (one per Set)
|
||||
long expectedFix = 2L * N;
|
||||
assert fixComp == expectedFix
|
||||
: "fixed require comparisons should be 2*N=" + expectedFix
|
||||
+ "; got " + fixComp;
|
||||
|
||||
assert ratio > 10.0
|
||||
: "require() ratio should be >10x at N=200; got " + ratio;
|
||||
|
||||
System.out.printf(
|
||||
"PASS testRequireHotPathRatio (defective=%d, fixed=%d, ratio=%.1fx)%n",
|
||||
defComp, fixComp, ratio);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
|
||||
public static void main(String[] args) {
|
||||
testBfsCorrectnessMatch();
|
||||
testBfsRatioAtScale();
|
||||
testAddAllToSetRatio();
|
||||
testRequireHotPathRatio();
|
||||
System.out.println("All webpack HMR tests passed.");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue