B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections. Squash of 94 local commits onto remote master.
390 lines
17 KiB
Java
390 lines
17 KiB
Java
package unit;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.IdentityHashMap;
|
|
|
|
/**
|
|
* NetworkXCyclesTest
|
|
*
|
|
* Models the CWE-407 defects across three projects:
|
|
*
|
|
* NX-001 (MEDIUM): networkx recursive_simple_cycles — B[nextnode] dedup
|
|
* Defective: B[nextnode] is an ArrayList; `not in` is O(|B[nextnode]|) per edge.
|
|
* Fixed: B[nextnode] is a HashSet; `not in` (contains) is O(1).
|
|
*
|
|
* rubocop-0001 (MEDIUM): RuboCop IgnoredNode — @ignored_nodes dedup
|
|
* Defective: @ignored_nodes is an ArrayList; identity scan O(N) per on_str call.
|
|
* Fixed: @ignored_nodes is a HashSet with identity-based hashing; O(1).
|
|
*
|
|
* solargraph-0001 (MEDIUM): Solargraph Chain — @@inference_stack dedup
|
|
* Defective: @@inference_stack is a shared ArrayList; O(D) include? per pin
|
|
* AND shared across simulated concurrent "threads" (no isolation).
|
|
* Fixed: Per-thread HashSet; O(1) contains, isolated per thread.
|
|
*
|
|
* All tests instrument operation counts explicitly — no wall-clock timing.
|
|
*/
|
|
public class NetworkXCyclesTest {
|
|
|
|
// -----------------------------------------------------------------------
|
|
// NX-001: B[nextnode] deduplication model
|
|
// Simulates the inner loop of circuit() across E edges and growing B sets.
|
|
// -----------------------------------------------------------------------
|
|
|
|
/**
|
|
* Defective: B[nextnode] is ArrayList; `not in` is O(|B[nextnode]|).
|
|
*
|
|
* Edge pattern: all edges target the same nextnode=0, with thisnode running
|
|
* from 0..numUniqueThisNodes-1. This maximises B[0] growth: after k distinct
|
|
* thisnode values have been added, the next novel thisnode scans all k entries
|
|
* before discovering it is absent. Total comparisons = 0+1+2+...+(k-1) = k*(k-1)/2.
|
|
* Subsequent repeats of already-seen thisnode values each scan until they hit,
|
|
* averaging k/2 comparisons per repeat.
|
|
*/
|
|
static long nx001_defectiveBDedup(int numDistinctSources, int numRepeats) {
|
|
ArrayList<Integer> B0 = new ArrayList<>(); // B[nextnode=0]
|
|
long comparisons = 0;
|
|
// Phase 1: insert all distinct thisnode values 0..numDistinctSources-1
|
|
for (int thisnode = 0; thisnode < numDistinctSources; thisnode++) {
|
|
// Scan full list before each new insert — O(size) miss
|
|
for (Integer existing : B0) {
|
|
comparisons++;
|
|
// won't match — thisnode not yet in list
|
|
}
|
|
B0.add(thisnode);
|
|
}
|
|
// Phase 2: repeat lookups for already-present nodes — O(position) hit
|
|
for (int rep = 0; rep < numRepeats; rep++) {
|
|
int thisnode = rep % numDistinctSources;
|
|
for (Integer existing : B0) {
|
|
comparisons++;
|
|
if (existing.equals(thisnode)) break; // hit at position thisnode
|
|
}
|
|
}
|
|
return comparisons;
|
|
}
|
|
|
|
/**
|
|
* Fixed: B[nextnode] is HashSet; `not in` (contains) is O(1).
|
|
* Same edge pattern — all operations are one hash probe each.
|
|
*/
|
|
static long nx001_fixedBDedup(int numDistinctSources, int numRepeats) {
|
|
HashSet<Integer> B0 = new HashSet<>();
|
|
long lookups = 0;
|
|
// Phase 1: insert distinct sources — O(1) contains check each
|
|
for (int thisnode = 0; thisnode < numDistinctSources; thisnode++) {
|
|
lookups++; // one O(1) hash probe — CWE-407 fix
|
|
B0.add(thisnode);
|
|
}
|
|
// Phase 2: repeat lookups — O(1) each
|
|
for (int rep = 0; rep < numRepeats; rep++) {
|
|
lookups++; // one O(1) hash probe — CWE-407 fix
|
|
}
|
|
return lookups;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// rubocop-0001: @ignored_nodes identity dedup model
|
|
// Simulates on_str firing R times with S ignored nodes accumulated.
|
|
// Nodes are modelled as Long object IDs (identity comparison via ==).
|
|
// -----------------------------------------------------------------------
|
|
|
|
/** Defective: ignored_nodes is ArrayList; identity scan O(S) per on_str call. */
|
|
static long rubocop0001_defectiveIgnoredNodes(int numStringNodes, int numIgnoredNodes) {
|
|
// Build ignored_nodes list (S entries)
|
|
ArrayList<Long> ignoredNodes = new ArrayList<>();
|
|
Long[] nodeObjects = new Long[numIgnoredNodes];
|
|
for (int i = 0; i < numIgnoredNodes; i++) {
|
|
nodeObjects[i] = (long) (i + 1_000_000); // distinct Long objects
|
|
ignoredNodes.add(nodeObjects[i]);
|
|
}
|
|
long comparisons = 0;
|
|
// Simulate on_str called R times — each call checks ignored_node?(node)
|
|
for (int r = 0; r < numStringNodes; r++) {
|
|
Long queryNode = nodeObjects[r % numIgnoredNodes]; // always a hit
|
|
// `ignored_nodes.any? { |n| n.equal?(node) }` — O(S) scan
|
|
for (Long ignored : ignoredNodes) {
|
|
comparisons++;
|
|
if (ignored == queryNode) { // identity comparison
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return comparisons;
|
|
}
|
|
|
|
/**
|
|
* Fixed: ignored_nodes is an identity-based HashSet; include?(node) is O(1).
|
|
* Java models identity-based hashing via IdentityHashMap used as a Set.
|
|
*/
|
|
static long rubocop0001_fixedIgnoredNodes(int numStringNodes, int numIgnoredNodes) {
|
|
// IdentityHashMap with dummy values models Set.new.compare_by_identity
|
|
IdentityHashMap<Long, Boolean> ignoredNodes = new IdentityHashMap<>();
|
|
Long[] nodeObjects = new Long[numIgnoredNodes];
|
|
for (int i = 0; i < numIgnoredNodes; i++) {
|
|
nodeObjects[i] = (long) (i + 1_000_000);
|
|
ignoredNodes.put(nodeObjects[i], Boolean.TRUE);
|
|
}
|
|
long lookups = 0;
|
|
for (int r = 0; r < numStringNodes; r++) {
|
|
Long queryNode = nodeObjects[r % numIgnoredNodes];
|
|
lookups++; // one O(1) identity hash lookup — CWE-407 fix
|
|
ignoredNodes.containsKey(queryNode);
|
|
}
|
|
return lookups;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// solargraph-0001: @@inference_stack isolation + dedup model
|
|
// Simulates infer_from_definitions across T concurrent "threads",
|
|
// each with D pins to process at inference depth D.
|
|
// -----------------------------------------------------------------------
|
|
|
|
/** Shared mutable state modelling @@inference_stack = [] (the defect). */
|
|
static class DefectiveInferenceStack {
|
|
final ArrayList<Long> stack = new ArrayList<>();
|
|
long comparisons = 0;
|
|
|
|
boolean include(long pinId) {
|
|
for (Long existing : stack) {
|
|
comparisons++;
|
|
if (existing.equals(pinId)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
void push(long pinId) { stack.add(pinId); }
|
|
void pop() { if (!stack.isEmpty()) stack.remove(stack.size() - 1); }
|
|
}
|
|
|
|
/** Per-thread state modelling Thread.current[:solargraph_inference_stack] (the fix). */
|
|
static class FixedInferenceStack {
|
|
// Each "thread" gets its own HashSet — thread-local isolation
|
|
final HashMap<Integer, HashSet<Long>> threadStacks = new HashMap<>();
|
|
long lookups = 0;
|
|
|
|
private HashSet<Long> stackFor(int threadId) {
|
|
return threadStacks.computeIfAbsent(threadId, k -> new HashSet<>());
|
|
}
|
|
|
|
boolean include(int threadId, long pinId) {
|
|
lookups++; // O(1) hash lookup — CWE-407 fix
|
|
return stackFor(threadId).contains(pinId);
|
|
}
|
|
void add(int threadId, long pinId) { stackFor(threadId).add(pinId); }
|
|
void delete(int threadId, long pinId) { stackFor(threadId).remove(pinId); }
|
|
}
|
|
|
|
/**
|
|
* Simulate T threads each processing D pins through the defective shared stack.
|
|
* Returns total comparisons across all threads.
|
|
*/
|
|
static long solargraph0001_defectiveStack(int numThreads, int pinsPerThread) {
|
|
DefectiveInferenceStack shared = new DefectiveInferenceStack();
|
|
// Sequential simulation: each thread pushes its pins, checks, pops
|
|
for (int t = 0; t < numThreads; t++) {
|
|
for (int p = 0; p < pinsPerThread; p++) {
|
|
long pinId = (long) t * pinsPerThread + p;
|
|
// `next if @@inference_stack.include?(pin)` — O(D) scan
|
|
shared.include(pinId);
|
|
shared.push(pinId);
|
|
}
|
|
// pop all pins for this "thread" (in defective impl they share the stack)
|
|
for (int p = 0; p < pinsPerThread; p++) {
|
|
shared.pop();
|
|
}
|
|
}
|
|
return shared.comparisons;
|
|
}
|
|
|
|
/**
|
|
* Simulate T threads each processing D pins through the fixed per-thread Set.
|
|
* Returns total lookups across all threads.
|
|
*/
|
|
static long solargraph0001_fixedStack(int numThreads, int pinsPerThread) {
|
|
FixedInferenceStack fixed = new FixedInferenceStack();
|
|
for (int t = 0; t < numThreads; t++) {
|
|
for (int p = 0; p < pinsPerThread; p++) {
|
|
long pinId = (long) t * pinsPerThread + p;
|
|
// O(1) set lookup — CWE-407 fix
|
|
fixed.include(t, pinId);
|
|
fixed.add(t, pinId);
|
|
}
|
|
for (int p = 0; p < pinsPerThread; p++) {
|
|
long pinId = (long) t * pinsPerThread + p;
|
|
fixed.delete(t, pinId);
|
|
}
|
|
}
|
|
return fixed.lookups;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 1 — NX-001: defective B-list scan > fixed B-set lookup at E=200, N=20
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void test1_nx001_BSetVsList() {
|
|
int numDistinctSources = 50;
|
|
int numRepeats = 100;
|
|
long defectOps = nx001_defectiveBDedup(numDistinctSources, numRepeats);
|
|
long fixedOps = nx001_fixedBDedup(numDistinctSources, numRepeats);
|
|
|
|
// Phase-1 defect cost: triangular 0+1+...+(k-1) = k*(k-1)/2
|
|
long expectedPhase1Defect = (long) numDistinctSources * (numDistinctSources - 1) / 2;
|
|
|
|
System.out.printf(
|
|
"test1 NX-001: distinct_sources=%d repeats=%d defect_comparisons=%d (expect phase1>=%d) fixed_lookups=%d%n",
|
|
numDistinctSources, numRepeats, defectOps, expectedPhase1Defect, fixedOps);
|
|
|
|
assert defectOps > fixedOps
|
|
: "NX-001: defective list scan must do more comparisons than set lookup; defect="
|
|
+ defectOps + " fixed=" + fixedOps;
|
|
assert defectOps >= expectedPhase1Defect
|
|
: "NX-001: defect comparisons=" + defectOps + " must be at least triangular=" + expectedPhase1Defect;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 2 — NX-001: scaling — doubling edges grows defect super-linearly
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void test2_nx001_quadraticScaling() {
|
|
// Double the number of distinct sources; repeats held constant.
|
|
// Defect phase-1 cost is k*(k-1)/2 — quadratic in k.
|
|
// Fixed cost is k + repeats — linear in k.
|
|
int repeats = 50;
|
|
int k1 = 40;
|
|
int k2 = 80; // 2x k
|
|
|
|
long d1 = nx001_defectiveBDedup(k1, repeats);
|
|
long d2 = nx001_defectiveBDedup(k2, repeats);
|
|
long f1 = nx001_fixedBDedup(k1, repeats);
|
|
long f2 = nx001_fixedBDedup(k2, repeats);
|
|
|
|
double defectGrowth = (double) d2 / Math.max(1, d1);
|
|
double fixedGrowth = (double) f2 / Math.max(1, f1);
|
|
|
|
System.out.printf(
|
|
"test2 NX-001 scaling: k1=%d k2=%d defect_growth=%.2fx fixed_growth=%.2fx%n",
|
|
k1, k2, defectGrowth, fixedGrowth);
|
|
|
|
assert defectGrowth > fixedGrowth
|
|
: "NX-001: defect should grow faster than fix when k doubles; got defect="
|
|
+ defectGrowth + " fixed=" + fixedGrowth;
|
|
assert defectGrowth > 2.0
|
|
: "NX-001: defect should grow super-linearly (quadratic), got " + defectGrowth;
|
|
assert fixedGrowth <= 2.5
|
|
: "NX-001: fixed set lookup should grow at most linearly; got " + fixedGrowth;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 3 — rubocop-0001: identity-list scan > identity-set lookup at R=500, S=50
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void test3_rubocop0001_identitySetVsList() {
|
|
int numStringNodes = 500;
|
|
int numIgnoredNodes = 50;
|
|
|
|
long defectOps = rubocop0001_defectiveIgnoredNodes(numStringNodes, numIgnoredNodes);
|
|
long fixedOps = rubocop0001_fixedIgnoredNodes(numStringNodes, numIgnoredNodes);
|
|
|
|
System.out.printf(
|
|
"test3 rubocop-0001: on_str=%d ignored=%d defect_comparisons=%d fixed_lookups=%d%n",
|
|
numStringNodes, numIgnoredNodes, defectOps, fixedOps);
|
|
|
|
assert defectOps > fixedOps
|
|
: "rubocop-0001: identity list scan must cost more than identity set lookup";
|
|
// Worst case for defect: query always hits last element → S comparisons each
|
|
// With hits cycling through all S nodes (always hits at position r%S+1 on average),
|
|
// total should be at least R comparisons.
|
|
assert defectOps >= numStringNodes
|
|
: "rubocop-0001: expected at least R=" + numStringNodes + " comparisons, got " + defectOps;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 4 — solargraph-0001: shared-stack list scan > per-thread set lookup
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void test4_solargraph0001_threadLocalSetVsSharedList() {
|
|
int numThreads = 10;
|
|
int pinsPerThread = 30;
|
|
|
|
long defectOps = solargraph0001_defectiveStack(numThreads, pinsPerThread);
|
|
long fixedOps = solargraph0001_fixedStack(numThreads, pinsPerThread);
|
|
|
|
System.out.printf(
|
|
"test4 solargraph-0001: threads=%d pins_per_thread=%d defect_comparisons=%d fixed_lookups=%d%n",
|
|
numThreads, pinsPerThread, defectOps, fixedOps);
|
|
|
|
assert defectOps > fixedOps
|
|
: "solargraph-0001: shared-list scan must do more work than per-thread set";
|
|
assert defectOps > 0
|
|
: "solargraph-0001: defect must perform at least one comparison";
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 5 — solargraph-0001: thread isolation — per-thread set never sees
|
|
// another thread's pins (no cross-contamination in fixed impl)
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void test5_solargraph0001_perThreadIsolation() {
|
|
FixedInferenceStack fixed = new FixedInferenceStack();
|
|
int numThreads = 5;
|
|
int pinsPerThread = 20;
|
|
|
|
// Each thread adds its own pins
|
|
for (int t = 0; t < numThreads; t++) {
|
|
for (int p = 0; p < pinsPerThread; p++) {
|
|
long pinId = (long) t * pinsPerThread + p;
|
|
fixed.add(t, pinId);
|
|
}
|
|
}
|
|
|
|
// Verify thread T cannot see thread T+1's pins (isolation invariant)
|
|
for (int t = 0; t < numThreads - 1; t++) {
|
|
long otherThreadPin = (long) (t + 1) * pinsPerThread; // first pin of next thread
|
|
boolean crossVisible = fixed.stackFor(t).contains(otherThreadPin);
|
|
assert !crossVisible
|
|
: "solargraph-0001: thread " + t + " must not see pin from thread " + (t+1)
|
|
+ " (pin=" + otherThreadPin + ")";
|
|
}
|
|
|
|
// Verify each thread can see its own pins
|
|
for (int t = 0; t < numThreads; t++) {
|
|
long ownPin = (long) t * pinsPerThread; // first pin of this thread
|
|
boolean selfVisible = fixed.stackFor(t).contains(ownPin);
|
|
assert selfVisible
|
|
: "solargraph-0001: thread " + t + " must be able to see its own pin (pin=" + ownPin + ")";
|
|
}
|
|
|
|
System.out.printf(
|
|
"test5 solargraph-0001 isolation: %d threads x %d pins — no cross-contamination confirmed%n",
|
|
numThreads, pinsPerThread);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Main
|
|
// -----------------------------------------------------------------------
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== NetworkXCyclesTest ===");
|
|
System.out.println("Modelling CWE-407: NX-001 + rubocop-0001 + solargraph-0001");
|
|
System.out.println();
|
|
|
|
test1_nx001_BSetVsList();
|
|
System.out.println(" PASS test1_nx001_BSetVsList");
|
|
|
|
test2_nx001_quadraticScaling();
|
|
System.out.println(" PASS test2_nx001_quadraticScaling");
|
|
|
|
test3_rubocop0001_identitySetVsList();
|
|
System.out.println(" PASS test3_rubocop0001_identitySetVsList");
|
|
|
|
test4_solargraph0001_threadLocalSetVsSharedList();
|
|
System.out.println(" PASS test4_solargraph0001_threadLocalSetVsSharedList");
|
|
|
|
test5_solargraph0001_perThreadIsolation();
|
|
System.out.println(" PASS test5_solargraph0001_perThreadIsolation");
|
|
|
|
System.out.println();
|
|
System.out.println("All 5 tests PASSED.");
|
|
}
|
|
}
|