java-topology/defects/duckdb/unit/DuckDbCorrelatedColumnsAlgorithm.java
russell@unturf.com 9934133dcf whitepaper: 312 sites / 151 ecosystems — wave2+3 defect tables and PDF rebuild
Add 88 new defect entries to HIGH and MEDIUM tables:
  HIGH: mysql-0001/0002, mariadb-0001, redis-0001/0002, valkey-0001/0002, openvpn-0001,
        vlc-0001, prometheus-0001, otel-collector-0001, cockroachdb-0001..0004,
        tidb-0001..0008, kubernetes-0001/0002, go-0001, kotlin-0002, scala-0001,
        allegro5-0001, sdl2-0001, grafana-0001, clickhouse-0001, duckdb-0001,
        mongodb-0001, envoy-0001, istio-0001, cilium-0001, linkerd2-0001,
        linux-0001/0002/0003, tor-0002/0003, curl-0001, julia-0001, lua-0001,
        perl5-0001, nats-0001, spring-0003/0004, tomcat-0001, onos-0002, odl-0002

  MEDIUM: helm-0001, mariadb-0002, openssl-0001/0002, memcached-0001,
          cassandra-0001..0004, flink-0001, storm-0001/0002, zookeeper-0001..0003,
          pip-0001, gradle-0001, nginx-0001, haproxy-0001, caddy-0001, varnish-0001,
          ffmpeg-0001, gstreamer-0001, raylib-0001, love2d-0001, php-0001/0002,
          r-source-0001, cpython-0002, ruby-0001, rabbitmq-0003/0004, activemq-0001,
          ovs-0001, onos-0003, odl-0002, jetty-0001

PDF: 976K
2026-03-27 15:23:43 -04:00

121 lines
4.5 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Unit test for CWE-407 defect in DuckDB CorrelatedColumns dedup.
*
* Defect: CorrelatedColumns is backed by vector<CorrelatedColumnInfo>. AddCorrelatedColumn()
* and ExtractCorrelatedColumns() each do std::find (O(n)) before inserting. Called from:
* - MergeCorrelatedColumns: for loop over other → AddCorrelatedColumn → O(n²)
* - ExtractCorrelatedColumns: recursive traversal × O(n) per ref
* - HasCorrelatedExpressions::VisitReplace: inner loop × O(n)
*
* Fix: add column_binding_set_t (unordered_set with ColumnBindingHashFunction) to CorrelatedColumns
* as a shadow set. CorrelatedColumns::contains() becomes O(1).
*
* This test models MergeCorrelatedColumns: merging S sets of C columns each into an accumulator.
* slow(): O(n) contains per insert → O(C * accumulated_size) total
* fast(): O(1) set contains → O(C * S) total
*
* Asserts slow ops > fast ops * 10x at N=300.
*/
public class DuckDbCorrelatedColumnsAlgorithm {
// -----------------------------------------------------------------------
// Node simulates CorrelatedColumnInfo (equality by binding integer id).
// -----------------------------------------------------------------------
static class Node {
final int binding;
Node(int b) { this.binding = b; }
@Override
public boolean equals(Object o) {
return o instanceof Node && ((Node) o).binding == this.binding;
}
@Override
public int hashCode() { return Integer.hashCode(binding); }
}
// -----------------------------------------------------------------------
// slow(): simulates vector-backed CorrelatedColumns with O(n) contains.
// Merges `numSets` sets of `sizePerSet` columns (with overlap to trigger dedup).
// Returns total comparison ops.
// -----------------------------------------------------------------------
static Result slow(int sizePerSet, int numSets) {
List<Node> accumulator = new ArrayList<>();
long ops = 0;
for (int s = 0; s < numSets; s++) {
for (int c = 0; c < sizePerSet; c++) {
int bindingId = c; // overlap: same columns each set → all deduped
// O(n) membership test (std::find)
boolean found = false;
for (Node n : accumulator) {
ops++;
if (n.binding == bindingId) {
found = true;
break;
}
}
if (!found) {
accumulator.add(new Node(bindingId));
}
}
}
return new Result(ops);
}
// -----------------------------------------------------------------------
// fast(): simulates CorrelatedColumns with shadow set contains() → O(1).
// Returns total lookup ops.
// -----------------------------------------------------------------------
static Result fast(int sizePerSet, int numSets) {
List<Node> accumulator = new ArrayList<>();
Set<Integer> shadowSet = new HashSet<>(); // column_binding_set_t
long ops = 0;
for (int s = 0; s < numSets; s++) {
for (int c = 0; c < sizePerSet; c++) {
int bindingId = c;
ops++; // O(1) hash lookup
if (!shadowSet.contains(bindingId)) {
shadowSet.add(bindingId);
accumulator.add(new Node(bindingId));
}
}
}
return new Result(ops);
}
// -----------------------------------------------------------------------
static class Result {
final long ops;
Result(long ops) { this.ops = ops; }
}
// -----------------------------------------------------------------------
public static void main(String[] args) {
int N = 300; // columns per set
int S = 50; // number of subquery levels (MergeCorrelatedColumns calls)
int NX = 10;
Result s = slow(N, S);
Result f = fast(N, S);
System.out.printf("slow ops=%d fast ops=%d ratio=%.1fx%n",
s.ops, f.ops, (double) s.ops / f.ops);
if (s.ops <= f.ops * NX) {
System.out.printf("FAIL: expected slow(%d) > fast(%d) * %d%n", s.ops, f.ops, NX);
System.exit(1);
}
System.out.printf("1/1 PASS (slow=%d >> fast=%d, N=%d S=%d)%n",
s.ops, f.ops, N, S);
}
}