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 initial, List depSources) { long ops = 0; List 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 initial, List depSources) { long ops = 0; List words = new ArrayList<>(initial); Set 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 initial = new ArrayList<>(); for (int i = 0; i < 10; i++) initial.add("init_dep_" + i); // depSources includes duplicates (realistic — many vars share deps) List 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 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 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 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 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 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 nameToIdx = new HashMap<>(); List 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 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); } }