194 lines
6.3 KiB
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);
|
|
}
|
|
}
|