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
390
defects/kicad/unit/KicadFromToTest.java
Normal file
390
defects/kicad/unit/KicadFromToTest.java
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
package unit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* KicadFromToTest — Java model of the CWE-407 defect in KiCad's
|
||||
* pcbnew/connectivity/from_to_cache.cpp :: uniquePathBetweenNodes().
|
||||
*
|
||||
* Defective path: visited check uses ArrayList.contains() — O(|path|) element probes per call.
|
||||
* Fixed path: visited check uses HashSet.contains() — O(1) element probes per call.
|
||||
*
|
||||
* Instrumentation:
|
||||
* elementProbes — total element comparisons inside isVertexVisited (the quadratic work)
|
||||
* membershipCalls — total number of isVertexVisited invocations
|
||||
* probesPerCall — elementProbes / membershipCalls = average scan depth
|
||||
*
|
||||
* For defective: probesPerCall = average path length scanned ≈ O(V).
|
||||
* For fixed: probesPerCall = 1.0 exactly (one hash probe per call).
|
||||
* Ratio = probesPerCall_defective / probesPerCall_fixed ≥ average path length.
|
||||
*
|
||||
* Graph model: adjacency list, nodes as Integer, V=300, B=2 (sparse).
|
||||
*/
|
||||
public class KicadFromToTest {
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Instrumentation counters //
|
||||
// ------------------------------------------------------------------ //
|
||||
static long elementProbes = 0; // element-level comparisons inside isVertexVisited
|
||||
static long membershipCalls = 0; // total calls to isVertexVisited
|
||||
|
||||
static void resetCounters() {
|
||||
elementProbes = 0;
|
||||
membershipCalls = 0;
|
||||
}
|
||||
|
||||
static double probesPerCall() {
|
||||
if (membershipCalls == 0) return 0.0;
|
||||
return (double) elementProbes / membershipCalls;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Graph builder //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
/**
|
||||
* Build a graph that maximizes BFS path lengths to stress the O(|path|) check.
|
||||
*
|
||||
* Structure: a spine of V nodes (0→1→2→…→V-1) with B-1 short-circuit branches
|
||||
* of length 3 from every 5th spine node. This creates alternate paths that keep
|
||||
* the BFS queue populated with long paths, exposing the O(V^2) membership work.
|
||||
*
|
||||
* For the linear-chain case (B=1) every path has to follow the spine,
|
||||
* forcing avg path length ≈ V/2 and ratio ≈ V/2.
|
||||
*/
|
||||
static Map<Integer, List<Integer>> buildGraph(int V, int B, long seed) {
|
||||
Random rng = new Random(seed);
|
||||
Map<Integer, List<Integer>> adj = new HashMap<>();
|
||||
for (int i = 0; i < V; i++) adj.put(i, new ArrayList<>());
|
||||
|
||||
// Spine: 0-1-2-...-V-1 (undirected)
|
||||
for (int i = 0; i < V - 1; i++) {
|
||||
adj.get(i).add(i + 1);
|
||||
adj.get(i + 1).add(i);
|
||||
}
|
||||
|
||||
// Extra random edges (avoid making graph too dense / short-circuiting paths)
|
||||
// Use only long-range edges (skip at least V/4 nodes) to keep paths long
|
||||
int extraEdges = (V * (B - 1)) / 2;
|
||||
for (int e = 0; e < extraEdges; e++) {
|
||||
int a = rng.nextInt(V);
|
||||
int delta = V / 4 + rng.nextInt(V / 4);
|
||||
int b = (a + delta) % V;
|
||||
adj.get(a).add(b);
|
||||
adj.get(b).add(a);
|
||||
}
|
||||
return adj;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// DEFECTIVE BFS — isVertexVisited is O(|path|) element probes //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
static boolean isVertexVisited_Defective(int v, List<Integer> path) {
|
||||
membershipCalls++;
|
||||
for (int u : path) {
|
||||
elementProbes++; // one probe per element examined — O(|path|) on miss
|
||||
if (u == v) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static List<Integer> uniquePathBetweenNodes_Defective(
|
||||
Map<Integer, List<Integer>> adj, int src, int dst) {
|
||||
|
||||
Deque<List<Integer>> Q = new ArrayDeque<>();
|
||||
List<Integer> init = new ArrayList<>();
|
||||
init.add(src);
|
||||
Q.add(init);
|
||||
|
||||
while (!Q.isEmpty()) {
|
||||
List<Integer> path = Q.pollFirst();
|
||||
int last = path.get(path.size() - 1);
|
||||
if (last == dst) return path;
|
||||
|
||||
for (int ci : adj.get(last)) {
|
||||
boolean visited = isVertexVisited_Defective(ci, path); // O(|path|)
|
||||
if (!visited) {
|
||||
for (List<Integer> p : Q) {
|
||||
if (isVertexVisited_Defective(ci, p)) { // O(|p|)
|
||||
visited = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!visited) {
|
||||
List<Integer> newPath = new ArrayList<>(path);
|
||||
newPath.add(ci);
|
||||
Q.add(newPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// FIXED BFS — isVertexVisited is O(1) via HashSet //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
static boolean isVertexVisited_Fixed(int v, Set<Integer> visited) {
|
||||
membershipCalls++;
|
||||
elementProbes++; // exactly one hash probe — O(1)
|
||||
return visited.contains(v);
|
||||
}
|
||||
|
||||
static class Path {
|
||||
final List<Integer> nodes;
|
||||
final Set<Integer> visited;
|
||||
|
||||
Path() {
|
||||
nodes = new ArrayList<>();
|
||||
visited = new HashSet<>();
|
||||
}
|
||||
|
||||
Path(Path other) {
|
||||
nodes = new ArrayList<>(other.nodes);
|
||||
visited = new HashSet<>(other.visited);
|
||||
}
|
||||
|
||||
void add(int node) { nodes.add(node); visited.add(node); }
|
||||
int last() { return nodes.get(nodes.size() - 1); }
|
||||
}
|
||||
|
||||
static List<Integer> uniquePathBetweenNodes_Fixed(
|
||||
Map<Integer, List<Integer>> adj, int src, int dst) {
|
||||
|
||||
Deque<Path> Q = new ArrayDeque<>();
|
||||
Path init = new Path();
|
||||
init.add(src);
|
||||
Q.add(init);
|
||||
|
||||
while (!Q.isEmpty()) {
|
||||
Path path = Q.pollFirst();
|
||||
int last = path.last();
|
||||
if (last == dst) return path.nodes;
|
||||
|
||||
for (int ci : adj.get(last)) {
|
||||
boolean visited = isVertexVisited_Fixed(ci, path.visited); // O(1)
|
||||
if (!visited) {
|
||||
for (Path p : Q) {
|
||||
if (isVertexVisited_Fixed(ci, p.visited)) { // O(1)
|
||||
visited = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!visited) {
|
||||
Path newPath = new Path(path);
|
||||
newPath.add(ci);
|
||||
Q.add(newPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Constants //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
// V=200, B=1 (pure spine): every path follows the chain, avg path length ~V/3.
|
||||
// Forces probes/call ≫ 10 for defective vs exactly 1 for fixed → ratio ≥ 10x.
|
||||
static final int V = 200;
|
||||
static final int B = 1;
|
||||
static final long SEED = 42L;
|
||||
static final int PAIRS = 10;
|
||||
|
||||
static int[][] buildPairs(int V, int pairs, long seed) {
|
||||
Random rng = new Random(seed + 1);
|
||||
int[][] result = new int[pairs][2];
|
||||
for (int i = 0; i < pairs; i++) {
|
||||
int a, b;
|
||||
do { a = rng.nextInt(V); b = rng.nextInt(V); } while (a == b);
|
||||
result[i][0] = a;
|
||||
result[i][1] = b;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void pass(String name) {
|
||||
System.out.println("PASS " + name);
|
||||
}
|
||||
|
||||
static void fail(String name, String reason) {
|
||||
System.out.println("FAIL " + name + " — " + reason);
|
||||
throw new AssertionError(name + ": " + reason);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Test methods //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
/**
|
||||
* Defective BFS: average element probes per isVertexVisited call must
|
||||
* exceed 10 (i.e., paths are long enough to manifest O(V) scan cost).
|
||||
*/
|
||||
static void testDefectiveIsQuadratic() {
|
||||
Map<Integer, List<Integer>> adj = buildGraph(V, B, SEED);
|
||||
int[][] pairs = buildPairs(V, PAIRS, SEED);
|
||||
|
||||
resetCounters();
|
||||
for (int[] pair : pairs)
|
||||
uniquePathBetweenNodes_Defective(adj, pair[0], pair[1]);
|
||||
|
||||
double ppc = probesPerCall();
|
||||
System.out.printf(" defective probes/call = %.1f (probes=%d calls=%d)%n",
|
||||
ppc, elementProbes, membershipCalls);
|
||||
|
||||
if (ppc <= 10.0)
|
||||
fail("testDefectiveIsQuadratic",
|
||||
String.format("expected probes/call > 10, got %.1f — paths may be too short", ppc));
|
||||
|
||||
pass("testDefectiveIsQuadratic");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed BFS: average element probes per isVertexVisited call must equal 1.0
|
||||
* (each call is exactly one HashSet.contains() probe — O(1)).
|
||||
*/
|
||||
static void testFixedIsLinear() {
|
||||
Map<Integer, List<Integer>> adj = buildGraph(V, B, SEED);
|
||||
int[][] pairs = buildPairs(V, PAIRS, SEED);
|
||||
|
||||
resetCounters();
|
||||
for (int[] pair : pairs)
|
||||
uniquePathBetweenNodes_Fixed(adj, pair[0], pair[1]);
|
||||
|
||||
double ppc = probesPerCall();
|
||||
System.out.printf(" fixed probes/call = %.1f (probes=%d calls=%d)%n",
|
||||
ppc, elementProbes, membershipCalls);
|
||||
|
||||
// Fixed must be exactly 1.0: elementProbes == membershipCalls
|
||||
if (elementProbes != membershipCalls)
|
||||
fail("testFixedIsLinear",
|
||||
"expected elementProbes == membershipCalls (each call = 1 probe), "
|
||||
+ "got probes=" + elementProbes + " calls=" + membershipCalls);
|
||||
|
||||
pass("testFixedIsLinear");
|
||||
}
|
||||
|
||||
/**
|
||||
* Ratio of probes/call: defective vs fixed must exceed 10x.
|
||||
*
|
||||
* defective probes/call ≈ average path length at each membership check.
|
||||
* fixed probes/call = 1.0 exactly.
|
||||
* Ratio = average path scan depth — must be ≥ 10 for test to be meaningful.
|
||||
*/
|
||||
static void testRatioAtScale() {
|
||||
Map<Integer, List<Integer>> adj = buildGraph(V, B, SEED);
|
||||
int[][] pairs = buildPairs(V, PAIRS, SEED);
|
||||
|
||||
resetCounters();
|
||||
for (int[] pair : pairs)
|
||||
uniquePathBetweenNodes_Defective(adj, pair[0], pair[1]);
|
||||
double defPPC = probesPerCall();
|
||||
long defProbes = elementProbes, defCalls = membershipCalls;
|
||||
|
||||
resetCounters();
|
||||
for (int[] pair : pairs)
|
||||
uniquePathBetweenNodes_Fixed(adj, pair[0], pair[1]);
|
||||
double fixPPC = probesPerCall();
|
||||
long fixProbes = elementProbes, fixCalls = membershipCalls;
|
||||
|
||||
double ratio = defPPC / fixPPC;
|
||||
System.out.printf(" defective probes/call = %.1f fixed probes/call = %.1f ratio = %.1fx%n",
|
||||
defPPC, fixPPC, ratio);
|
||||
|
||||
if (ratio < 10.0)
|
||||
fail("testRatioAtScale",
|
||||
String.format("expected probes/call ratio > 10x, got %.1fx "
|
||||
+ "(defective=%.1f, fixed=%.1f)", ratio, defPPC, fixPPC));
|
||||
|
||||
pass("testRatioAtScale");
|
||||
}
|
||||
|
||||
/**
|
||||
* Correctness: defective BFS returns a valid simple path for every pair.
|
||||
*/
|
||||
static void testCorrectnessDefective() {
|
||||
Map<Integer, List<Integer>> adj = buildGraph(V, B, SEED);
|
||||
int[][] pairs = buildPairs(V, PAIRS, SEED);
|
||||
|
||||
for (int[] pair : pairs) {
|
||||
List<Integer> path = uniquePathBetweenNodes_Defective(adj, pair[0], pair[1]);
|
||||
if (path == null)
|
||||
fail("testCorrectnessDefective",
|
||||
"no path found from " + pair[0] + " to " + pair[1]);
|
||||
if (path.get(0) != pair[0])
|
||||
fail("testCorrectnessDefective", "path does not start at src=" + pair[0]);
|
||||
if (path.get(path.size() - 1) != pair[1])
|
||||
fail("testCorrectnessDefective", "path does not end at dst=" + pair[1]);
|
||||
for (int i = 0; i < path.size() - 1; i++) {
|
||||
int a = path.get(i), b = path.get(i + 1);
|
||||
if (!adj.get(a).contains(b))
|
||||
fail("testCorrectnessDefective", "invalid edge " + a + "→" + b);
|
||||
}
|
||||
Set<Integer> seen = new HashSet<>(path);
|
||||
if (seen.size() != path.size())
|
||||
fail("testCorrectnessDefective", "path contains repeated nodes");
|
||||
}
|
||||
pass("testCorrectnessDefective");
|
||||
}
|
||||
|
||||
/**
|
||||
* Correctness: fixed BFS agrees with defective on reachability and returns
|
||||
* a valid simple path for every pair.
|
||||
*/
|
||||
static void testCorrectnessFixed() {
|
||||
Map<Integer, List<Integer>> adj = buildGraph(V, B, SEED);
|
||||
int[][] pairs = buildPairs(V, PAIRS, SEED);
|
||||
|
||||
for (int[] pair : pairs) {
|
||||
List<Integer> defPath = uniquePathBetweenNodes_Defective(adj, pair[0], pair[1]);
|
||||
List<Integer> fixPath = uniquePathBetweenNodes_Fixed(adj, pair[0], pair[1]);
|
||||
|
||||
boolean defFound = (defPath != null);
|
||||
boolean fixFound = (fixPath != null);
|
||||
if (defFound != fixFound)
|
||||
fail("testCorrectnessFixed",
|
||||
"reachability disagreement " + pair[0] + "→" + pair[1]
|
||||
+ ": defective=" + defFound + " fixed=" + fixFound);
|
||||
|
||||
if (fixPath == null) continue;
|
||||
|
||||
if (fixPath.get(0) != pair[0])
|
||||
fail("testCorrectnessFixed", "path does not start at src=" + pair[0]);
|
||||
if (fixPath.get(fixPath.size() - 1) != pair[1])
|
||||
fail("testCorrectnessFixed", "path does not end at dst=" + pair[1]);
|
||||
|
||||
for (int i = 0; i < fixPath.size() - 1; i++) {
|
||||
int a = fixPath.get(i), b = fixPath.get(i + 1);
|
||||
if (!adj.get(a).contains(b))
|
||||
fail("testCorrectnessFixed", "invalid edge " + a + "→" + b);
|
||||
}
|
||||
|
||||
Set<Integer> seen = new HashSet<>(fixPath);
|
||||
if (seen.size() != fixPath.size())
|
||||
fail("testCorrectnessFixed", "fixed path contains repeated nodes");
|
||||
}
|
||||
pass("testCorrectnessFixed");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Main //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== KicadFromToTest — KICAD-001 CWE-407 ===");
|
||||
System.out.println(" V=" + V + " B=" + B + " pairs=" + PAIRS);
|
||||
System.out.println();
|
||||
|
||||
testDefectiveIsQuadratic();
|
||||
testFixedIsLinear();
|
||||
testRatioAtScale();
|
||||
testCorrectnessDefective();
|
||||
testCorrectnessFixed();
|
||||
|
||||
System.out.println();
|
||||
System.out.println("All tests passed.");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue