252 lines
9.8 KiB
Java
252 lines
9.8 KiB
Java
package unit;
|
||
|
||
import java.util.*;
|
||
|
||
/**
|
||
* Standalone unit tests for NumPy CWE-407 defects.
|
||
*
|
||
* numpy-0001: f2py crackfortran _get_depend_dict — O(W²) list dedup in dep resolution
|
||
* Simulates the "if w not in words: words.append(w)" inner loop.
|
||
* slow(): counts ops using list membership (words.contains(w)) — O(W) per insert
|
||
* fast(): counts ops using a parallel HashSet for O(1) membership
|
||
* Assert: slowOps >= fastOps * 10 for W=500 deps per variable
|
||
*
|
||
* numpy-0002: recfunctions.stack_arrays — seen=[] list dedup O(A×F²) field-name tracking
|
||
* Real code (numpy/lib/recfunctions.py:1396-1405):
|
||
* seen = []
|
||
* if name not in seen: seen.append(name) # O(F) per insert
|
||
* Fix: seen = set()
|
||
*
|
||
* numpy-0003: recfunctions.join_by — names list rebuilt inside loop + .index() O(F²)
|
||
* Real code (numpy/lib/recfunctions.py:1610-1612):
|
||
* names = [name for name, dtype in ndtype] # O(F) rebuild per iteration
|
||
* nameidx = names.index(fname) # O(F) linear search
|
||
* Fix: name_to_idx dict for O(1) lookup
|
||
*/
|
||
public class NumpyTest {
|
||
|
||
// ── numpy-0001 ─────────────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Simulates _get_depend_dict slow path.
|
||
*
|
||
* For each variable, we have a list of direct deps (depSources).
|
||
* Each dep expands into more deps — all land in 'words' (a list).
|
||
* Every insertion does: if w not in words → O(|words|) scan.
|
||
*
|
||
* @param depSources transitive dependencies to merge into words (simulates the expansion)
|
||
* @return op count (each list.contains() call = 1 op)
|
||
*/
|
||
static long slowDependDict(List<String> initial, List<String> depSources) {
|
||
long ops = 0;
|
||
List<String> words = new ArrayList<>(initial);
|
||
|
||
// Simulate: for word in words[:]: for w in deps[word]: if w not in words: words.append(w)
|
||
// We flatten: just insert all depSources into words using linear contains()
|
||
for (String w : depSources) {
|
||
// O(|words|) scan per candidate
|
||
for (int i = 0; i < words.size(); i++) {
|
||
ops++; // linear scan cost
|
||
if (words.get(i).equals(w)) {
|
||
break; // already present, skip
|
||
}
|
||
if (i == words.size() - 1) {
|
||
// not found — append (words grows, next iterations cost more)
|
||
words.add(w);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
/**
|
||
* Simulates _get_depend_dict fast path.
|
||
*
|
||
* Parallel HashSet for O(1) membership; list kept for ordering.
|
||
*
|
||
* @return op count (each HashSet.contains() = 1 op)
|
||
*/
|
||
static long fastDependDict(List<String> initial, List<String> depSources) {
|
||
long ops = 0;
|
||
List<String> words = new ArrayList<>(initial);
|
||
Set<String> wordsSet = new HashSet<>(initial);
|
||
|
||
for (String w : depSources) {
|
||
ops++; // O(1) set contains
|
||
if (!wordsSet.contains(w)) {
|
||
words.add(w);
|
||
wordsSet.add(w);
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
static void testDependDict() {
|
||
int numVars = 1; // single variable with many deps (worst case per variable)
|
||
int W = 500; // transitive dep count
|
||
|
||
// Build unique dep names
|
||
List<String> initial = new ArrayList<>();
|
||
for (int i = 0; i < 10; i++) initial.add("init_dep_" + i);
|
||
|
||
// depSources includes duplicates (realistic — many vars share deps)
|
||
List<String> depSources = new ArrayList<>();
|
||
for (int i = 0; i < W; i++) depSources.add("var_" + i);
|
||
// Add duplicates to simulate repeated merges
|
||
for (int i = 0; i < W / 2; i++) depSources.add("var_" + i);
|
||
|
||
long slowOps = slowDependDict(initial, depSources);
|
||
long fastOps = fastDependDict(initial, depSources);
|
||
|
||
System.out.printf(
|
||
"numpy-0001 W=%-4d slowOps=%-8d fastOps=%-6d ratio=%.1fx%n",
|
||
W, slowOps, fastOps, (double) slowOps / fastOps
|
||
);
|
||
|
||
assert slowOps > fastOps * 10 :
|
||
"numpy-0001 FAIL: expected slowOps > 10×fastOps, got " + slowOps + " vs " + fastOps;
|
||
System.out.println("numpy-0001 PASS");
|
||
}
|
||
|
||
// ── numpy-0002: stack_arrays seen=[] list dedup ───────────────────────────
|
||
|
||
/** Simulates stack_arrays seen=[] field dedup — returns total list comparisons */
|
||
static long slowStackArrays(int numArrays, int fieldsPerArray) {
|
||
List<String> seen = new ArrayList<>();
|
||
long ops = 0;
|
||
for (int a = 0; a < numArrays; a++) {
|
||
for (int f = 0; f < fieldsPerArray; f++) {
|
||
String name = "field_" + f; // shared field names across arrays
|
||
boolean found = false;
|
||
for (int k = 0; k < seen.size(); k++) {
|
||
ops++;
|
||
if (seen.get(k).equals(name)) { found = true; break; }
|
||
}
|
||
if (!found) seen.add(name);
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
/** Simulates fixed stack_arrays with seen=set() — returns total ops */
|
||
static long fastStackArrays(int numArrays, int fieldsPerArray) {
|
||
Set<String> seen = new HashSet<>();
|
||
long ops = 0;
|
||
for (int a = 0; a < numArrays; a++) {
|
||
for (int f = 0; f < fieldsPerArray; f++) {
|
||
String name = "field_" + f;
|
||
ops++; // O(1) set probe
|
||
seen.add(name);
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
static void testStackArrays() {
|
||
int A = 100, F = 200; // 100 arrays, 200 fields each
|
||
|
||
long slowOps = slowStackArrays(A, F);
|
||
long fastOps = fastStackArrays(A, F);
|
||
|
||
System.out.printf(
|
||
"numpy-0002 A=%-4d F=%-4d slowOps=%-10d fastOps=%-8d ratio=%.1fx%n",
|
||
A, F, slowOps, fastOps, (double) slowOps / fastOps
|
||
);
|
||
|
||
assert slowOps > fastOps * 30 :
|
||
"numpy-0002 FAIL: expected >30x ratio; slow=" + slowOps + " fast=" + fastOps;
|
||
System.out.println("numpy-0002 PASS");
|
||
}
|
||
|
||
// ── numpy-0003: join_by names-list rebuild + .index() ─────────────────────
|
||
|
||
/** Simulates join_by ndtype names-list rebuild + .index() — returns total comparisons */
|
||
static long slowJoinBy(int f1, int f2) {
|
||
List<String> ndtype = new ArrayList<>();
|
||
long ops = 0;
|
||
for (int i = 0; i < f1; i++) ndtype.add("r1_field_" + i);
|
||
for (int i = 0; i < f2; i++) {
|
||
String fname = "r2_field_" + i;
|
||
// Rebuild list (simulates list comprehension O(F) per iteration)
|
||
List<String> names = new ArrayList<>(ndtype);
|
||
ops += names.size();
|
||
// .index() linear search
|
||
int idx = -1;
|
||
for (int k = 0; k < names.size(); k++) {
|
||
ops++;
|
||
if (names.get(k).equals(fname)) { idx = k; break; }
|
||
}
|
||
if (idx == -1) ndtype.add(fname);
|
||
}
|
||
// Site 2: output dtype names tuple membership check
|
||
List<String> outputNames = new ArrayList<>(ndtype);
|
||
for (int i = 0; i < f1; i++) {
|
||
String f = "r1_field_" + i;
|
||
for (int k = 0; k < outputNames.size(); k++) {
|
||
ops++;
|
||
if (outputNames.get(k).equals(f)) break;
|
||
}
|
||
}
|
||
for (int i = 0; i < f2; i++) {
|
||
String f = "r2_field_" + i;
|
||
for (int k = 0; k < outputNames.size(); k++) {
|
||
ops++;
|
||
if (outputNames.get(k).equals(f)) break;
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
/** Simulates fixed join_by with name_to_idx dict — returns total ops */
|
||
static long fastJoinBy(int f1, int f2) {
|
||
Map<String, Integer> nameToIdx = new HashMap<>();
|
||
List<String> ndtype = new ArrayList<>();
|
||
long ops = 0;
|
||
for (int i = 0; i < f1; i++) {
|
||
nameToIdx.put("r1_field_" + i, i);
|
||
ndtype.add("r1_field_" + i);
|
||
}
|
||
for (int i = 0; i < f2; i++) {
|
||
String fname = "r2_field_" + i;
|
||
ops++; // O(1) dict probe
|
||
if (!nameToIdx.containsKey(fname)) {
|
||
nameToIdx.put(fname, ndtype.size());
|
||
ndtype.add(fname);
|
||
}
|
||
}
|
||
// Site 2: set membership
|
||
Set<String> outputNamesSet = new HashSet<>(ndtype);
|
||
for (int i = 0; i < f1 + f2; i++) ops++;
|
||
return ops;
|
||
}
|
||
|
||
static void testJoinBy() {
|
||
int F1 = 200, F2 = 200;
|
||
|
||
long slowOps = slowJoinBy(F1, F2);
|
||
long fastOps = fastJoinBy(F1, F2);
|
||
|
||
System.out.printf(
|
||
"numpy-0003 F1=%-4d F2=%-4d slowOps=%-10d fastOps=%-8d ratio=%.1fx%n",
|
||
F1, F2, slowOps, fastOps, (double) slowOps / fastOps
|
||
);
|
||
|
||
assert slowOps > fastOps * 50 :
|
||
"numpy-0003 FAIL: expected >50x ratio; slow=" + slowOps + " fast=" + fastOps;
|
||
System.out.println("numpy-0003 PASS");
|
||
}
|
||
|
||
// ── main ──────────────────────────────────────────────────────────────────
|
||
|
||
public static void main(String[] args) {
|
||
int pass = 0, total = 3;
|
||
|
||
try { testDependDict(); pass++; } catch (AssertionError e) { System.err.println(e.getMessage()); }
|
||
try { testStackArrays(); pass++; } catch (AssertionError e) { System.err.println(e.getMessage()); }
|
||
try { testJoinBy(); pass++; } catch (AssertionError e) { System.err.println(e.getMessage()); }
|
||
|
||
System.out.printf("%n%d/%d PASS%n", pass, total);
|
||
if (pass != total) System.exit(1);
|
||
}
|
||
}
|