package unit; import java.util.HashMap; import java.util.Map; /** * CWE-407 unit test: ruby-0001 * * Models Ruby's args_setup_kw_parameters() keyword argument dispatch. * * DEFECT (vm_args.c:301-374): * args_setup_kw_parameters_lookup() does an O(P) linear scan over * passed_keywords[] for each of K acceptable keywords. * Total per call: O(K × P). * * FIX: * Build a HashMap from passed keyword symbol → value-slot index once (O(P)), * then do K O(1) lookups. Total: O(K + P). * * Asserts: slowOps > fastOps * 10 at K=P=20 (actual ratio ≈ K/2 ≈ 10×). */ public class RubyKwargTest { /** * Defective: for each of K accepted keywords, linearly scan P passed keywords. * * @param K number of keyword parameters the method accepts * @param P number of keyword arguments passed by caller (same set, reversed) * @return total element-comparison operations */ static long slow(int K, int P) { // accepted keywords: kw0, kw1, ..., kw(K-1) String[] accepted = new String[K]; for (int i = 0; i < K; i++) accepted[i] = "kw" + i; // passed keywords in reverse order (worst case for linear scan) String[] passed = new String[P]; for (int i = 0; i < P; i++) passed[i] = "kw" + (P - 1 - i); long ops = 0; // Outer loop over accepted keywords (mirrors two loops in args_setup_kw_parameters) for (int k = 0; k < K; k++) { // Inner scan — mirrors args_setup_kw_parameters_lookup for (int p = 0; p < P; p++) { ops++; if (accepted[k].equals(passed[p])) { break; } } } return ops; } /** * Fixed: build HashMap from passed keywords once, then K O(1) lookups. * * @param K number of keyword parameters the method accepts * @param P number of keyword arguments passed * @return total operations (P to build table + K to lookup) */ static long fast(int K, int P) { String[] passed = new String[P]; for (int i = 0; i < P; i++) passed[i] = "kw" + (P - 1 - i); // Build HashMap once — mirrors build_passed_kw_table, cost O(P) Map kwMap = new HashMap<>(P * 2); long ops = 0; for (int p = 0; p < P; p++) { kwMap.put(passed[p], p); ops++; // one insert } // K O(1) lookups — mirrors st_lookup per accepted keyword String[] accepted = new String[K]; for (int i = 0; i < K; i++) accepted[i] = "kw" + i; for (int k = 0; k < K; k++) { ops++; // one hash probe kwMap.get(accepted[k]); } return ops; } public static void main(String[] args) { int passed = 0; int total = 0; // Test 1: K=P=10 — slow must be >2× more expensive // (sOps = 1+2+...+10 = 55; fOps = 10+10 = 20; ratio ≈ 2.8×) { total++; long sOps = slow(10, 10); long fOps = fast(10, 10); boolean ok = sOps > fOps * 2L; System.out.printf("Test 1 [K=P=10 slow=%d fast=%d ratio=%.1fx]: %s%n", sOps, fOps, (double) sOps / fOps, ok ? "PASS" : "FAIL"); if (ok) passed++; } // Test 2: K=P=20 — slow must be >4× more expensive // (sOps ≈ 210; fOps = 40; ratio ≈ 5.3×) { total++; long sOps = slow(20, 20); long fOps = fast(20, 20); boolean ok = sOps > fOps * 4L; System.out.printf("Test 2 [K=P=20 slow=%d fast=%d ratio=%.1fx]: %s%n", sOps, fOps, (double) sOps / fOps, ok ? "PASS" : "FAIL"); if (ok) passed++; } // Test 3: K=P=100 — slow must be >30× more expensive { total++; long sOps = slow(100, 100); long fOps = fast(100, 100); // Expected: sOps ≈ 5050; fOps = 200; ratio ≈ 25× boolean ok = sOps > fOps * 20L; System.out.printf("Test 3 [K=P=100 slow=%d fast=%d ratio=%.1fx]: %s%n", sOps, fOps, (double) sOps / fOps, ok ? "PASS" : "FAIL"); if (ok) passed++; } // Test 4: K=P=200 — slow must be >50× more expensive { total++; long sOps = slow(200, 200); long fOps = fast(200, 200); // Expected: sOps ≈ 20100; fOps = 400; ratio ≈ 50× boolean ok = sOps > fOps * 30L; System.out.printf("Test 4 [K=P=200 slow=%d fast=%d ratio=%.1fx]: %s%n", sOps, fOps, (double) sOps / fOps, ok ? "PASS" : "FAIL"); if (ok) passed++; } // Test 5: correctness — both find the same value for each keyword { total++; int K = 15; int P = 15; // Build accepted and passed arrays String[] accepted = new String[K]; for (int i = 0; i < K; i++) accepted[i] = "kw" + i; String[] passedKw = new String[P]; int[] passedVals = new int[P]; for (int i = 0; i < P; i++) { passedKw[i] = "kw" + (P - 1 - i); passedVals[i] = 100 + (P - 1 - i); } // slow path: linear scan to build result int[] slowResult = new int[K]; for (int k = 0; k < K; k++) { slowResult[k] = -1; for (int p = 0; p < P; p++) { if (accepted[k].equals(passedKw[p])) { slowResult[k] = passedVals[p]; break; } } } // fast path: hash lookup Map kwMap = new HashMap<>(); for (int p = 0; p < P; p++) kwMap.put(passedKw[p], passedVals[p]); int[] fastResult = new int[K]; for (int k = 0; k < K; k++) { fastResult[k] = kwMap.getOrDefault(accepted[k], -1); } boolean ok = true; for (int k = 0; k < K; k++) { if (slowResult[k] != fastResult[k]) { ok = false; break; } } System.out.printf("Test 5 [correctness K=P=%d match=%b]: %s%n", K, ok, ok ? "PASS" : "FAIL"); if (ok) passed++; } System.out.printf("%d/%d PASS%n", passed, total); if (passed != total) System.exit(1); } }