B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections. Squash of 94 local commits onto remote master.
187 lines
7.1 KiB
Java
187 lines
7.1 KiB
Java
package unit;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.Collections;
|
||
|
||
/**
|
||
* Unit tests modelling CWE-407 defect in GNU Octave:
|
||
*
|
||
* octave-0001 — data.cc and numeric/max.cc: vecdim is already sorted by
|
||
* std::sort, but the subsequent loop uses std::find (O(|vecdim|))
|
||
* instead of std::binary_search (O(log |vecdim|)).
|
||
*
|
||
* Modelled as: outer loop over ndims=64 dimensions, inner
|
||
* ArrayList.contains() (unsorted scan, O(|vecdim|)) vs
|
||
* Collections.binarySearch() on a sorted list (O(log |vecdim|)).
|
||
*
|
||
* Pure Java stdlib, instrumented operation counts.
|
||
*/
|
||
public class OctaveVecdimTest {
|
||
|
||
/**
|
||
* Simulate defective path: ndims iterations, std::find scan per iteration.
|
||
* vecdim has n entries drawn from [0, ndims).
|
||
*/
|
||
static long vecdimDefective(int ndims, int vecdimSize) {
|
||
ArrayList<Integer> vecdim = new ArrayList<>();
|
||
// Populate sorted vecdim with every other dim (representative subset)
|
||
for (int i = 0; i < vecdimSize; i++) {
|
||
vecdim.add(i * (ndims / vecdimSize));
|
||
}
|
||
// Mimics std::sort already done — list is sorted
|
||
Collections.sort(vecdim);
|
||
|
||
long ops = 0;
|
||
for (int i = 0; i < ndims; i++) {
|
||
// O(|vecdim|) — mirrors std::find(vecdim.begin(), vecdim.end(), i)
|
||
boolean found = false;
|
||
for (int j = 0; j < vecdim.size(); j++) {
|
||
ops++;
|
||
if (vecdim.get(j).equals(i)) { found = true; break; }
|
||
// std::find does not exploit sort order; keep scanning past hits
|
||
// would continue, but we break on first match (same as std::find)
|
||
}
|
||
// remaining iterations for elements not in vecdim scan to the end
|
||
if (!found) {
|
||
// already counted full scan above — no extra ops needed
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
/**
|
||
* Simulate defective path with full scan (no early exit) to model worst
|
||
* case where dims not in vecdim cause a full O(|vecdim|) scan.
|
||
*/
|
||
static long vecdimDefectiveWorstCase(int ndims, int vecdimSize) {
|
||
ArrayList<Integer> vecdim = new ArrayList<>();
|
||
for (int i = 0; i < vecdimSize; i++) {
|
||
vecdim.add(i * (ndims / vecdimSize));
|
||
}
|
||
Collections.sort(vecdim);
|
||
|
||
long ops = 0;
|
||
for (int i = 0; i < ndims; i++) {
|
||
// Full O(|vecdim|) scan regardless (std::find scans all when not found)
|
||
for (int j = 0; j < vecdim.size(); j++) {
|
||
ops++;
|
||
if (vecdim.get(j).equals(i)) break;
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
/**
|
||
* Simulate fixed path: ndims iterations, Collections.binarySearch per iteration.
|
||
* Each binarySearch is O(log |vecdim|).
|
||
*/
|
||
static long vecdimFixed(int ndims, int vecdimSize) {
|
||
ArrayList<Integer> vecdim = new ArrayList<>();
|
||
for (int i = 0; i < vecdimSize; i++) {
|
||
vecdim.add(i * (ndims / vecdimSize));
|
||
}
|
||
Collections.sort(vecdim);
|
||
|
||
long ops = 0;
|
||
for (int i = 0; i < ndims; i++) {
|
||
// O(log |vecdim|) — mirrors std::binary_search(vecdim.begin(), vecdim.end(), i)
|
||
int lo = 0, hi = vecdim.size() - 1;
|
||
while (lo <= hi) {
|
||
ops++;
|
||
int mid = (lo + hi) >>> 1;
|
||
int cmp = vecdim.get(mid).compareTo(i);
|
||
if (cmp < 0) lo = mid + 1;
|
||
else if (cmp > 0) hi = mid - 1;
|
||
else break;
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Test methods
|
||
// -----------------------------------------------------------------------
|
||
|
||
/**
|
||
* Defective op count must grow as ndims × |vecdim|.
|
||
* Confirm: opsLarge > opsSmall × 3 when scaling ndims from 16 to 64.
|
||
*/
|
||
static void testDefectiveCountGrows() {
|
||
long opsSmall = vecdimDefectiveWorstCase(16, 8);
|
||
long opsFull = vecdimDefectiveWorstCase(64, 32);
|
||
assert opsFull > opsSmall * 3
|
||
: "octave-0001: expected super-linear growth, opsSmall=" + opsSmall
|
||
+ " opsFull=" + opsFull;
|
||
System.out.printf("PASS testDefectiveCountGrows: opsSmall=%d opsFull=%d ratio=%.1fx%n",
|
||
opsSmall, opsFull, (double) opsFull / opsSmall);
|
||
}
|
||
|
||
/**
|
||
* Fixed op count must be O(ndims × log|vecdim|).
|
||
* At ndims=64, vecdimSize=32: ops <= ndims * ceil(log2(32)+1) = 64*6 = 384.
|
||
*/
|
||
static void testFixedCountLogBound() {
|
||
int ndims = 64, vecdimSize = 32;
|
||
long ops = vecdimFixed(ndims, vecdimSize);
|
||
long bound = (long) ndims * (long) Math.ceil(Math.log(vecdimSize) / Math.log(2) + 1);
|
||
assert ops <= bound
|
||
: "octave-0001: fixed ops=" + ops + " exceeded log bound=" + bound;
|
||
System.out.printf("PASS testFixedCountLogBound: ops=%d bound=%d%n", ops, bound);
|
||
}
|
||
|
||
/**
|
||
* Speedup ratio defective/fixed must exceed 5× at ndims=64, vecdimSize=32.
|
||
*/
|
||
static void testSpeedupRatio() {
|
||
int ndims = 64, vecdimSize = 32;
|
||
long defOps = vecdimDefectiveWorstCase(ndims, vecdimSize);
|
||
long fixOps = vecdimFixed(ndims, vecdimSize);
|
||
double ratio = (double) defOps / fixOps;
|
||
assert ratio > 5.0
|
||
: "octave-0001: speedup ratio " + ratio + " not > 5x";
|
||
System.out.printf("PASS testSpeedupRatio: defective=%d fixed=%d ratio=%.1fx%n",
|
||
defOps, fixOps, ratio);
|
||
}
|
||
|
||
/**
|
||
* Correctness: defective and fixed paths agree on which dims are excluded.
|
||
* Both should exclude the same set of dims not present in vecdim.
|
||
*/
|
||
static void testCorrectnessAgreement() {
|
||
int ndims = 64, vecdimSize = 32;
|
||
ArrayList<Integer> vecdim = new ArrayList<>();
|
||
for (int i = 0; i < vecdimSize; i++) {
|
||
vecdim.add(i * (ndims / vecdimSize));
|
||
}
|
||
Collections.sort(vecdim);
|
||
|
||
ArrayList<Integer> excludedByFind = new ArrayList<>();
|
||
ArrayList<Integer> excludedByBsearch = new ArrayList<>();
|
||
|
||
for (int i = 0; i < ndims; i++) {
|
||
// std::find equivalent
|
||
if (!vecdim.contains(i)) excludedByFind.add(i);
|
||
// std::binary_search equivalent
|
||
if (Collections.binarySearch(vecdim, i) < 0) excludedByBsearch.add(i);
|
||
}
|
||
|
||
assert excludedByFind.equals(excludedByBsearch)
|
||
: "octave-0001: correctness mismatch: find=" + excludedByFind
|
||
+ " bsearch=" + excludedByBsearch;
|
||
System.out.printf("PASS testCorrectnessAgreement: %d dims excluded (identical)%n",
|
||
excludedByFind.size());
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Main
|
||
// -----------------------------------------------------------------------
|
||
|
||
public static void main(String[] args) {
|
||
System.out.println("=== OctaveVecdimTest ===");
|
||
testDefectiveCountGrows();
|
||
testFixedCountLogBound();
|
||
testSpeedupRatio();
|
||
testCorrectnessAgreement();
|
||
System.out.println("All tests passed.");
|
||
}
|
||
}
|