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 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 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 dirList = new ArrayList<>(); Set 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 slowList = new ArrayList<>(); Set 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 fastList = new ArrayList<>(); Set 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 slowList = new ArrayList<>(); Set 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); } }