wave9b: 455/204 — graphhopper-0001/0002 + valhalla-0001 + OSRM CLEAN

This commit is contained in:
russell@unturf.com 2026-03-27 16:54:46 -04:00
parent 16cc7ecf1d
commit 81bc62b9cb
9 changed files with 811 additions and 5 deletions

View file

@ -0,0 +1,118 @@
# graphhopper-0001: AlternativeRouteCH — IntArrayList.contains() O(P) inside edge loop
## File
`core/src/main/java/com/graphhopper/routing/AlternativeRouteCH.java`
## Severity
**HIGH**
## Lines Affected
- Line 174: `alternatives.get(0).nodes.contains()` × 2 — inside per-edge loop
- Lines 182187: `nodesInCurrentAlternativeSetContains()` — O(A×P) per edge call
## Defective Code
```java
// Line 159168 — sharedDistance() — O(E × A × P) total
private double sharedDistance(Path path) {
double sharedDistance = 0.0;
List<EdgeIteratorState> edges = path.calcEdges();
for (EdgeIteratorState edge : edges) {
if (nodesInCurrentAlternativeSetContains(edge.getBaseNode()) && nodesInCurrentAlternativeSetContains(edge.getAdjNode())) {
sharedDistance += edge.getDistance();
}
}
return sharedDistance;
}
// Line 170178 — sharedDistanceWithShortest() — O(E × P) total
private double sharedDistanceWithShortest(Path path) {
double sharedDistance = 0.0;
List<EdgeIteratorState> edges = path.calcEdges();
for (EdgeIteratorState edge : edges) {
if (alternatives.get(0).nodes.contains(edge.getBaseNode()) && alternatives.get(0).nodes.contains(edge.getAdjNode())) {
sharedDistance += edge.getDistance();
}
}
return sharedDistance;
}
// Line 181188 — nodesInCurrentAlternativeSetContains() — O(A × P) per call
private boolean nodesInCurrentAlternativeSetContains(int v) {
for (AlternativeInfo alternative : alternatives) {
if (alternative.nodes.contains(v)) { // IntArrayList.contains() = O(P) linear scan
return true;
}
}
return false;
}
```
`AlternativeInfo.nodes` is assigned from `path.calcNodes()` which returns an `IntArrayList`
(HPPC). `IntArrayList.contains()` is a linear scan over the node list — O(P) per call where
P = path length (number of nodes on the path).
## Root Cause
`Path.calcNodes()` returns `IntArrayList` (an indexed array list), not a hash set.
`AlternativeInfo` stores the result as `IntIndexedContainer` — the declared interface — which
does not prevent linear-scan `contains()`. Every edge shared-distance calculation performs
O(P) membership tests, called O(E) times per path evaluation, with up to A alternatives.
## Complexity Analysis
| Path | Per-call | Total calls | Overall |
|------|----------|-------------|---------|
| Slow (IntArrayList.contains) | O(P) | O(E × A) | **O(E × A × P)** |
| Fast (IntScatterSet.contains) | O(1) | O(E × A) | **O(E × A)** |
For a 1000-edge route with paths of 800 nodes and 3 alternatives: slow = 2,400,000 comparisons
vs fast = 3,000 comparisons — **800× speedup**.
## Fixed Code
```java
import com.carrotsearch.hppc.IntScatterSet;
import com.carrotsearch.hppc.IntSet;
// In AlternativeInfo inner class — store node set alongside node list:
public static class AlternativeInfo {
final double shareWeight;
final Path path;
final IntIndexedContainer nodes;
final IntSet nodeSet; // ADD: O(1) lookup set
AlternativeInfo(Path path, double shareWeight) {
this.path = path;
this.shareWeight = shareWeight;
this.nodes = path.calcNodes();
// Build O(1) lookup set from the node list
IntScatterSet set = new IntScatterSet(nodes.size());
for (int i = 0; i < nodes.size(); i++) {
set.add(nodes.get(i));
}
this.nodeSet = set;
}
}
// sharedDistanceWithShortest — use nodeSet instead of nodes
private double sharedDistanceWithShortest(Path path) {
double sharedDistance = 0.0;
List<EdgeIteratorState> edges = path.calcEdges();
for (EdgeIteratorState edge : edges) {
if (alternatives.get(0).nodeSet.contains(edge.getBaseNode())
&& alternatives.get(0).nodeSet.contains(edge.getAdjNode())) {
sharedDistance += edge.getDistance();
}
}
return sharedDistance;
}
// nodesInCurrentAlternativeSetContains — use nodeSet
private boolean nodesInCurrentAlternativeSetContains(int v) {
for (AlternativeInfo alternative : alternatives) {
if (alternative.nodeSet.contains(v)) { // O(1) hash lookup
return true;
}
}
return false;
}
```

View file

@ -0,0 +1,75 @@
# graphhopper-0002: AlternativeRouteEdgeCH — IntArrayList.contains() O(P) inside edge loop
## File
`core/src/main/java/com/graphhopper/routing/AlternativeRouteEdgeCH.java`
## Severity
**HIGH**
## Lines Affected
- Line 190: `alternatives.get(0).nodes.contains()` × 2 — inside per-edge loop
- Lines 197204: `nodesInCurrentAlternativeSetContains()` — O(A×P) per edge call
## Defective Code
```java
// sharedDistanceWithShortest() — O(E × P) per call
private double sharedDistanceWithShortest(Path path) {
double sharedDistance = 0.0;
List<EdgeIteratorState> edges = path.calcEdges();
for (EdgeIteratorState edge : edges) {
if (alternatives.get(0).nodes.contains(edge.getBaseNode()) && alternatives.get(0).nodes.contains(edge.getAdjNode())) {
sharedDistance += edge.getDistance();
}
}
return sharedDistance;
}
// nodesInCurrentAlternativeSetContains() — O(A × P) per call
private boolean nodesInCurrentAlternativeSetContains(int v) {
for (AlternativeInfo alternative : alternatives) {
if (alternative.nodes.contains(v)) { // IntArrayList.contains() = O(P)
return true;
}
}
return false;
}
```
## Root Cause
Identical to graphhopper-0001. This is the edge-based CH variant of the same algorithm.
`AlternativeInfo.nodes` is an `IntArrayList` backed by `Path.calcNodes()`. Every call to
`contains()` performs a linear scan over path nodes.
## Complexity Analysis
| Path | Per-call | Total calls | Overall |
|------|----------|-------------|---------|
| Slow (IntArrayList.contains) | O(P) | O(E × A) | **O(E × A × P)** |
| Fast (IntScatterSet.contains) | O(1) | O(E × A) | **O(E × A)** |
## Fixed Code
Same fix as graphhopper-0001: add an `IntScatterSet nodeSet` field to `AlternativeInfo`,
populate it at construction time from `nodes`, and use `nodeSet.contains()` in all
membership test calls.
```java
public static class AlternativeInfo {
final double shareWeight;
final Path path;
final IntIndexedContainer nodes;
final IntSet nodeSet; // ADD
AlternativeInfo(Path path, double shareWeight) {
this.path = path;
this.shareWeight = shareWeight;
this.nodes = path.calcNodes();
IntScatterSet set = new IntScatterSet(nodes.size());
for (int i = 0; i < nodes.size(); i++) set.add(nodes.get(i));
this.nodeSet = set;
}
}
```
Replace all `alternative.nodes.contains(v)` and `alternatives.get(0).nodes.contains(x)` with
the corresponding `nodeSet.contains()` calls.

View file

@ -0,0 +1,255 @@
package unit;
import java.util.*;
/**
* Unit test for graphhopper-0001 / graphhopper-0002:
* AlternativeRouteCH / AlternativeRouteEdgeCH IntArrayList.contains() O(P) inside edge loop.
*
* Simulates the sharedDistanceWithShortest() pattern:
* for each edge (E edges):
* nodeList.contains(baseNode) // O(P) linear scan on slow path
* nodeList.contains(adjNode) // O(P) linear scan on slow path
*
* Compile: javac -d . AlternativeRouteCHNodesContainsAlgorithm.java
* Run: java -ea unit.AlternativeRouteCHNodesContainsAlgorithm
*/
public class AlternativeRouteCHNodesContainsAlgorithm {
static int checkCount = 0;
static void check(String desc, boolean cond) {
checkCount++;
if (!cond) {
System.out.println("FAIL [" + checkCount + "]: " + desc);
} else {
System.out.println("PASS [" + checkCount + "]: " + desc);
}
}
// -------------------------------------------------------------------------
// Slow path: simulate IntArrayList membership linear scan O(P)
// -------------------------------------------------------------------------
/** Simulated ArrayList-backed node list (mirrors IntArrayList) */
static class SlowNodeList {
final int[] data;
int ops = 0;
SlowNodeList(int[] nodes) {
this.data = nodes;
}
boolean contains(int v) {
for (int n : data) {
ops++;
if (n == v) return true;
}
return false;
}
}
static long sharedDistanceSlow(int[] pathNodes, int[] edgeBase, int[] edgeAdj) {
SlowNodeList nodeList = new SlowNodeList(pathNodes);
long sharedCount = 0;
for (int i = 0; i < edgeBase.length; i++) {
if (nodeList.contains(edgeBase[i]) && nodeList.contains(edgeAdj[i])) {
sharedCount++;
}
}
return nodeList.ops;
}
// -------------------------------------------------------------------------
// Fast path: simulate IntScatterSet membership hash lookup O(1)
// -------------------------------------------------------------------------
static class FastNodeSet {
final Set<Integer> data;
int ops = 0;
FastNodeSet(int[] nodes) {
data = new HashSet<>(nodes.length * 2);
for (int n : nodes) data.add(n);
}
boolean contains(int v) {
ops++;
return data.contains(v);
}
}
static long sharedDistanceFast(int[] pathNodes, int[] edgeBase, int[] edgeAdj) {
FastNodeSet nodeSet = new FastNodeSet(pathNodes);
long sharedCount = 0;
for (int i = 0; i < edgeBase.length; i++) {
if (nodeSet.contains(edgeBase[i]) && nodeSet.contains(edgeAdj[i])) {
sharedCount++;
}
}
return nodeSet.ops;
}
// -------------------------------------------------------------------------
// nodesInCurrentAlternativeSetContains pattern:
// for each alternative: nodeList.contains(v)
// called for each edge endpoint (2 × E calls), each doing O(P) scan
// -------------------------------------------------------------------------
static long nodesInAltSetSlow(int[][] altNodeArrays, int[] edgeBase, int[] edgeAdj) {
SlowNodeList[] lists = new SlowNodeList[altNodeArrays.length];
for (int i = 0; i < altNodeArrays.length; i++) {
lists[i] = new SlowNodeList(altNodeArrays[i]);
}
long totalOps = 0;
for (int i = 0; i < edgeBase.length; i++) {
boolean baseFound = false;
for (SlowNodeList list : lists) {
if (list.contains(edgeBase[i])) { baseFound = true; break; }
}
if (baseFound) {
for (SlowNodeList list : lists) {
list.contains(edgeAdj[i]);
}
}
}
for (SlowNodeList list : lists) totalOps += list.ops;
return totalOps;
}
static long nodesInAltSetFast(int[][] altNodeArrays, int[] edgeBase, int[] edgeAdj) {
FastNodeSet[] sets = new FastNodeSet[altNodeArrays.length];
for (int i = 0; i < altNodeArrays.length; i++) {
sets[i] = new FastNodeSet(altNodeArrays[i]);
}
long totalOps = 0;
for (int i = 0; i < edgeBase.length; i++) {
boolean baseFound = false;
for (FastNodeSet set : sets) {
if (set.contains(edgeBase[i])) { baseFound = true; break; }
}
if (baseFound) {
for (FastNodeSet set : sets) {
set.contains(edgeAdj[i]);
}
}
}
for (FastNodeSet set : sets) totalOps += set.ops;
return totalOps;
}
// -------------------------------------------------------------------------
// Build test data
// -------------------------------------------------------------------------
/** Build a path of N nodes (linear: 0→1→2→...→N-1) */
static int[] buildPathNodes(int n) {
int[] nodes = new int[n];
for (int i = 0; i < n; i++) nodes[i] = i;
return nodes;
}
/**
* Build E edges where half share path nodes (from front of path) and
* half are outside the path (node ids >= n).
*/
static int[][] buildEdges(int e, int n) {
int[] base = new int[e];
int[] adj = new int[e];
for (int i = 0; i < e; i++) {
if (i < e / 2) {
// edge between two path nodes both contained
base[i] = i % n;
adj[i] = (i + 1) % n;
} else {
// edge outside the path not contained
base[i] = n + i;
adj[i] = n + i + 1;
}
}
return new int[][]{base, adj};
}
public static void main(String[] args) {
System.out.println("=== graphhopper-0001/0002: AlternativeRouteCH nodes.contains() ===\n");
// --- Test 1: sharedDistanceWithShortest small N to verify correctness ---
{
int P = 10, E = 10;
int[] path = buildPathNodes(P);
int[][] edges = buildEdges(E, P);
long slowOps = sharedDistanceSlow(path, edges[0], edges[1]);
long fastOps = sharedDistanceFast(path, edges[0], edges[1]);
check("small: slow_ops > 0", slowOps > 0);
check("small: fast_ops > 0", fastOps > 0);
check("small: slow >= fast", slowOps >= fastOps);
}
// --- Test 2: sharedDistanceWithShortest large N to measure ratio ---
{
int P = 800, E = 1000;
int[] path = buildPathNodes(P);
int[][] edges = buildEdges(E, P);
long slowOps = sharedDistanceSlow(path, edges[0], edges[1]);
long fastOps = sharedDistanceFast(path, edges[0], edges[1]);
// Slow: each .contains() scans up to P=800 nodes; 2 calls per edge × E edges
// worst-case slow_ops 2 × E × P / 2 (half edges found early, half scan all)
// minimum triangular-ish: slow_ops >> fast_ops by factor of P
long expectedMinSlowOps = (long) E * P / 4; // conservative lower bound
double ratio = (double) slowOps / fastOps;
check("large: slow_ops >= E*P/4 (" + slowOps + " >= " + expectedMinSlowOps + ")",
slowOps >= expectedMinSlowOps);
check("large: fast_ops <= 2*E (" + fastOps + " <= " + (2L * E) + ")",
fastOps <= 2L * E);
check("large: ratio >= 10x (actual " + String.format("%.1f", ratio) + "x)",
ratio >= 10.0);
System.out.println(" slow_ops=" + slowOps + " fast_ops=" + fastOps
+ " ratio=" + String.format("%.0f", ratio) + "x");
}
// --- Test 3: nodesInCurrentAlternativeSetContains 3 alternatives ---
{
int P = 600, E = 800, A = 3;
int[][] altNodes = new int[A][];
for (int i = 0; i < A; i++) altNodes[i] = buildPathNodes(P + i * 50);
int[][] edges = buildEdges(E, P);
long slowOps = nodesInAltSetSlow(altNodes, edges[0], edges[1]);
long fastOps = nodesInAltSetFast(altNodes, edges[0], edges[1]);
double ratio = (double) slowOps / fastOps;
check("altset: slow_ops > 0 (" + slowOps + ")", slowOps > 0);
check("altset: fast_ops > 0 (" + fastOps + ")", fastOps > 0);
check("altset: ratio >= 10x (actual " + String.format("%.1f", ratio) + "x)",
ratio >= 10.0);
System.out.println(" slow_ops=" + slowOps + " fast_ops=" + fastOps
+ " ratio=" + String.format("%.0f", ratio) + "x (A=" + A + ")");
}
// --- Test 4: verify correctness both paths return same shared count ---
{
int P = 20, E = 20;
int[] path = buildPathNodes(P);
int[][] edges = buildEdges(E, P);
// Run both paths and count how many edges are "shared" (both endpoints in path)
SlowNodeList slowList = new SlowNodeList(path);
FastNodeSet fastSet = new FastNodeSet(path);
int slowShared = 0, fastShared = 0;
for (int i = 0; i < E; i++) {
if (slowList.contains(edges[0][i]) && slowList.contains(edges[1][i])) slowShared++;
if (fastSet.contains(edges[0][i]) && fastSet.contains(edges[1][i])) fastShared++;
}
check("correctness: slow and fast agree on shared count ("
+ slowShared + " == " + fastShared + ")", slowShared == fastShared);
}
System.out.println("\n" + checkCount + "/" + checkCount + " checks complete");
}
}

View file

@ -0,0 +1,30 @@
# OSRM — CWE-407 Scan Result: CLEAN
## Scan Date
2026-03-27
## Files Scanned
- `src/engine/routing_algorithms/alternative_path_ch.cpp`
- `src/engine/routing_algorithms/alternative_path_mld.cpp`
- `src/engine/routing_algorithms/routing_base_ch.cpp`
- `src/engine/routing_algorithms/routing_base_mld.cpp`
- `src/engine/routing_algorithms/shortest_path.cpp`
- `src/contractor/` (full directory)
- `src/extractor/` (full directory)
## Findings
**No CWE-407 defects found in routing hot paths.**
OSRM explicitly uses appropriate O(1) data structures:
- `alternative_path_ch.cpp` line 638: `std::unordered_set<NodeID> nodes_in_path` — correct O(1) node membership for path sharing calculation
- `alternative_path_mld.cpp` line 292: `std::unordered_set<CellID> cells` — correct O(1) cell membership for path deduplication
- `std::find` uses found: all on small bounded vectors (trip waypoints, class names, via lists) — not in O(N) traversal loops
## Notes
OSRM's CH alternative path solver (`alternative_path_ch.cpp`) demonstrates the correct pattern
that GraphHopper's `AlternativeRouteCH.java` should follow: the shortest path node set is built
as `std::unordered_set<NodeID>` before iterating the search space, giving O(1) membership
tests during the O(E) sweep.

View file

@ -0,0 +1,79 @@
# valhalla-0001: linkclassification.cc — IsSlipLane() nested linear scan O(F×R)
## File
`src/mjolnir/linkclassification.cc`
## Severity
**MEDIUM**
## Lines Affected
- Lines 658662: `std::find(forward_nodes.begin(), forward_nodes.end(), node)` inside
`for (auto node : reverse_nodes)` loop
## Defective Code
```cpp
// IsSlipLane() — lines 648673
bool IsSlipLane(Data& data, SlipLaneInput input, double traverse_threshold) {
auto forward_nodes =
GoTowardsIntersection(input.first_node, input.fork_edge, true, traverse_threshold, data);
auto reverse_nodes =
GoTowardsIntersection(input.last_node, input.merge_edge, false, traverse_threshold, data);
// O(R × F) — for each of R reverse nodes, linear scan over F forward nodes
std::optional<uint32_t> intersection_node;
for (auto node : reverse_nodes) {
if (std::find(forward_nodes.begin(), forward_nodes.end(), node) != forward_nodes.end()) {
intersection_node = node;
break;
}
}
return intersection_node != std::nullopt;
}
```
`forward_nodes` and `reverse_nodes` are `std::vector<uint32_t>`. For each node in
`reverse_nodes` (up to R nodes), `std::find` does a linear scan over `forward_nodes`
(up to F nodes). Total work: O(R × F).
Note: `GoTowardsIntersection` already uses an `std::unordered_set<size_t>` for its own
visited tracking, so visited tracking is O(1) there — but the intersection check reverts
to O(N²).
`IsSlipLane` is called during graph tile building (`mjolnir`) for every candidate link edge
in the OSM road network. Dense urban areas may have thousands of slip lane candidates, and
the traverse threshold controls path lengths. At high thresholds both vectors can reach
hundreds of nodes.
## Complexity Analysis
| Path | Per intersection check | Call frequency | Overall |
|------|----------------------|----------------|---------|
| Slow (std::find on vector) | O(F × R) | per link edge | **O(L × F × R)** |
| Fast (unordered_set lookup) | O(R) | per link edge | **O(L × R)** |
Where L = number of link edges, F = forward path length, R = reverse path length.
At traverse_threshold = 200m and urban density, F and R can each reach ~50 nodes,
giving 2500× overhead per link check vs O(R).
## Fixed Code
```cpp
bool IsSlipLane(Data& data, SlipLaneInput input, double traverse_threshold) {
auto forward_nodes =
GoTowardsIntersection(input.first_node, input.fork_edge, true, traverse_threshold, data);
auto reverse_nodes =
GoTowardsIntersection(input.last_node, input.merge_edge, false, traverse_threshold, data);
// Build O(1) lookup set from forward_nodes
std::unordered_set<uint32_t> forward_set(forward_nodes.begin(), forward_nodes.end());
std::optional<uint32_t> intersection_node;
for (auto node : reverse_nodes) {
if (forward_set.count(node)) { // O(1) instead of O(F)
intersection_node = node;
break;
}
}
return intersection_node != std::nullopt;
}
```

View file

@ -0,0 +1,228 @@
package unit;
import java.util.*;
/**
* Unit test for valhalla-0001:
* linkclassification.cc IsSlipLane() nested linear scan O(F×R).
*
* Simulates the intersection-check loop:
* for (node : reverse_nodes) // R iterations
* std::find(forward_nodes.begin(), ..., node) // O(F) linear scan
*
* vs. the fixed version using unordered_set:
* forward_set = unordered_set(forward_nodes)
* for (node : reverse_nodes) // R iterations
* forward_set.count(node) // O(1) hash lookup
*
* Compile: javac -d . IsSlipLaneAlgorithm.java
* Run: java -ea unit.IsSlipLaneAlgorithm
*/
public class IsSlipLaneAlgorithm {
static int checkCount = 0;
static int passCount = 0;
static void check(String desc, boolean cond) {
checkCount++;
if (!cond) {
System.out.println("FAIL [" + checkCount + "]: " + desc);
} else {
passCount++;
System.out.println("PASS [" + checkCount + "]: " + desc);
}
}
// -------------------------------------------------------------------------
// Slow path: std::find linear scan over forward_nodes for each reverse node
// -------------------------------------------------------------------------
static class SlowIntersectionFinder {
int ops = 0;
boolean find(int[] forwardNodes, int target) {
for (int n : forwardNodes) {
ops++;
if (n == target) return true;
}
return false;
}
/** Returns intersection node index, or -1 if none */
int findIntersection(int[] forwardNodes, int[] reverseNodes) {
for (int node : reverseNodes) {
if (find(forwardNodes, node)) {
return node;
}
}
return -1;
}
}
// -------------------------------------------------------------------------
// Fast path: unordered_set (HashSet) O(1) lookup
// -------------------------------------------------------------------------
static class FastIntersectionFinder {
int ops = 0;
int findIntersection(int[] forwardNodes, int[] reverseNodes) {
// Build set from forward_nodes once O(F)
Set<Integer> forwardSet = new HashSet<>(forwardNodes.length * 2);
for (int n : forwardNodes) forwardSet.add(n);
for (int node : reverseNodes) {
ops++;
if (forwardSet.contains(node)) {
return node;
}
}
return -1;
}
}
// -------------------------------------------------------------------------
// Build test data: two vectors of node IDs that share a suffix
// forward_nodes: 0, 1, 2, ..., F-1
// reverse_nodes: F*2, F*2+1, ..., F*2+R-sharedSuffix-1, then F-sharedSuffix, ..., F-1
// Intersection is at node F-sharedSuffix (last shared node in forward, first in overlap)
// If sharedSuffix == 0, no intersection exists (worst-case: scan all R × F)
// -------------------------------------------------------------------------
static int[] buildForwardNodes(int F) {
int[] nodes = new int[F];
for (int i = 0; i < F; i++) nodes[i] = i;
return nodes;
}
static int[] buildReverseNodes(int F, int R, int sharedSuffix) {
// First (R - sharedSuffix) nodes are unique to reverse, rest overlap with forward tail
int[] nodes = new int[R];
int uniqueCount = R - sharedSuffix;
for (int i = 0; i < uniqueCount; i++) {
nodes[i] = F * 2 + i; // disjoint from forward
}
for (int i = 0; i < sharedSuffix; i++) {
nodes[uniqueCount + i] = F - sharedSuffix + i; // overlaps with forward tail
}
return nodes;
}
public static void main(String[] args) {
System.out.println("=== valhalla-0001: IsSlipLane forward_nodes linear scan ===\n");
// --- Test 1: correctness both paths find the same intersection ---
{
int F = 20, R = 20, shared = 5;
int[] fwd = buildForwardNodes(F);
int[] rev = buildReverseNodes(F, R, shared);
SlowIntersectionFinder slow = new SlowIntersectionFinder();
FastIntersectionFinder fast = new FastIntersectionFinder();
int slowResult = slow.findIntersection(fwd, rev);
int fastResult = fast.findIntersection(fwd, rev);
check("correctness: intersection found by both paths",
slowResult != -1 && fastResult != -1);
check("correctness: both agree on intersection node ("
+ slowResult + " == " + fastResult + ")", slowResult == fastResult);
}
// --- Test 2: correctness no intersection case ---
{
int F = 20, R = 20, shared = 0;
int[] fwd = buildForwardNodes(F);
int[] rev = buildReverseNodes(F, R, shared);
SlowIntersectionFinder slow = new SlowIntersectionFinder();
FastIntersectionFinder fast = new FastIntersectionFinder();
int slowResult = slow.findIntersection(fwd, rev);
int fastResult = fast.findIntersection(fwd, rev);
check("no-intersection: both return -1 (slow=" + slowResult + " fast=" + fastResult + ")",
slowResult == -1 && fastResult == -1);
}
// --- Test 3: complexity worst case (no intersection) ---
{
// No shared nodes slow does F×R comparisons
int F = 200, R = 200;
int[] fwd = buildForwardNodes(F);
int[] rev = buildReverseNodes(F, R, 0); // no intersection
SlowIntersectionFinder slow = new SlowIntersectionFinder();
FastIntersectionFinder fast = new FastIntersectionFinder();
slow.findIntersection(fwd, rev);
fast.findIntersection(fwd, rev);
long expectedSlowOps = (long) F * R; // scans all F for each of R reverse nodes
double ratio = (double) slow.ops / fast.ops;
check("worst-case: slow_ops == F*R ("
+ slow.ops + " == " + expectedSlowOps + ")", slow.ops == expectedSlowOps);
check("worst-case: fast_ops == R ("
+ fast.ops + " == " + R + ")", fast.ops == R);
check("worst-case: ratio >= 10x (actual " + String.format("%.1f", ratio) + "x)",
ratio >= 10.0);
System.out.println(" slow_ops=" + slow.ops + " fast_ops=" + fast.ops
+ " ratio=" + String.format("%.0f", ratio) + "x (F=" + F + " R=" + R + ")");
}
// --- Test 4: complexity early exit (intersection at start of reverse) ---
{
// Intersection at first reverse node slow does F comparisons, fast does 1
int F = 200, R = 200;
int[] fwd = buildForwardNodes(F);
// Make first reverse node = 0 (first forward node found immediately in fast,
// but slow scans forward until it finds it = position 0 = 1 op)
int[] rev = new int[R];
rev[0] = 0; // immediately in forward set
for (int i = 1; i < R; i++) rev[i] = F * 2 + i; // rest disjoint
SlowIntersectionFinder slow = new SlowIntersectionFinder();
FastIntersectionFinder fast = new FastIntersectionFinder();
int slowResult = slow.findIntersection(fwd, rev);
int fastResult = fast.findIntersection(fwd, rev);
check("early-exit: both find node 0", slowResult == 0 && fastResult == 0);
check("early-exit: slow_ops=1 (found at start of forward)",
slow.ops == 1);
check("early-exit: fast_ops=1 (hash lookup)", fast.ops == 1);
}
// --- Test 5: complexity intersection at END of forward (worst linear scan) ---
{
// Intersection at forward[F-1] slow must scan all F nodes before finding match
int F = 300, R = 100;
int[] fwd = buildForwardNodes(F);
// rev[0] = F-1 found, but only after scanning all F forward nodes (slow)
int[] rev = new int[R];
rev[0] = F - 1;
for (int i = 1; i < R; i++) rev[i] = F * 2 + i;
SlowIntersectionFinder slow = new SlowIntersectionFinder();
FastIntersectionFinder fast = new FastIntersectionFinder();
slow.findIntersection(fwd, rev);
fast.findIntersection(fwd, rev);
double ratio = (double) slow.ops / fast.ops;
check("tail-match: slow_ops == F (scanned all forward, slow=" + slow.ops + ")",
slow.ops == F);
check("tail-match: fast_ops == 1 (hash, fast=" + fast.ops + ")", fast.ops == 1);
check("tail-match: ratio >= F (" + String.format("%.0f", ratio) + "x >= " + F + "x)",
ratio >= F);
System.out.println(" slow_ops=" + slow.ops + " fast_ops=" + fast.ops
+ " ratio=" + String.format("%.0f", ratio) + "x (F=" + F + " R=" + R + ")");
}
System.out.println("\n" + passCount + "/" + checkCount + " PASS");
}
}

View file

@ -1 +1 @@
0d761adb441dac756c307319377c0ac9 undefect-cwe407-2026-03-27.pdf
c8e0e41b98a566adc3c3894c1d7d3881 undefect-cwe407-2026-03-27.pdf

View file

@ -39,8 +39,8 @@ A single well-crafted implementation serves as the genetic blueprint.
4. **Harvest Stage:** Mature implementations compile into comprehensive documentation, ready for use
Code propagates according to its kind — clean architecture begets clean implementations,
elegant solutions inspire elegant variations. The process of generating 452 validated
defect patches across 202 ecosystems in a single research wave demonstrates how truth,
elegant solutions inspire elegant variations. The process of generating 455 validated
defect patches across 204 ecosystems in a single research wave demonstrates how truth,
properly seeded, multiplies. Each tested patch validates the correctness of the original
diagnosis & extends light into new programming paradigms.
@ -159,7 +159,7 @@ the missing linkages, applied them, tested them, and benchmarked them across eve
confirmed site — compiler, routing, database, build tool, event streaming, web framework,
query optimizer, and browser runtime.
**452 sites patched. 3 deferred (PostgreSQL -0001/-0005; MongoDB -0005 IndexBounds).
**455 sites patched. 3 deferred (PostgreSQL -0001/-0005; MongoDB -0005 IndexBounds).
1 fixable-upstream (Erlang OTP). 1 fixable-pending (swipl-0003). 2 not-worth-fixing.
3 unpatched (Minecraft, Create mod). No language left behind.
@ -633,6 +633,9 @@ stacks, Spark schemas — this is the dominant build cost.
| traefik-0003 | Traefik | `pkg/config/runtime/runtime_http.go:30``slices.Contains(entryPoints)` O(R×E) per router in config loading; fix: pre-build `map[string]bool` (20×) | **PATCHED** |
| caddy-0001 | Caddy | `modules/caddyhttp/reverseproxy/``hostByHashing()` O(N) xxhash-per-upstream recalculation; fix: pre-computed hash ring | **PATCHED** |
| varnish-0001 | Varnish | `bin/varnishd/cache/cache_ban.c``BAN_CheckObject()` O(B) ban list walk per request; fix: pre-filtered active-ban set | **PATCHED** |
| graphhopper-0001 | GraphHopper | `routing/AlternativeRouteCH.java:174``IntArrayList.contains()` in edge loop for shared-distance calc; O(E×A×P) (434×) | **PATCHED** |
| graphhopper-0002 | GraphHopper | `routing/AlternativeRouteEdgeCH.java:190` — same pattern, edge-based CH variant (434×) | **PATCHED** |
| valhalla-0001 | Valhalla | `mjolnir/linkclassification.cc:659``std::find(forward_nodes)` in reverse-node loop; O(F×R) during tile build (200×) | **PATCHED** |
| ffmpeg-0001 | FFmpeg | `libavformat/utils.c``av_codec_get_tag2()` O(n) linear tag scan per codec per format probe; fix: `unordered_map<tag, codec>` (45×) | **PATCHED** |
| gstreamer-0001 | GStreamer | `gst/gstregistry.c``gst_registry_get_feature_list_by_plugin()` O(n) linear filter per factory lookup; fix: plugin→features hash (35×) | **PATCHED** |
| raylib-0001 | raylib | `src/rtext.c``GetGlyphIndex()` O(G) linear scan per codepoint per text draw call; fix: `unordered_map<codepoint, index>` | **PATCHED** |
@ -725,7 +728,7 @@ where D is the depth of the diamond chain. For a diamond of depth 10, that is 2^
1,024 redundant node visits per edge check. Large modpacks produce diamond dependency
chains with depths in this range.
**452 sites patched. 3 deferred (PostgreSQL -0001/-0005; MongoDB -0005 IndexBounds). 1 fixable-upstream (Erlang OTP — sltab patch). 1 fixable-pending (swipl-0003 attr_unify_hook). 2 not-worth-fixing. 3 unpatched (Minecraft, Create mod). 5 CLEAN (WireGuard-tools, Solana, git, JGit, Dask).**
**455 sites patched. 3 deferred (PostgreSQL -0001/-0005; MongoDB -0005 IndexBounds). 1 fixable-upstream (Erlang OTP — sltab patch). 1 fixable-pending (swipl-0003 attr_unify_hook). 2 not-worth-fixing. 3 unpatched (Minecraft, Create mod). 7 CLEAN (WireGuard-tools, Solana, git, JGit, Dask, OSRM, Buck2).**
---
@ -1188,6 +1191,24 @@ resolution is worth verifying.
**Caddy** — written in Go; Go compiler is already confirmed clean. Caddy's own routing
graph uses Go maps throughout. Low risk.
**GraphHopper — graphhopper-0001/0002 (HIGH, 434×)**
GraphHopper's alternative route search (`AlternativeRouteCH` and `AlternativeRouteEdgeCH`) stores
the node list of each candidate path as an `IntArrayList` and calls `.contains()` on it inside the
edge-iteration loop used to compute shared distance with the shortest path. Because `IntArrayList.contains()`
is a linear scan, each of E edge evaluations costs O(P) per alternative path, giving O(E × A × P) total.
At P=800, E=1000, A=3 this is over 2.4 million comparisons versus 3,000 with a hash set (434×). The fix
is to augment `AlternativeInfo` with an `IntScatterSet nodeSet` built at construction time and use O(1)
hash lookups everywhere. OSRM, notably, already does this correctly: `alternative_path_ch.cpp` builds
`std::unordered_set<NodeID> nodes_in_path` before the search space sweep — the correct pattern.
**Valhalla — valhalla-0001 (MEDIUM, 200×)**
Valhalla's `IsSlipLane()` in `mjolnir/linkclassification.cc` contains a nested linear scan to find the
intersection of two traversal vectors — O(F × R) — called during graph tile building for every link-edge
candidate in OSM. Fix: build `std::unordered_set<uint32_t> forward_set(forward_nodes.begin(), forward_nodes.end())`
before the reverse-node loop and replace `std::find(...)` with `forward_set.count(node)` — O(1).
### 8.3 GeoIP and Geographic Routing
This is the most subtle third-order effect.