duckdb/arrow: CWE-407 scan — 4 DuckDB defects, 2 Arrow defects
DuckDB (C++ query engine): - duckdb-0001: Binder::AddCorrelatedColumn vector dedup O(C²) MEDIUM 200x - duckdb-0002: HasCorrelatedExpressions vector scan O(N×M) MEDIUM 100x - duckdb-0003: ComputeOverlappingBindings vector scan O(N×H) MEDIUM 219x - duckdb-0004: Deliminator group-join binding check O(G×J) MEDIUM 125x Apache Arrow (C++ analytics): - arrow-0001: AsofJoin IsTimeOrKeyColumn vector scan O(F×K) MEDIUM 114x - arrow-0002: Scanner AddFieldsNeededForFilter vector dedup O(F×C) MEDIUM 250x All 6/6 unit tests PASS.
This commit is contained in:
parent
0772cf539a
commit
6b975a3b9e
13 changed files with 959 additions and 184 deletions
214
defects/duckdb/unit/DuckDBTest.java
Normal file
214
defects/duckdb/unit/DuckDBTest.java
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
import java.util.*;
|
||||
|
||||
/**
|
||||
* CWE-407 unit tests for DuckDB defects.
|
||||
*
|
||||
* duckdb-0001: src/planner/binder.cpp Binder::AddCorrelatedColumn()
|
||||
* std::find over correlated_columns vector -> O(C²) dedup.
|
||||
* Fix: shadow HashSet for O(1) Contains().
|
||||
*
|
||||
* duckdb-0002: src/planner/subquery/has_correlated_expressions.cpp
|
||||
* outer loop over correlated_columns × inner std::find -> O(N×M).
|
||||
* Fix: use CorrelatedColumns::Contains() O(1).
|
||||
*
|
||||
* duckdb-0003: src/optimizer/build_probe_side_optimizer.cpp
|
||||
* ComputeOverlappingBindings: for each needle, std::find in haystack -> O(N×H).
|
||||
* Fix: unordered_set for haystack -> O(N+H).
|
||||
*
|
||||
* duckdb-0004: src/optimizer/deliminator.cpp
|
||||
* For each aggregate group, std::find in join_bindings -> O(G×J).
|
||||
* Fix: unordered_set for join_bindings -> O(G+J).
|
||||
*/
|
||||
public class DuckDBTest {
|
||||
|
||||
// --- Simulate ColumnBinding ---
|
||||
static class ColumnBinding {
|
||||
final long tableIndex;
|
||||
final long columnIndex;
|
||||
ColumnBinding(long t, long c) { this.tableIndex = t; this.columnIndex = c; }
|
||||
@Override public boolean equals(Object o) {
|
||||
if (!(o instanceof ColumnBinding)) return false;
|
||||
ColumnBinding b = (ColumnBinding) o;
|
||||
return tableIndex == b.tableIndex && columnIndex == b.columnIndex;
|
||||
}
|
||||
@Override public int hashCode() { return Objects.hash(tableIndex, columnIndex); }
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// duckdb-0001: AddCorrelatedColumn O(C²) vs O(C)
|
||||
// =========================================================
|
||||
|
||||
static class CorrelatedColumnsVector {
|
||||
List<Integer> cols = new ArrayList<>();
|
||||
void addColumn(int binding) {
|
||||
if (!cols.contains(binding)) { cols.add(0, binding); }
|
||||
}
|
||||
int size() { return cols.size(); }
|
||||
}
|
||||
|
||||
static class CorrelatedColumnsHashSet {
|
||||
List<Integer> cols = new ArrayList<>();
|
||||
Set<Integer> bindingSet = new HashSet<>();
|
||||
void addColumn(int binding) {
|
||||
if (!bindingSet.contains(binding)) { cols.add(0, binding); bindingSet.add(binding); }
|
||||
}
|
||||
int size() { return cols.size(); }
|
||||
}
|
||||
|
||||
static long mergeCorrelatedVector(List<Integer> other, CorrelatedColumnsVector dst) {
|
||||
long ops = 0;
|
||||
for (int b : other) { ops += dst.cols.size(); dst.addColumn(b); }
|
||||
return ops;
|
||||
}
|
||||
|
||||
static long mergeCorrelatedHashSet(List<Integer> other, CorrelatedColumnsHashSet dst) {
|
||||
long ops = 0;
|
||||
for (int b : other) { ops += 1; dst.addColumn(b); }
|
||||
return ops;
|
||||
}
|
||||
|
||||
static boolean testDuckDB0001() {
|
||||
System.out.println("=== duckdb-0001: AddCorrelatedColumn O(C^2) vs O(C) ===");
|
||||
int C = 400;
|
||||
List<Integer> other = new ArrayList<>();
|
||||
for (int i = 0; i < C; i++) other.add(i);
|
||||
|
||||
CorrelatedColumnsVector vec = new CorrelatedColumnsVector();
|
||||
CorrelatedColumnsHashSet hset = new CorrelatedColumnsHashSet();
|
||||
long opsVec = mergeCorrelatedVector(other, vec);
|
||||
long opsHash = mergeCorrelatedHashSet(other, hset);
|
||||
double ratio = (double) opsVec / opsHash;
|
||||
System.out.printf(" C=%d vector_ops=%,d hashset_ops=%,d ratio=%.1fx%n", C, opsVec, opsHash, ratio);
|
||||
assert vec.size() == C && hset.size() == C : "size mismatch";
|
||||
boolean pass = ratio > 10.0;
|
||||
System.out.println(" " + (pass ? "PASS" : "FAIL"));
|
||||
return pass;
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// duckdb-0002: HasCorrelatedExpressions O(N×M) vs O(N+M)
|
||||
// =========================================================
|
||||
|
||||
static long hasCorrelatedVector(List<Integer> correlatedCols, List<Integer> binderCols) {
|
||||
long ops = 0;
|
||||
for (int b : correlatedCols) {
|
||||
for (int bc : binderCols) { ops++; if (bc == b) break; }
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
static long hasCorrelatedHashSet(List<Integer> correlatedCols, List<Integer> binderCols) {
|
||||
Set<Integer> binderSet = new HashSet<>(binderCols);
|
||||
long ops = binderCols.size();
|
||||
for (int b : correlatedCols) { ops += 1; binderSet.contains(b); }
|
||||
return ops;
|
||||
}
|
||||
|
||||
static boolean testDuckDB0002() {
|
||||
System.out.println("=== duckdb-0002: HasCorrelatedExpressions O(N*M) vs O(N+M) ===");
|
||||
int N = 200;
|
||||
List<Integer> correlatedCols = new ArrayList<>();
|
||||
List<Integer> binderCols = new ArrayList<>();
|
||||
for (int i = 0; i < N; i++) correlatedCols.add(i);
|
||||
for (int i = 0; i < N; i++) binderCols.add(i + N); // non-overlapping worst case
|
||||
|
||||
long opsVec = hasCorrelatedVector(correlatedCols, binderCols);
|
||||
long opsHash = hasCorrelatedHashSet(correlatedCols, binderCols);
|
||||
double ratio = (double) opsVec / opsHash;
|
||||
System.out.printf(" N=M=%d vector_ops=%,d hashset_ops=%,d ratio=%.1fx%n", N, opsVec, opsHash, ratio);
|
||||
boolean pass = ratio > 10.0;
|
||||
System.out.println(" " + (pass ? "PASS" : "FAIL"));
|
||||
return pass;
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// duckdb-0003: ComputeOverlappingBindings O(N×H) vs O(N+H)
|
||||
// =========================================================
|
||||
|
||||
static long computeOverlappingDefective(List<ColumnBinding> haystack, List<ColumnBinding> needles) {
|
||||
long ops = 0;
|
||||
for (ColumnBinding needle : needles) {
|
||||
for (ColumnBinding h : haystack) { ops++; if (h.equals(needle)) break; }
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
static long computeOverlappingFixed(List<ColumnBinding> haystack, List<ColumnBinding> needles) {
|
||||
Set<ColumnBinding> haystackSet = new HashSet<>(haystack);
|
||||
long ops = haystack.size(); // build set
|
||||
for (ColumnBinding needle : needles) { ops++; haystackSet.contains(needle); }
|
||||
return ops;
|
||||
}
|
||||
|
||||
static boolean testDuckDB0003() {
|
||||
System.out.println("=== duckdb-0003: ComputeOverlappingBindings O(N*H) vs O(N+H) ===");
|
||||
int N = 500;
|
||||
List<ColumnBinding> haystack = new ArrayList<>();
|
||||
List<ColumnBinding> needles = new ArrayList<>();
|
||||
for (int i = 0; i < N; i++) {
|
||||
haystack.add(new ColumnBinding(0, i));
|
||||
needles.add(new ColumnBinding(0, i + N / 2)); // half overlap, worst-case scan
|
||||
}
|
||||
|
||||
long opsDefective = computeOverlappingDefective(haystack, needles);
|
||||
long opsFixed = computeOverlappingFixed(haystack, needles);
|
||||
double ratio = (double) opsDefective / opsFixed;
|
||||
System.out.printf(" N=H=%d defective_ops=%,d fixed_ops=%,d ratio=%.1fx%n",
|
||||
N, opsDefective, opsFixed, ratio);
|
||||
boolean pass = ratio > 10.0;
|
||||
System.out.println(" " + (pass ? "PASS" : "FAIL"));
|
||||
return pass;
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// duckdb-0004: Deliminator group-join binding check O(G×J)
|
||||
// =========================================================
|
||||
|
||||
static long checkGroupsDefective(List<ColumnBinding> joinBindings, long groupIndex, int groupCount) {
|
||||
long ops = 0;
|
||||
for (int g = 0; g < groupCount; g++) {
|
||||
ColumnBinding target = new ColumnBinding(groupIndex, g);
|
||||
for (ColumnBinding jb : joinBindings) { ops++; if (jb.equals(target)) break; }
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
static long checkGroupsFixed(List<ColumnBinding> joinBindings, long groupIndex, int groupCount) {
|
||||
Set<ColumnBinding> joinSet = new HashSet<>(joinBindings);
|
||||
long ops = joinBindings.size(); // build set
|
||||
for (int g = 0; g < groupCount; g++) {
|
||||
ops++; joinSet.contains(new ColumnBinding(groupIndex, g));
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
static boolean testDuckDB0004() {
|
||||
System.out.println("=== duckdb-0004: Deliminator group-join check O(G*J) vs O(G+J) ===");
|
||||
int N = 500;
|
||||
List<ColumnBinding> joinBindings = new ArrayList<>();
|
||||
for (int i = 0; i < N; i++) joinBindings.add(new ColumnBinding(42, i));
|
||||
|
||||
long opsDefective = checkGroupsDefective(joinBindings, 42, N);
|
||||
long opsFixed = checkGroupsFixed(joinBindings, 42, N);
|
||||
double ratio = (double) opsDefective / opsFixed;
|
||||
System.out.printf(" G=J=%d defective_ops=%,d fixed_ops=%,d ratio=%.1fx%n",
|
||||
N, opsDefective, opsFixed, ratio);
|
||||
boolean pass = ratio > 10.0;
|
||||
System.out.println(" " + (pass ? "PASS" : "FAIL"));
|
||||
return pass;
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
|
||||
public static void main(String[] args) {
|
||||
int pass = 0, fail = 0;
|
||||
|
||||
if (testDuckDB0001()) pass++; else fail++;
|
||||
if (testDuckDB0002()) pass++; else fail++;
|
||||
if (testDuckDB0003()) pass++; else fail++;
|
||||
if (testDuckDB0004()) pass++; else fail++;
|
||||
|
||||
System.out.printf("%nDuckDB CWE-407: %d/%d PASS%n", pass, pass + fail);
|
||||
if (fail > 0) System.exit(1);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue