undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
147
tests/support/TopoSorterAlgorithm.java
Normal file
147
tests/support/TopoSorterAlgorithm.java
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
package support;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Reference implementations of ModuleHashesBuilder$TopoSorter.visit() — defective and fixed.
|
||||
*
|
||||
* DEFECT 0003 (java.base/jdk.internal.module): visit() checks
|
||||
* Deque.contains(node) to detect whether the current node is on the DFS
|
||||
* recursion stack (cycle detection). ArrayDeque.contains() = O(N) linear scan.
|
||||
* This code lives in java.base — present in every JDK/JRE.
|
||||
*
|
||||
* Fix: maintain a HashSet<Node> onStack alongside the Deque.
|
||||
* HashSet.contains() = O(1).
|
||||
*
|
||||
* Exact comparison counts for a linear chain of V nodes (N0→N1→...→N(V-1)):
|
||||
* defective: V*(V-1)/2 (visit(N_k) scans k-element stack = k comparisons;
|
||||
* sum(k, k=0..V-1) = V*(V-1)/2)
|
||||
* fixed: V (1 per visit — one HashSet.contains() call)
|
||||
*
|
||||
* Derivation of defective count:
|
||||
* visit(N0): stack=[], deque.contains(N0) scans 0 elements → 0 comparisons
|
||||
* visit(N1): stack=[N0], contains(N1) scans 1 element → 1 comparison
|
||||
* visit(N2): stack=[N1,N0], contains(N2) scans 2 elements → 2 comparisons
|
||||
* ...
|
||||
* visit(N(V-1)): stack is V-1 deep → V-1 comparisons
|
||||
* Total: 0+1+2+...+(V-1) = V*(V-1)/2.
|
||||
*
|
||||
* Growth when V doubles: defective ≈4x (quadratic), fixed ≈2x (linear).
|
||||
*/
|
||||
public class TopoSorterAlgorithm {
|
||||
|
||||
public static class Node {
|
||||
public final String label;
|
||||
public final List<Node> deps = new ArrayList<>();
|
||||
public Node(String label) { this.label = label; }
|
||||
public void addDep(Node dep) { deps.add(dep); }
|
||||
@Override public String toString() { return label; }
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
public final List<Node> sorted;
|
||||
public final long comparisons;
|
||||
Result(List<Node> sorted, long comparisons) {
|
||||
this.sorted = sorted;
|
||||
this.comparisons = comparisons;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── DEFECTIVE: Deque.contains() for on-stack check ─────────────────────
|
||||
// Mirrors ModuleHashesBuilder$TopoSorter.visit() — uses Deque.contains(m)
|
||||
// to check whether the current node is already on the recursion stack.
|
||||
|
||||
public static Result topoSortDefective(List<Node> nodes) {
|
||||
return new DefectiveSorter().sort(nodes);
|
||||
}
|
||||
|
||||
private static class DefectiveSorter {
|
||||
private final Deque<Node> stack = new ArrayDeque<>();
|
||||
private final Set<Node> visited = new HashSet<>();
|
||||
private final List<Node> sorted = new ArrayList<>();
|
||||
private long comparisons = 0;
|
||||
|
||||
Result sort(List<Node> nodes) {
|
||||
for (Node n : nodes) {
|
||||
if (!visited.contains(n)) visit(n);
|
||||
}
|
||||
return new Result(sorted, comparisons);
|
||||
}
|
||||
|
||||
void visit(Node v) {
|
||||
// DEFECT: O(N) scan of the recursion stack for cycle detection
|
||||
comparisons += dequeContains(stack, v);
|
||||
visited.add(v);
|
||||
stack.push(v);
|
||||
for (Node dep : v.deps) {
|
||||
if (!visited.contains(dep)) visit(dep);
|
||||
}
|
||||
stack.pop();
|
||||
sorted.add(0, v);
|
||||
}
|
||||
|
||||
private static long dequeContains(Deque<Node> deque, Node target) {
|
||||
long count = 0;
|
||||
for (Node n : deque) {
|
||||
count++;
|
||||
if (n == target) break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── FIXED: HashSet<Node> for on-stack check ─────────────────────────────
|
||||
// Maintain a dedicated onStack set. HashSet.contains() = O(1).
|
||||
|
||||
public static Result topoSortFixed(List<Node> nodes) {
|
||||
return new FixedSorter().sort(nodes);
|
||||
}
|
||||
|
||||
private static class FixedSorter {
|
||||
private final Set<Node> onStack = new HashSet<>();
|
||||
private final Set<Node> visited = new HashSet<>();
|
||||
private final List<Node> sorted = new ArrayList<>();
|
||||
private long comparisons = 0;
|
||||
|
||||
Result sort(List<Node> nodes) {
|
||||
for (Node n : nodes) {
|
||||
if (!visited.contains(n)) visit(n);
|
||||
}
|
||||
return new Result(sorted, comparisons);
|
||||
}
|
||||
|
||||
void visit(Node v) {
|
||||
comparisons++; // O(1) HashSet.contains() for cycle check
|
||||
// onStack.contains(v) would be false here (not a cycle); add anyway
|
||||
visited.add(v);
|
||||
onStack.add(v);
|
||||
for (Node dep : v.deps) {
|
||||
if (!visited.contains(dep)) visit(dep);
|
||||
}
|
||||
onStack.remove(v);
|
||||
sorted.add(0, v);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Factory ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Linear chain: N0→N1→...→N(V-1). No cycles — a valid DAG for topo sort.
|
||||
* DFS visits nodes in stack-depth order, producing an increasing stack depth
|
||||
* at each level.
|
||||
*
|
||||
* Defective comparisons: V*(V-1)/2
|
||||
* Fixed comparisons: V
|
||||
*/
|
||||
public static List<Node> buildLinearChain(int v) {
|
||||
List<Node> nodes = new ArrayList<>();
|
||||
for (int i = 0; i < v; i++) nodes.add(new Node("N" + i));
|
||||
for (int i = 0; i < v - 1; i++) nodes.get(i).addDep(nodes.get(i + 1));
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue