java-topology/defects/flightgear-0002/test/test_flightgear_0002.py
russell@unturf.com e92597895e flightgear: 3 CWE-407 defects, MOAD 0002-0005 CLEAN
flightgear-0001: addSegment/addParking std::find on m_nodes vector O(S*N), 50x
flightgear-0002: Dijkstra findShortestRoute vector unvisited O(V^2), 13.6x
flightgear-0003: A* airway search linear findInOpen O(E*V), 4x
2026-03-31 12:43:46 -04:00

147 lines
4 KiB
Python

"""
flightgear-0002: FGGroundNetwork::findShortestRoute Dijkstra O(V^2)
DEFECT: vector as unvisited set, linear min-scan + std::remove per iteration
FIX: priority queue O((V+E) log V)
Simulates airport ground network Dijkstra pathfinding.
Large airports have 200-500 taxiway nodes.
"""
import time
import heapq
import random
import math
def build_ground_network(n_nodes):
"""Build a random ground network graph (adjacency list).
Each node connects to 2-4 neighbors, simulating taxiway topology.
"""
adj = {i: [] for i in range(n_nodes)}
for i in range(n_nodes):
for offset in [1, 3, 7]:
j = (i + offset) % n_nodes
w = random.uniform(50.0, 500.0) # edge weight (meters)
adj[i].append((j, w))
adj[j].append((i, w))
return adj
def dijkstra_unpatched(adj, start, end):
"""Original: vector unvisited + linear min-scan + linear remove."""
INF = float('inf')
n = len(adj)
dist_map = {i: INF for i in range(n)}
prev_map = {i: None for i in range(n)}
unvisited = list(range(n))
dist_map[start] = 0.0
ops = 0
while unvisited:
# Linear scan to find minimum (O(V) per iteration)
best = unvisited[0]
for node in unvisited:
ops += 1
if dist_map[node] < dist_map[best]:
best = node
# Linear remove (O(V) per iteration)
unvisited.remove(best)
ops += len(unvisited)
if best == end:
break
if dist_map[best] == INF:
break
for neighbor, weight in adj[best]:
alt = dist_map[best] + weight
if alt < dist_map[neighbor]:
dist_map[neighbor] = alt
prev_map[neighbor] = best
return dist_map[end], ops
def dijkstra_patched(adj, start, end):
"""Patched: priority queue + visited set."""
INF = float('inf')
n = len(adj)
dist_map = {i: INF for i in range(n)}
visited = set()
dist_map[start] = 0.0
pq = [(0.0, start)]
ops = 0
while pq:
d, best = heapq.heappop(pq)
ops += 1
if best in visited:
continue
visited.add(best)
if best == end:
break
for neighbor, weight in adj[best]:
alt = d + weight
ops += 1
if alt < dist_map[neighbor]:
dist_map[neighbor] = alt
heapq.heappush(pq, (alt, neighbor))
return dist_map[end], ops
def test_correctness():
"""Verify both produce same shortest distance."""
random.seed(42)
adj = build_ground_network(100)
d_unpatched, _ = dijkstra_unpatched(adj, 0, 50)
d_patched, _ = dijkstra_patched(adj, 0, 50)
assert abs(d_unpatched - d_patched) < 1e-6, \
f"Distances differ: {d_unpatched} vs {d_patched}"
print(f"PASS correctness: distance={d_unpatched:.2f}")
def test_performance():
"""Benchmark at V=400 (large airport ground network)."""
random.seed(42)
n = 400
adj = build_ground_network(n)
iters = 10
pairs = [(random.randint(0, n-1), random.randint(0, n-1)) for _ in range(iters)]
t0 = time.perf_counter()
ops_u = 0
for s, e in pairs:
_, o = dijkstra_unpatched(adj, s, e)
ops_u += o
t_unpatched = time.perf_counter() - t0
t0 = time.perf_counter()
ops_p = 0
for s, e in pairs:
_, o = dijkstra_patched(adj, s, e)
ops_p += o
t_patched = time.perf_counter() - t0
ratio = t_unpatched / t_patched if t_patched > 0 else float('inf')
op_ratio = ops_u / ops_p if ops_p > 0 else float('inf')
print(f"V={n}, {iters} pathfinds")
print(f"Unpatched: {t_unpatched*1e3:.1f} ms, {ops_u} ops")
print(f"Patched: {t_patched*1e3:.1f} ms, {ops_p} ops")
print(f"Time ratio: {ratio:.1f}x")
print(f"Ops ratio: {op_ratio:.1f}x")
assert ratio > 2.0, f"Expected >2x speedup, got {ratio:.1f}x"
print(f"PASS performance: {ratio:.1f}x speedup")
if __name__ == "__main__":
test_correctness()
test_performance()