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
264
defects/odl/unit/OdlGroupRegistryTest.java
Normal file
264
defects/odl/unit/OdlGroupRegistryTest.java
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
package unit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Unit test for ODL-001: DevicesGroupRegistry CWE-407 defect.
|
||||
*
|
||||
* Models both defective (ArrayList/O(N)) and fixed (HashSet/O(1))
|
||||
* implementations of DevicesGroupRegistry and measures comparison counts
|
||||
* using an instrumented counter — no wall-clock timing needed.
|
||||
*
|
||||
* Defect: isGroupPresent() calls ArrayList.contains() which does a linear scan.
|
||||
* Called from the outer reconciliation loop over G groups with N already-tracked
|
||||
* groups per node, producing O(G×N) total comparisons per switch reconnect.
|
||||
*
|
||||
* Fix: Replace List<Uint32> with Set<Uint32> (HashSet). contains() becomes O(1).
|
||||
*/
|
||||
public class OdlGroupRegistryTest {
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Instrumented counter — incremented by every element comparison
|
||||
// -----------------------------------------------------------------------
|
||||
static final AtomicLong comparisonCounter = new AtomicLong(0);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Instrumented value type (stand-in for Uint32)
|
||||
// -----------------------------------------------------------------------
|
||||
static class TrackedId {
|
||||
final long value;
|
||||
|
||||
TrackedId(long value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
comparisonCounter.incrementAndGet();
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof TrackedId)) return false;
|
||||
return value == ((TrackedId) o).value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// Standard hash — NOT instrumented; only equals() counts comparisons.
|
||||
return Long.hashCode(value);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Defective registry — ArrayList per node (O(N) contains)
|
||||
// -----------------------------------------------------------------------
|
||||
static class DefectiveRegistry {
|
||||
private final Map<String, List<TrackedId>> deviceGroupMapping = new ConcurrentHashMap<>();
|
||||
|
||||
public boolean isGroupPresent(String nodeId, TrackedId groupId) {
|
||||
List<TrackedId> groups = deviceGroupMapping.get(nodeId);
|
||||
return groups != null && groups.contains(groupId); // O(N) linear scan
|
||||
}
|
||||
|
||||
public void storeGroup(String nodeId, TrackedId groupId) {
|
||||
deviceGroupMapping.computeIfAbsent(nodeId, k -> new ArrayList<>()).add(groupId);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Fixed registry — HashSet per node (O(1) contains) // CWE-407 fix
|
||||
// -----------------------------------------------------------------------
|
||||
static class FixedRegistry {
|
||||
private final Map<String, Set<TrackedId>> deviceGroupMapping = new ConcurrentHashMap<>();
|
||||
|
||||
public boolean isGroupPresent(String nodeId, TrackedId groupId) {
|
||||
Set<TrackedId> groups = deviceGroupMapping.get(nodeId);
|
||||
return groups != null && groups.contains(groupId); // O(1) hash lookup
|
||||
}
|
||||
|
||||
public void storeGroup(String nodeId, TrackedId groupId) {
|
||||
deviceGroupMapping.computeIfAbsent(nodeId, k -> new HashSet<>()).add(groupId); // CWE-407 fix
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/** Pre-populate a DefectiveRegistry with N already-tracked groups for nodeId. */
|
||||
static DefectiveRegistry buildDefective(String nodeId, int n) {
|
||||
DefectiveRegistry reg = new DefectiveRegistry();
|
||||
for (int i = 0; i < n; i++) {
|
||||
reg.storeGroup(nodeId, new TrackedId(i));
|
||||
}
|
||||
return reg;
|
||||
}
|
||||
|
||||
/** Pre-populate a FixedRegistry with N already-tracked groups for nodeId. */
|
||||
static FixedRegistry buildFixed(String nodeId, int n) {
|
||||
FixedRegistry reg = new FixedRegistry();
|
||||
for (int i = 0; i < n; i++) {
|
||||
reg.storeGroup(nodeId, new TrackedId(i));
|
||||
}
|
||||
return reg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate the reconciliation loop: for each of G groups-to-install,
|
||||
* call isGroupPresent() using a TrackedId that is NOT present (worst-case
|
||||
* for ArrayList — must scan full list before returning false).
|
||||
*
|
||||
* Returns the number of equals() comparisons recorded.
|
||||
*/
|
||||
static long runDefectiveReconciliation(DefectiveRegistry reg, String nodeId, int g) {
|
||||
comparisonCounter.set(0);
|
||||
long absent = 1_000_000L; // ids that do not exist in the registry
|
||||
for (int i = 0; i < g; i++) {
|
||||
reg.isGroupPresent(nodeId, new TrackedId(absent + i));
|
||||
}
|
||||
return comparisonCounter.get();
|
||||
}
|
||||
|
||||
static long runFixedReconciliation(FixedRegistry reg, String nodeId, int g) {
|
||||
comparisonCounter.set(0);
|
||||
long absent = 1_000_000L;
|
||||
for (int i = 0; i < g; i++) {
|
||||
reg.isGroupPresent(nodeId, new TrackedId(absent + i));
|
||||
}
|
||||
return comparisonCounter.get();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Test methods
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* testDefectiveIsQuadratic:
|
||||
* With G=200 installs and N=200 tracked, the defective ArrayList path
|
||||
* must produce at least G*N/2 comparisons (i.e. at least N comparisons
|
||||
* per miss for half the cases on average — in practice all N because
|
||||
* the id is absent).
|
||||
*/
|
||||
static void testDefectiveIsQuadratic() {
|
||||
int G = 200, N = 200;
|
||||
String node = "openflow:1";
|
||||
DefectiveRegistry reg = buildDefective(node, N);
|
||||
long comparisons = runDefectiveReconciliation(reg, node, G);
|
||||
long expected = (long) G * N; // each miss scans all N entries
|
||||
assert comparisons >= expected :
|
||||
"testDefectiveIsQuadratic FAIL: expected >= " + expected + " comparisons, got " + comparisons;
|
||||
System.out.printf(" testDefectiveIsQuadratic PASS G=%d N=%d comparisons=%d (expected>=%d)%n",
|
||||
G, N, comparisons, expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* testFixedIsLinear:
|
||||
* With G=200 installs and N=200 tracked, the fixed HashSet path must
|
||||
* produce at most G*2 comparisons (each hash-bucket lookup may hit at
|
||||
* most a handful of equals() calls in a well-distributed set; in practice
|
||||
* usually 0 or 1 for absent keys with no hash collisions).
|
||||
*
|
||||
* Upper bound: G * 4 comparisons — very generous for a 200-entry HashSet.
|
||||
*/
|
||||
static void testFixedIsLinear() {
|
||||
int G = 200, N = 200;
|
||||
String node = "openflow:1";
|
||||
FixedRegistry reg = buildFixed(node, N);
|
||||
long comparisons = runFixedReconciliation(reg, node, G);
|
||||
long upperBound = (long) G * 4;
|
||||
assert comparisons <= upperBound :
|
||||
"testFixedIsLinear FAIL: expected <= " + upperBound + " comparisons, got " + comparisons;
|
||||
System.out.printf(" testFixedIsLinear PASS G=%d N=%d comparisons=%d (expected<=%d)%n",
|
||||
G, N, comparisons, upperBound);
|
||||
}
|
||||
|
||||
/**
|
||||
* testRatioAtScale:
|
||||
* Measures defective vs fixed comparison counts at G=200, N=200 and
|
||||
* asserts the ratio is at least 20x, confirming the algorithmic
|
||||
* complexity improvement.
|
||||
*/
|
||||
static void testRatioAtScale() {
|
||||
int G = 200, N = 200;
|
||||
String node = "openflow:1";
|
||||
|
||||
DefectiveRegistry defReg = buildDefective(node, N);
|
||||
long defectiveCount = runDefectiveReconciliation(defReg, node, G);
|
||||
|
||||
FixedRegistry fixReg = buildFixed(node, N);
|
||||
long fixedCount = runFixedReconciliation(fixReg, node, G);
|
||||
|
||||
// Avoid division by zero: fixed may be 0 comparisons (all hash misses with no collisions)
|
||||
double ratio = fixedCount > 0 ? (double) defectiveCount / fixedCount : defectiveCount;
|
||||
|
||||
assert ratio >= 20.0 :
|
||||
"testRatioAtScale FAIL: ratio=" + ratio + " (defective=" + defectiveCount
|
||||
+ " fixed=" + fixedCount + "), expected >= 20x";
|
||||
System.out.printf(" testRatioAtScale PASS defective=%d fixed=%d ratio=%.1fx%n",
|
||||
defectiveCount, fixedCount, ratio);
|
||||
}
|
||||
|
||||
/**
|
||||
* testCorrectnessDefective:
|
||||
* Verifies that the defective implementation still produces correct
|
||||
* boolean results — the defect is performance only, not correctness.
|
||||
*/
|
||||
static void testCorrectnessDefective() {
|
||||
String node = "openflow:1";
|
||||
DefectiveRegistry reg = new DefectiveRegistry();
|
||||
TrackedId g1 = new TrackedId(10);
|
||||
TrackedId g2 = new TrackedId(20);
|
||||
TrackedId g3 = new TrackedId(30);
|
||||
|
||||
reg.storeGroup(node, g1);
|
||||
reg.storeGroup(node, g2);
|
||||
|
||||
comparisonCounter.set(0);
|
||||
assert reg.isGroupPresent(node, new TrackedId(10)) : "testCorrectnessDefective FAIL: g1 should be present";
|
||||
assert reg.isGroupPresent(node, new TrackedId(20)) : "testCorrectnessDefective FAIL: g2 should be present";
|
||||
assert !reg.isGroupPresent(node, new TrackedId(30)) : "testCorrectnessDefective FAIL: g3 should be absent";
|
||||
assert !reg.isGroupPresent("openflow:2", new TrackedId(10)) : "testCorrectnessDefective FAIL: wrong node";
|
||||
|
||||
System.out.println(" testCorrectnessDefective PASS present/absent/wrong-node all correct");
|
||||
}
|
||||
|
||||
/**
|
||||
* testCorrectnessFixed:
|
||||
* Same correctness assertions for the fixed HashSet implementation.
|
||||
*/
|
||||
static void testCorrectnessFixed() {
|
||||
String node = "openflow:1";
|
||||
FixedRegistry reg = new FixedRegistry();
|
||||
reg.storeGroup(node, new TrackedId(10));
|
||||
reg.storeGroup(node, new TrackedId(20));
|
||||
|
||||
comparisonCounter.set(0);
|
||||
assert reg.isGroupPresent(node, new TrackedId(10)) : "testCorrectnessFixed FAIL: g1 should be present";
|
||||
assert reg.isGroupPresent(node, new TrackedId(20)) : "testCorrectnessFixed FAIL: g2 should be present";
|
||||
assert !reg.isGroupPresent(node, new TrackedId(30)) : "testCorrectnessFixed FAIL: g3 should be absent";
|
||||
assert !reg.isGroupPresent("openflow:2", new TrackedId(10)) : "testCorrectnessFixed FAIL: wrong node";
|
||||
|
||||
System.out.println(" testCorrectnessFixed PASS present/absent/wrong-node all correct");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Main
|
||||
// -----------------------------------------------------------------------
|
||||
public static void main(String[] args) {
|
||||
System.out.println("ODL-001 DevicesGroupRegistry CWE-407 unit tests");
|
||||
System.out.println("================================================");
|
||||
|
||||
testCorrectnessDefective();
|
||||
testCorrectnessFixed();
|
||||
testDefectiveIsQuadratic();
|
||||
testFixedIsLinear();
|
||||
testRatioAtScale();
|
||||
|
||||
System.out.println("================================================");
|
||||
System.out.println("ALL TESTS PASSED");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue