undefect. CWE-407 — 92 sites, 42 ecosystems
B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections. Squash of 94 local commits onto remote master.
This commit is contained in:
parent
0a580b313d
commit
db29a08762
1311 changed files with 371202 additions and 1188 deletions
|
|
@ -0,0 +1,61 @@
|
|||
From: agent-blackops <blackops@unturf.com>
|
||||
Date: Thu, 26 Mar 2026 00:00:00 +0000
|
||||
Subject: [PATCH] compiler/backend: replace spilled_consts ZoneVector with ZoneUnorderedSet
|
||||
|
||||
CWE-407: Algorithmic complexity via O(k^2) linear scan deduplication in
|
||||
MeetConstraintsBefore(). spilled_consts was a ZoneVector used only for
|
||||
membership testing inside an O(inputs) outer loop, producing O(k^2) total
|
||||
pointer comparisons per instruction when k constant-spill inputs exist.
|
||||
|
||||
Replace with ZoneUnorderedSet<TopLevelLiveRange*> for O(1) amortised
|
||||
membership test and insertion. The constant-spill path is already noted
|
||||
as "very rare" in the source comment; in pathological cases (e.g., a
|
||||
function with many identical constant-input SSA values) this can still
|
||||
materialise as a measurable hot-path.
|
||||
|
||||
ZoneUnorderedSet is provided by src/zone/zone-containers.h, already
|
||||
reachable via register-allocator.h.
|
||||
|
||||
Defect-Id: V8-001
|
||||
Severity: MEDIUM-HIGH
|
||||
CWE: CWE-407 (Inefficient Algorithmic Complexity)
|
||||
---
|
||||
src/compiler/backend/register-allocator.cc | 14 +++++++-------
|
||||
1 file changed, 7 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/src/compiler/backend/register-allocator.cc b/src/compiler/backend/register-allocator.cc
|
||||
index xxxxxxx..yyyyyyy 100644
|
||||
--- a/src/compiler/backend/register-allocator.cc
|
||||
+++ b/src/compiler/backend/register-allocator.cc
|
||||
@@ -1672,7 +1672,8 @@ void ConstraintBuilder::MeetConstraintsBefore(int instr_index) {
|
||||
Instruction* second = code()->InstructionAt(instr_index);
|
||||
// Handle fixed input operands of second instruction.
|
||||
- ZoneVector<TopLevelLiveRange*>* spilled_consts = nullptr;
|
||||
+ ZoneUnorderedSet<TopLevelLiveRange*>* spilled_consts = nullptr; // CWE-407 fix
|
||||
for (size_t i = 0; i < second->InputCount(); i++) {
|
||||
InstructionOperand* input = second->InputAt(i);
|
||||
if (input->IsImmediate()) {
|
||||
@@ -1685,15 +1686,14 @@ void ConstraintBuilder::MeetConstraintsBefore(int instr_index) {
|
||||
if (range->HasSpillOperand() && range->GetSpillOperand()->IsConstant()) {
|
||||
bool already_spilled = false;
|
||||
if (spilled_consts == nullptr) {
|
||||
- spilled_consts =
|
||||
- allocation_zone()->New<ZoneVector<TopLevelLiveRange*>>(
|
||||
- allocation_zone());
|
||||
+ spilled_consts = // CWE-407 fix
|
||||
+ allocation_zone()->New<ZoneUnorderedSet<TopLevelLiveRange*>>( // CWE-407 fix
|
||||
+ allocation_zone()); // CWE-407 fix
|
||||
} else {
|
||||
- auto it =
|
||||
- std::find(spilled_consts->begin(), spilled_consts->end(), range);
|
||||
- already_spilled = it != spilled_consts->end();
|
||||
+ already_spilled = spilled_consts->count(range) != 0; // CWE-407 fix: O(1)
|
||||
}
|
||||
auto it = data()->slot_for_const_range().find(range);
|
||||
@@ -1709,7 +1709,7 @@ void ConstraintBuilder::MeetConstraintsBefore(int instr_index) {
|
||||
data()->AddGapMove(instr_index, Instruction::END, input_copy, *slot);
|
||||
- spilled_consts->push_back(range);
|
||||
+ spilled_consts->insert(range); // CWE-407 fix: O(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
247
defects/v8/unit/V8RegisterAllocatorTest.java
Normal file
247
defects/v8/unit/V8RegisterAllocatorTest.java
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
package unit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* V8RegisterAllocatorTest
|
||||
*
|
||||
* Models the CWE-407 defect in ConstraintBuilder::MeetConstraintsBefore():
|
||||
*
|
||||
* Defective: ZoneVector<TopLevelLiveRange*> used for deduplication via std::find,
|
||||
* producing O(k) membership test → O(k^2) total per instruction.
|
||||
*
|
||||
* Fixed: ZoneUnorderedSet<TopLevelLiveRange*> with .count(), O(1) membership
|
||||
* test → O(k) total per instruction.
|
||||
*
|
||||
* Each "range pointer" is modelled as a Long ID. Comparison counts are instrumented
|
||||
* explicitly — not wall-clock timing — to isolate the algorithmic difference.
|
||||
*/
|
||||
public class V8RegisterAllocatorTest {
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Instrumented defective implementation: ArrayList + linear scan
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
static long defectiveDedup(long[] inputRangeIds) {
|
||||
ArrayList<Long> spilledConsts = null;
|
||||
long comparisons = 0;
|
||||
for (long rangeId : inputRangeIds) {
|
||||
boolean alreadySpilled = false;
|
||||
if (spilledConsts == null) {
|
||||
spilledConsts = new ArrayList<>();
|
||||
} else {
|
||||
// O(k) linear scan — the defect
|
||||
for (Long existing : spilledConsts) {
|
||||
comparisons++;
|
||||
if (existing.equals(rangeId)) {
|
||||
alreadySpilled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!alreadySpilled) {
|
||||
spilledConsts.add(rangeId);
|
||||
}
|
||||
}
|
||||
return comparisons;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Instrumented fixed implementation: HashSet + O(1) contains
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
static long fixedDedup(long[] inputRangeIds) {
|
||||
HashSet<Long> spilledConsts = null;
|
||||
long lookups = 0;
|
||||
for (long rangeId : inputRangeIds) {
|
||||
boolean alreadySpilled = false;
|
||||
if (spilledConsts == null) {
|
||||
spilledConsts = new HashSet<>();
|
||||
} else {
|
||||
lookups++; // one O(1) hash lookup per non-first input
|
||||
alreadySpilled = spilledConsts.contains(rangeId);
|
||||
}
|
||||
if (!alreadySpilled) {
|
||||
spilledConsts.add(rangeId);
|
||||
}
|
||||
}
|
||||
return lookups;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helper: build an input array where all k inputs map to the same k/2
|
||||
// distinct range IDs, maximising the average scan length in the defect.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
static long[] makeInputs(int k) {
|
||||
long[] ids = new long[k];
|
||||
int distinct = Math.max(1, k / 2);
|
||||
for (int i = 0; i < k; i++) {
|
||||
ids[i] = i % distinct;
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Test 1 — Single instruction, k=50 constant-spill inputs: defect > fixed
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
static void test1_singleInstructionRatio() {
|
||||
int k = 50;
|
||||
long[] inputs = makeInputs(k);
|
||||
long defectOps = defectiveDedup(inputs);
|
||||
long fixedOps = fixedDedup(inputs);
|
||||
|
||||
System.out.printf("test1: k=%d defect_comparisons=%d fixed_lookups=%d%n",
|
||||
k, defectOps, fixedOps);
|
||||
|
||||
assert defectOps > fixedOps
|
||||
: "defect must do more work than fix at k=" + k;
|
||||
assert defectOps >= (k / 2) * ((k / 2) - 1) / 2
|
||||
: "defect comparison count must be at least triangular for k/2 distinct ranges";
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Test 2 — Ratio > 10x at k=50 across 100 instructions
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
static void test2_ratioExceedsTenX() {
|
||||
int k = 50;
|
||||
int instructions = 100;
|
||||
long[] inputs = makeInputs(k);
|
||||
|
||||
long totalDefect = 0;
|
||||
long totalFixed = 0;
|
||||
for (int i = 0; i < instructions; i++) {
|
||||
totalDefect += defectiveDedup(inputs);
|
||||
totalFixed += fixedDedup(inputs);
|
||||
}
|
||||
|
||||
double ratio = (double) totalDefect / Math.max(1, totalFixed);
|
||||
System.out.printf("test2: instructions=%d total_defect=%d total_fixed=%d ratio=%.1fx%n",
|
||||
instructions, totalDefect, totalFixed, ratio);
|
||||
|
||||
assert ratio > 10.0
|
||||
: "expected ratio > 10x, got " + ratio;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Test 3 — All inputs are unique (worst case: every input is a cache miss)
|
||||
// defect is still O(k^2); fixed is O(k)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
static void test3_allUniqueInputs() {
|
||||
int k = 60;
|
||||
long[] inputs = new long[k];
|
||||
for (int i = 0; i < k; i++) inputs[i] = i; // all distinct
|
||||
|
||||
long defectOps = defectiveDedup(inputs);
|
||||
long fixedOps = fixedDedup(inputs);
|
||||
|
||||
// All inputs are unique → no hit ever found → no dedup gains.
|
||||
// Defect: input 0 → list null, no scan (0).
|
||||
// input 1 → list.size()=1, scans 1 item (full miss).
|
||||
// input i → list.size()=i, scans i items.
|
||||
// Total: 0 + 1 + 2 + ... + (k-1) = k*(k-1)/2
|
||||
// Fixed: k-1 lookups (first input builds null→new, no lookup counted; inputs 1..k-1 each +1).
|
||||
long expectedDefect = (long) k * (k - 1) / 2;
|
||||
double ratio = (double) defectOps / Math.max(1, fixedOps);
|
||||
|
||||
System.out.printf("test3: k=%d unique defect=%d (expect=%d) fixed=%d ratio=%.1fx%n",
|
||||
k, defectOps, expectedDefect, fixedOps, ratio);
|
||||
|
||||
assert defectOps == expectedDefect
|
||||
: "defect comparisons=" + defectOps + " expected=" + expectedDefect;
|
||||
assert ratio > 10.0
|
||||
: "expected ratio > 10x for all-unique, got " + ratio;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Test 4 — All inputs map to the same range ID (degenerate: only one
|
||||
// unique entry ever appended; every subsequent input hits on
|
||||
// the first comparison)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
static void test4_allSameRangeId() {
|
||||
int k = 100;
|
||||
long[] inputs = new long[k];
|
||||
for (int i = 0; i < k; i++) inputs[i] = 42L; // all the same
|
||||
|
||||
long defectOps = defectiveDedup(inputs);
|
||||
long fixedOps = fixedDedup(inputs);
|
||||
|
||||
// Defect: first input → list is empty, no scan.
|
||||
// Inputs 2..k each scan a list of length 1 → 1 comparison each = k-1.
|
||||
// Fixed: each non-first input → 1 hash lookup = k-1 lookups.
|
||||
// Counts are equal in this degenerate case (list always length 1), but
|
||||
// the defect comparison is still pointer-equality vs hash — no asymptote yet.
|
||||
System.out.printf("test4: k=%d same-id defect=%d fixed=%d%n",
|
||||
k, defectOps, fixedOps);
|
||||
|
||||
// At minimum, defect ≥ fixed (same list length of 1 throughout)
|
||||
assert defectOps >= fixedOps
|
||||
: "defect should not be cheaper than fix in any case";
|
||||
// Both should be exactly k-1
|
||||
assert defectOps == k - 1
|
||||
: "expected k-1=" + (k-1) + " defect comparisons, got " + defectOps;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Test 5 — Scaling: doubling k roughly quadruples defect ops, doubles fixed
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
static void test5_quadraticVsLinearScaling() {
|
||||
int k1 = 40;
|
||||
int k2 = 80; // double k
|
||||
|
||||
long d1 = defectiveDedup(makeInputs(k1));
|
||||
long d2 = defectiveDedup(makeInputs(k2));
|
||||
long f1 = fixedDedup(makeInputs(k1));
|
||||
long f2 = fixedDedup(makeInputs(k2));
|
||||
|
||||
double defectGrowth = (double) d2 / Math.max(1, d1);
|
||||
double fixedGrowth = (double) f2 / Math.max(1, f1);
|
||||
|
||||
System.out.printf("test5: defect growth on 2x k: %.2fx fixed growth: %.2fx%n",
|
||||
defectGrowth, fixedGrowth);
|
||||
|
||||
// Defect should grow super-linearly (>2x when k doubles for quadratic algo)
|
||||
assert defectGrowth > 2.0
|
||||
: "defect should grow super-linearly, got " + defectGrowth;
|
||||
// Fixed should grow at most linearly (≤2.5x for 2x k, allowing hash overhead)
|
||||
assert fixedGrowth <= 2.5
|
||||
: "fixed should grow at most linearly, got " + fixedGrowth;
|
||||
// Defect should grow meaningfully faster than fixed
|
||||
assert defectGrowth > fixedGrowth
|
||||
: "defect growth should exceed fixed growth";
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Main
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== V8RegisterAllocatorTest ===");
|
||||
System.out.println("Modelling CWE-407: MeetConstraintsBefore spilled_consts deduplication");
|
||||
System.out.println();
|
||||
|
||||
test1_singleInstructionRatio();
|
||||
System.out.println(" PASS test1_singleInstructionRatio");
|
||||
|
||||
test2_ratioExceedsTenX();
|
||||
System.out.println(" PASS test2_ratioExceedsTenX");
|
||||
|
||||
test3_allUniqueInputs();
|
||||
System.out.println(" PASS test3_allUniqueInputs");
|
||||
|
||||
test4_allSameRangeId();
|
||||
System.out.println(" PASS test4_allSameRangeId");
|
||||
|
||||
test5_quadraticVsLinearScaling();
|
||||
System.out.println(" PASS test5_quadraticVsLinearScaling");
|
||||
|
||||
System.out.println();
|
||||
System.out.println("All 5 tests PASSED.");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue