java-topology/defects/octave/unit/OctaveLoadPathAlgorithm.java

194 lines
6.3 KiB
Java

package unit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* CWE-407 unit test: octave-0002
*
* Models Octave's load_path::add() / find_dir_info():
* initializing the load path by adding N directories one at a time.
*
* DEFECT (load-path.cc:1021-1055, 1119, 1151):
* Each add() calls find_dir_info() twice (lines 1119 + 1151).
* find_dir_info() does an O(D) linear scan over m_dir_info_list.
* For N directories: total = 2*(0+1+2+...+(N-1)) = O(N^2).
*
* FIX: maintain a HashSet<String> alongside the list for O(1) membership test.
* Total: O(N) for N-directory path initialization.
*
* Asserts: slowOps > fastOps * 5 at N=500+ (actual ratio ~250x at N=500).
*/
public class OctaveLoadPathAlgorithm {
/**
* Simulate load_path::set(N dirs) using the defective O(D^2) approach.
* Each add() calls find_dir_info() (O(D) linear scan) twice.
*
* @param N number of directories to add
* @return total string-comparison operations performed
*/
static long slow(int N) {
List<String> dirList = new ArrayList<>();
long ops = 0;
for (int i = 0; i < N; i++) {
String newDir = "dir" + i;
// First find_dir_info call: check if newDir already in list (line 1119)
boolean found = false;
for (String d : dirList) {
ops++;
if (d.equals(newDir)) {
found = true;
break;
}
}
if (!found) {
dirList.add(newDir);
}
// Second find_dir_info call: keep "." at front (line 1151)
// "." is never in the list during this simulation (cleared at start)
// but the scan still walks the whole list before reaching the end
for (String d : dirList) {
ops++;
if (d.equals(".")) {
break;
}
}
}
return ops;
}
/**
* Simulate the patched path: HashSet for O(1) membership, list for order.
*
* @param N number of directories to add
* @return total operations performed
*/
static long fast(int N) {
List<String> dirList = new ArrayList<>();
Set<String> dirSet = new HashSet<>();
long ops = 0;
for (int i = 0; i < N; i++) {
String newDir = "dir" + i;
// O(1) membership check via HashSet
ops++;
if (!dirSet.contains(newDir)) {
dirList.add(newDir);
dirSet.add(newDir);
}
// O(1) membership check for "." via HashSet
ops++;
if (dirSet.contains(".")) {
// move "." to front — O(1) check, O(D) move but rare
}
}
return ops;
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
// Test 1: N=100, ratio must be >= 5x
{
total++;
long sOps = slow(100);
long fOps = fast(100);
double ratio = (double) sOps / fOps;
boolean ok = ratio >= 5.0;
System.out.printf("Test 1 [N=100 slow=%d fast=%d ratio=%.1fx]: %s%n",
sOps, fOps, ratio, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 2: N=500, ratio must be >= 50x
{
total++;
long sOps = slow(500);
long fOps = fast(500);
double ratio = (double) sOps / fOps;
boolean ok = ratio >= 50.0;
System.out.printf("Test 2 [N=500 slow=%d fast=%d ratio=%.1fx]: %s%n",
sOps, fOps, ratio, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 3: N=1000, ratio must be >= 100x
{
total++;
long sOps = slow(1000);
long fOps = fast(1000);
double ratio = (double) sOps / fOps;
boolean ok = ratio >= 100.0;
System.out.printf("Test 3 [N=1000 slow=%d fast=%d ratio=%.1fx]: %s%n",
sOps, fOps, ratio, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 4: correctness — both methods produce same final directory list
{
total++;
int N = 200;
// Slow path: list of dirs after adding 0..N-1 (no duplicates in this run)
List<String> slowList = new ArrayList<>();
Set<String> slowSeen = new HashSet<>();
for (int i = 0; i < N; i++) {
String d = "dir" + i;
if (!slowSeen.contains(d)) {
slowList.add(d);
slowSeen.add(d);
}
}
// Fast path: same
List<String> fastList = new ArrayList<>();
Set<String> fastSeen = new HashSet<>();
for (int i = 0; i < N; i++) {
String d = "dir" + i;
if (!fastSeen.contains(d)) {
fastList.add(d);
fastSeen.add(d);
}
}
boolean ok = slowList.equals(fastList);
System.out.printf("Test 4 [N=200 correctness lists_match=%b]: %s%n",
ok, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 5: duplicate handling — slow and fast both skip duplicates
{
total++;
int N = 100;
// Add dirs 0..49, then 0..49 again (all duplicates)
List<String> slowList = new ArrayList<>();
Set<String> slowSeen = new HashSet<>();
for (int round = 0; round < 2; round++) {
for (int i = 0; i < N / 2; i++) {
String d = "dir" + i;
if (!slowSeen.contains(d)) {
slowList.add(d);
slowSeen.add(d);
}
}
}
boolean ok = slowList.size() == N / 2;
System.out.printf("Test 5 [N=100 dup_filter size=%d expected=%d]: %s%n",
slowList.size(), N / 2, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
System.out.printf("%d/%d PASS%n", passed, total);
if (passed != total) System.exit(1);
}
}