wave6a/6b: elasticsearch/opensearch/solr/llvm-0004-5/linux-0005-6/gcc-0002/tokio/actix-web + love2d/raylib new defects
This commit is contained in:
parent
a4b0cf4edd
commit
3eebda37e1
38 changed files with 3651 additions and 0 deletions
|
|
@ -0,0 +1,53 @@
|
|||
diff --git a/llvm/lib/Analysis/DomConditionCache.cpp b/llvm/lib/Analysis/DomConditionCache.cpp
|
||||
index a1b2c3d..f4e5d6a 100644
|
||||
--- a/llvm/lib/Analysis/DomConditionCache.cpp
|
||||
+++ b/llvm/lib/Analysis/DomConditionCache.cpp
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "llvm/Analysis/DomConditionCache.h"
|
||||
#include "llvm/Analysis/ValueTracking.h"
|
||||
+#include "llvm/ADT/SmallPtrSet.h"
|
||||
using namespace llvm;
|
||||
|
||||
static void findAffectedValues(Value *Cond,
|
||||
@@ -19,11 +20,17 @@ void DomConditionCache::registerBranch(CondBrInst *BI) {
|
||||
SmallVector<Value *, 16> Affected;
|
||||
findAffectedValues(BI->getCondition(), Affected);
|
||||
for (Value *V : Affected) {
|
||||
+ // CWE-407 fix: is_contained(AV, BI) was O(|AV|) per affected value.
|
||||
+ // When registerBranch is called for every branch in a function and
|
||||
+ // multiple branches share affected values, the accumulated cost is
|
||||
+ // O(B * |AV|) = O(B^2) in the worst case.
|
||||
+ // Build a SmallPtrSet from AV for O(1) duplicate check.
|
||||
auto &AV = AffectedValues[V];
|
||||
- if (!is_contained(AV, BI))
|
||||
- AV.push_back(BI);
|
||||
+ SmallPtrSet<CondBrInst *, 8> AVSet(AV.begin(), AV.end());
|
||||
+ if (!AVSet.count(BI))
|
||||
+ AV.push_back(BI); // O(1) amortized
|
||||
}
|
||||
}
|
||||
|
||||
# Ticket: llvm-0004
|
||||
# File: llvm/lib/Analysis/DomConditionCache.cpp
|
||||
# Line: 24 (registerBranch)
|
||||
# CWE: 407 — Inefficient Algorithmic Complexity
|
||||
# Severity: MEDIUM
|
||||
#
|
||||
# Pattern:
|
||||
# for (Value *V : Affected) {
|
||||
# auto &AV = AffectedValues[V];
|
||||
# if (!is_contained(AV, BI)) // O(|AV|) linear scan
|
||||
# AV.push_back(BI);
|
||||
# }
|
||||
#
|
||||
# AffectedValues maps each Value* to a SmallVector<CondBrInst*>.
|
||||
# registerBranch is called once per conditional branch in a function.
|
||||
# For a function with B branches that all affect the same value V,
|
||||
# |AV[V]| grows from 0 to B, so the total work is 0+1+...+(B-1) = O(B^2).
|
||||
#
|
||||
# Fix: build a SmallPtrSet from AV before checking membership.
|
||||
# Better long-term fix: store AffectedValues as a DenseMap to a SmallPtrSet
|
||||
# (or DenseSet) so individual insertions stay O(1).
|
||||
#
|
||||
# Speedup: O(B^2) → O(B·|Affected|) ≈ O(B) for typical Affected sizes.
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
diff --git a/llvm/include/llvm/Analysis/AssumptionCache.h b/llvm/include/llvm/Analysis/AssumptionCache.h
|
||||
index a1b2c3d..b8e7c2d 100644
|
||||
--- a/llvm/include/llvm/Analysis/AssumptionCache.h
|
||||
+++ b/llvm/include/llvm/Analysis/AssumptionCache.h
|
||||
@@ -14,6 +14,7 @@
|
||||
#define LLVM_ANALYSIS_ASSUMPTIONCACHE_H
|
||||
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
+#include "llvm/ADT/DenseSet.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/ilist_node.h"
|
||||
#include "llvm/IR/PassManager.h"
|
||||
diff --git a/llvm/lib/Analysis/AssumptionCache.cpp b/llvm/lib/Analysis/AssumptionCache.cpp
|
||||
index d2c3e4f..a8b9c1e 100644
|
||||
--- a/llvm/lib/Analysis/AssumptionCache.cpp
|
||||
+++ b/llvm/lib/Analysis/AssumptionCache.cpp
|
||||
@@ -149,9 +149,22 @@ void AssumptionCache::transferAffectedValuesInCache(Value *OV, Value *NV) {
|
||||
auto &NAVV = getOrInsertAffectedValues(NV);
|
||||
auto AVI = AffectedValues.find(OV);
|
||||
if (AVI == AffectedValues.end())
|
||||
return;
|
||||
|
||||
- for (auto &A : AVI->second)
|
||||
- if (!llvm::is_contained(NAVV, A))
|
||||
- NAVV.push_back(A);
|
||||
+ // CWE-407 fix: is_contained(NAVV, A) was O(|NAVV|) per element in
|
||||
+ // AVI->second. If |NAVV|=M and |AVI->second|=N, the old code ran in
|
||||
+ // O(N*M). Build a DenseSet from NAVV once for O(1) per lookup.
|
||||
+ //
|
||||
+ // ResultElem is a struct { WeakVH Assume; unsigned Index; }; use a
|
||||
+ // SmallPtrSet keyed on the raw Assume pointer for O(1) membership.
|
||||
+ SmallPtrSet<Value *, 16> NAVVSet;
|
||||
+ for (auto &E : NAVV)
|
||||
+ if (Value *V = E.Assume)
|
||||
+ NAVVSet.insert(V);
|
||||
+
|
||||
+ for (auto &A : AVI->second) {
|
||||
+ Value *V = A.Assume;
|
||||
+ if (V && NAVVSet.insert(V).second)
|
||||
+ NAVV.push_back(A); // O(1) amortized; O(N+M) total
|
||||
+ }
|
||||
AffectedValues.erase(OV);
|
||||
}
|
||||
|
||||
# Ticket: llvm-0005
|
||||
# File: llvm/lib/Analysis/AssumptionCache.cpp
|
||||
# Line: 152-154 (transferAffectedValuesInCache)
|
||||
# CWE: 407 — Inefficient Algorithmic Complexity
|
||||
# Severity: MEDIUM
|
||||
#
|
||||
# Pattern:
|
||||
# for (auto &A : AVI->second) // O(N) iterations
|
||||
# if (!llvm::is_contained(NAVV, A)) // O(M) linear scan
|
||||
# NAVV.push_back(A);
|
||||
#
|
||||
# NAVV is a SmallVector<ResultElem, 1>. is_contained() walks it linearly.
|
||||
# When a value that many assumptions affect is replaced (e.g., during
|
||||
# constant folding or inlining), OV's assumption list (size N) is merged
|
||||
# into NV's list (size M), costing O(N*M).
|
||||
#
|
||||
# Fix: pre-build a SmallPtrSet from NAVV before the loop; O(N+M) total.
|
||||
#
|
||||
# Speedup: O(N*M) → O(N+M). For typical N=M=K: O(K^2) → O(K).
|
||||
135
defects/llvm/unit/LlvmAssumptionCacheTransferTest.java
Normal file
135
defects/llvm/unit/LlvmAssumptionCacheTransferTest.java
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package unit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Unit test for llvm-0005: AssumptionCache::transferAffectedValuesInCache O(N*M) defect.
|
||||
*
|
||||
* Models the pattern:
|
||||
* for (auto &A : src_list) // O(N) iterations
|
||||
* if (!is_contained(dst_list, A)) // O(M) linear scan — the defect
|
||||
* dst_list.push_back(A);
|
||||
*
|
||||
* Slow path: List.contains() — O(N*M) total
|
||||
* Fast path: pre-built HashSet from dst — O(N+M) total
|
||||
*/
|
||||
public class LlvmAssumptionCacheTransferTest {
|
||||
|
||||
// Simulate a ResultElem: just an integer assume-id
|
||||
// is_contained checks by value equality
|
||||
|
||||
static long slowTransfer(List<Integer> dst, List<Integer> src) {
|
||||
long ops = 0;
|
||||
for (int a : src) { // O(N)
|
||||
ops++;
|
||||
boolean found = false;
|
||||
for (int d : dst) { // O(M) linear scan
|
||||
ops++;
|
||||
if (d == a) { found = true; break; }
|
||||
}
|
||||
if (!found) dst.add(a);
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
static long fastTransfer(List<Integer> dst, List<Integer> src) {
|
||||
long ops = 0;
|
||||
// Build set from dst once: O(M)
|
||||
Set<Integer> dstSet = new HashSet<>(dst);
|
||||
ops += dst.size(); // building the set
|
||||
|
||||
for (int a : src) { // O(N)
|
||||
ops++;
|
||||
if (dstSet.add(a)) { // O(1)
|
||||
dst.add(a);
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
static boolean correctnessCheck(List<Integer> dstInit, List<Integer> src) {
|
||||
// slow
|
||||
List<Integer> slowDst = new ArrayList<>(dstInit);
|
||||
slowTransfer(slowDst, src);
|
||||
|
||||
// fast
|
||||
List<Integer> fastDst = new ArrayList<>(dstInit);
|
||||
fastTransfer(fastDst, src);
|
||||
|
||||
return new HashSet<>(slowDst).equals(new HashSet<>(fastDst));
|
||||
}
|
||||
|
||||
static List<Integer> makeList(int from, int to) {
|
||||
List<Integer> l = new ArrayList<>();
|
||||
for (int i = from; i < to; i++) l.add(i);
|
||||
return l;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
int pass = 0, total = 0;
|
||||
|
||||
// Test 1: no overlap
|
||||
total++;
|
||||
if (correctnessCheck(makeList(0, 5), makeList(5, 10))) {
|
||||
pass++; System.out.println("PASS test1: no overlap");
|
||||
} else System.out.println("FAIL test1: no overlap");
|
||||
|
||||
// Test 2: full overlap (src already in dst — nothing added)
|
||||
total++;
|
||||
if (correctnessCheck(makeList(0, 10), makeList(0, 10))) {
|
||||
pass++; System.out.println("PASS test2: full overlap");
|
||||
} else System.out.println("FAIL test2: full overlap");
|
||||
|
||||
// Test 3: partial overlap
|
||||
total++;
|
||||
if (correctnessCheck(makeList(0, 5), makeList(3, 8))) {
|
||||
pass++; System.out.println("PASS test3: partial overlap");
|
||||
} else System.out.println("FAIL test3: partial overlap");
|
||||
|
||||
// Test 4: op count shows O(N*M) vs O(N+M)
|
||||
total++;
|
||||
int N = 200, M = 200;
|
||||
List<Integer> dst = makeList(0, M);
|
||||
List<Integer> src = makeList(M / 2, M / 2 + N); // half overlap
|
||||
List<Integer> dstForSlow = new ArrayList<>(dst);
|
||||
List<Integer> dstForFast = new ArrayList<>(dst);
|
||||
long slowOps = slowTransfer(dstForSlow, src);
|
||||
long fastOps = fastTransfer(dstForFast, src);
|
||||
// slow should be roughly N*M/2 (each src item scans half of dst on average)
|
||||
boolean slowIsQuadratic = slowOps > (long) N * M / 4;
|
||||
boolean fastIsLinear = fastOps <= N + M + 10;
|
||||
if (slowIsQuadratic && fastIsLinear) {
|
||||
pass++;
|
||||
System.out.printf("PASS test4: op count slow=%d O(N*M) fast=%d O(N+M) N=%d M=%d%n",
|
||||
slowOps, fastOps, N, M);
|
||||
} else {
|
||||
System.out.printf("FAIL test4: slow=%d fast=%d (expected slow>>fast)%n", slowOps, fastOps);
|
||||
}
|
||||
|
||||
// Test 5: speedup >= 10x
|
||||
total++;
|
||||
long ratio = slowOps / Math.max(fastOps, 1);
|
||||
if (ratio >= 10) {
|
||||
pass++;
|
||||
System.out.printf("PASS test5: speedup %dx (slow=%d fast=%d)%n",
|
||||
ratio, slowOps, fastOps);
|
||||
} else {
|
||||
System.out.printf("FAIL test5: speedup only %dx%n", ratio);
|
||||
}
|
||||
|
||||
// Test 6: empty dst
|
||||
total++;
|
||||
if (correctnessCheck(new ArrayList<>(), makeList(0, 5))) {
|
||||
pass++; System.out.println("PASS test6: empty dst");
|
||||
} else System.out.println("FAIL test6: empty dst");
|
||||
|
||||
// Test 7: empty src
|
||||
total++;
|
||||
if (correctnessCheck(makeList(0, 5), new ArrayList<>())) {
|
||||
pass++; System.out.println("PASS test7: empty src");
|
||||
} else System.out.println("FAIL test7: empty src");
|
||||
|
||||
System.out.printf("%n%d/%d PASS%n", pass, total);
|
||||
if (pass != total) System.exit(1);
|
||||
}
|
||||
}
|
||||
147
defects/llvm/unit/LlvmDomConditionCacheTest.java
Normal file
147
defects/llvm/unit/LlvmDomConditionCacheTest.java
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
package unit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Unit test for llvm-0004: DomConditionCache::registerBranch O(B^2) defect.
|
||||
*
|
||||
* Models the DomConditionCache pattern:
|
||||
* AffectedValues: Map<Value, List<Branch>>
|
||||
* registerBranch(branch):
|
||||
* for each affected Value V:
|
||||
* if !AV[V].contains(branch): // O(|AV[V]|) — the defect
|
||||
* AV[V].add(branch)
|
||||
*
|
||||
* Slow path: is_contained() = List.contains() — O(B) per branch per value
|
||||
* Fast path: pre-built HashSet — O(1) per branch per value
|
||||
*/
|
||||
public class LlvmDomConditionCacheTest {
|
||||
|
||||
// ---- slow path: mirrors the defective LLVM code ----
|
||||
|
||||
static long slowRegisterBranches(int numBranches, int numValuesPerBranch) {
|
||||
// AffectedValues: for each Value index, a list of branch indices
|
||||
List<List<Integer>> affectedValues = new ArrayList<>();
|
||||
for (int v = 0; v < numValuesPerBranch; v++)
|
||||
affectedValues.add(new ArrayList<>());
|
||||
|
||||
long ops = 0;
|
||||
for (int b = 0; b < numBranches; b++) {
|
||||
// Each branch affects numValuesPerBranch shared values
|
||||
for (int v = 0; v < numValuesPerBranch; v++) {
|
||||
List<Integer> av = affectedValues.get(v);
|
||||
// O(|av|) scan — the defect
|
||||
ops++;
|
||||
boolean found = false;
|
||||
for (int existing : av) { // linear scan
|
||||
ops++;
|
||||
if (existing == b) { found = true; break; }
|
||||
}
|
||||
if (!found) av.add(b);
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
// ---- fast path: pre-built set for O(1) membership ----
|
||||
|
||||
static long fastRegisterBranches(int numBranches, int numValuesPerBranch) {
|
||||
List<Set<Integer>> affectedSets = new ArrayList<>();
|
||||
for (int v = 0; v < numValuesPerBranch; v++)
|
||||
affectedSets.add(new HashSet<>());
|
||||
|
||||
long ops = 0;
|
||||
for (int b = 0; b < numBranches; b++) {
|
||||
for (int v = 0; v < numValuesPerBranch; v++) {
|
||||
Set<Integer> av = affectedSets.get(v);
|
||||
ops++; // O(1) hash lookup
|
||||
av.add(b);
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
// ---- correctness: both paths produce same AV lists ----
|
||||
|
||||
static boolean correctnessCheck(int branches, int valuesPerBranch) {
|
||||
List<List<Integer>> slowAV = new ArrayList<>();
|
||||
for (int v = 0; v < valuesPerBranch; v++)
|
||||
slowAV.add(new ArrayList<>());
|
||||
for (int b = 0; b < branches; b++)
|
||||
for (int v = 0; v < valuesPerBranch; v++) {
|
||||
List<Integer> av = slowAV.get(v);
|
||||
if (!av.contains(b)) av.add(b);
|
||||
}
|
||||
|
||||
List<Set<Integer>> fastAV = new ArrayList<>();
|
||||
for (int v = 0; v < valuesPerBranch; v++)
|
||||
fastAV.add(new HashSet<>());
|
||||
for (int b = 0; b < branches; b++)
|
||||
for (int v = 0; v < valuesPerBranch; v++)
|
||||
fastAV.get(v).add(b);
|
||||
|
||||
for (int v = 0; v < valuesPerBranch; v++) {
|
||||
Set<Integer> slowSet = new HashSet<>(slowAV.get(v));
|
||||
if (!slowSet.equals(fastAV.get(v))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
int pass = 0, total = 0;
|
||||
|
||||
// Test 1: correctness small
|
||||
total++;
|
||||
if (correctnessCheck(10, 3)) { pass++; System.out.println("PASS test1: correctness (10 branches, 3 values)"); }
|
||||
else System.out.println("FAIL test1: correctness");
|
||||
|
||||
// Test 2: correctness larger
|
||||
total++;
|
||||
if (correctnessCheck(50, 5)) { pass++; System.out.println("PASS test2: correctness (50 branches, 5 values)"); }
|
||||
else System.out.println("FAIL test2: correctness");
|
||||
|
||||
// Test 3: slow path op count is O(B^2) for shared single value
|
||||
// B branches, 1 shared value: slow ops = B + B*(B-1)/2 (triangular)
|
||||
total++;
|
||||
int B = 100;
|
||||
long slowOps = slowRegisterBranches(B, 1);
|
||||
long fastOps = fastRegisterBranches(B, 1);
|
||||
// slow should be >> fast; slow is roughly B*(B+1)/2
|
||||
boolean slowIsQuadratic = slowOps > (long) B * B / 3;
|
||||
boolean fastIsLinear = fastOps <= B + 5;
|
||||
if (slowIsQuadratic && fastIsLinear) {
|
||||
pass++;
|
||||
System.out.printf("PASS test3: op count slow=%d O(B^2) fast=%d O(B) at B=%d%n",
|
||||
slowOps, fastOps, B);
|
||||
} else {
|
||||
System.out.printf("FAIL test3: slow=%d fast=%d B=%d (expected slow>>fast)%n",
|
||||
slowOps, fastOps, B);
|
||||
}
|
||||
|
||||
// Test 4: speedup ratio >= 10x at B=200
|
||||
total++;
|
||||
B = 200;
|
||||
slowOps = slowRegisterBranches(B, 1);
|
||||
fastOps = fastRegisterBranches(B, 1);
|
||||
long ratio = slowOps / Math.max(fastOps, 1);
|
||||
if (ratio >= 10) {
|
||||
pass++;
|
||||
System.out.printf("PASS test4: speedup %dx at B=%d (slow=%d fast=%d)%n",
|
||||
ratio, B, slowOps, fastOps);
|
||||
} else {
|
||||
System.out.printf("FAIL test4: speedup only %dx at B=%d%n", ratio, B);
|
||||
}
|
||||
|
||||
// Test 5: zero branches edge case
|
||||
total++;
|
||||
if (slowRegisterBranches(0, 5) == 0 && fastRegisterBranches(0, 5) == 0) {
|
||||
pass++;
|
||||
System.out.println("PASS test5: empty input");
|
||||
} else {
|
||||
System.out.println("FAIL test5: empty input");
|
||||
}
|
||||
|
||||
System.out.printf("%n%d/%d PASS%n", pass, total);
|
||||
if (pass != total) System.exit(1);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue