package unit; import java.util.*; /** * MysqlTest — CWE-407 benchmarks for MySQL defects. * * mysql-0001: SHOW GRANTS USING roles — O(U*G) vector find vs O(U) hash lookup * mysql-0002: has_global_grant fallback — O(P) multimap equal_range+find vs O(1) map lookup * mysql-0003: setup_fields() iterator recovery — O(F²) std::find vs O(F) index loop * mysql-0004: dict_index_find_and_set_cols() col_added — O(F²) std::find vs O(F) unordered_set * * No JUnit. Prints N/N PASS. */ public class MysqlTest { // ----------------------------------------------------------------------- // Harness // ----------------------------------------------------------------------- static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) { slow.run(); fast.run(); long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000; System.out.printf(" %-60s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n", label, sMs, sOps, fMs, fOps, fOps > 0 ? (double) sOps / fOps : 0); } // ----------------------------------------------------------------------- // mysql-0001 — SHOW GRANTS USING: vector find vs unordered_set // // Models sql/auth/sql_authorization.cc:4875-4898 // Outer loop: using_roles (U entries) // Inner: std::find on granted_roles vector (G entries) — O(G) per iter // Inner: std::find_if on mandatory_roles vector (M entries) — O(M) per iter // Total: O(U * (G + M)) // // Fix: build unordered_set from granted_roles + mandatory_roles before loop // Total: O(G + M) setup + O(U) lookups // ----------------------------------------------------------------------- static long[] showGrantsSlow(int U, int G, int M) { // granted_roles: vector of pairs (authid, with_admin) List grantedRoles = new ArrayList<>(G); for (int i = 0; i < G; i++) grantedRoles.add(new String[]{"role_granted_" + i}); // mandatory_roles: vector of Role_id List mandatoryRoles = new ArrayList<>(M); for (int i = 0; i < M; i++) mandatoryRoles.add("role_mandatory_" + i); // using_roles: request to activate U roles (last one is a granted role, rest miss) List usingRoles = new ArrayList<>(U); for (int i = 0; i < U - 1; i++) usingRoles.add("role_granted_" + i); // found in granted if (U > 0) usingRoles.add("role_mandatory_0"); // found in mandatory long ops = 0; for (String authid : usingRoles) { // O(G) linear find on granted_roles vector boolean foundInGranted = false; for (String[] gr : grantedRoles) { ops++; if (gr[0].equals(authid)) { foundInGranted = true; break; } } if (!foundInGranted) { // O(M) linear find on mandatory_roles vector for (String rid : mandatoryRoles) { ops++; if (rid.equals(authid)) { break; } } } } return new long[]{ops}; } static long[] showGrantsFast(int U, int G, int M) { List grantedRoles = new ArrayList<>(G); for (int i = 0; i < G; i++) grantedRoles.add(new String[]{"role_granted_" + i}); List mandatoryRoles = new ArrayList<>(M); for (int i = 0; i < M; i++) mandatoryRoles.add("role_mandatory_" + i); List usingRoles = new ArrayList<>(U); for (int i = 0; i < U - 1; i++) usingRoles.add("role_granted_" + i); if (U > 0) usingRoles.add("role_mandatory_0"); long ops = 0; // Build O(1) sets before the loop — the fix Set grantedSet = new HashSet<>(G * 2); for (String[] gr : grantedRoles) { grantedSet.add(gr[0]); ops++; } Set mandatorySet = new HashSet<>(M * 2); for (String rid : mandatoryRoles) { mandatorySet.add(rid); ops++; } for (String authid : usingRoles) { ops++; // O(1) hash lookup if (!grantedSet.contains(authid)) { ops++; // O(1) hash lookup grantedSet.contains(authid); // suppress mandatorySet.contains(authid); } } return new long[]{ops}; } // ----------------------------------------------------------------------- // mysql-0002 — has_global_grant fallback: O(P) std::find vs O(1) map // // Models sql/auth/sql_security_ctx.cc:735-740 // equal_range returns P entries for this user in the multimap // std::find walks all P entries to find the privilege string // Called Q times (Q queries checking this user's privileges) // Total: O(Q * P) // // Fix: build local unordered_map from equal_range once per // security context refresh — O(P) setup + O(Q) lookups // ----------------------------------------------------------------------- static long[] hasGlobalGrantSlow(int P, int Q) { // P dynamic privileges for one user in the multimap equal_range List equalRange = new ArrayList<>(P); for (int i = 0; i < P; i++) equalRange.add(new String[]{"PRIV_" + i, "false"}); // Target privilege is always the last one (worst case O(P)) String target = "PRIV_" + (P - 1); long ops = 0; for (int q = 0; q < Q; q++) { // O(P) std::find scan for (String[] entry : equalRange) { ops++; if (entry[0].equals(target)) break; } } return new long[]{ops}; } static long[] hasGlobalGrantFast(int P, int Q) { List equalRange = new ArrayList<>(P); for (int i = 0; i < P; i++) equalRange.add(new String[]{"PRIV_" + i, "false"}); String target = "PRIV_" + (P - 1); long ops = 0; // Build local unordered_map once (models per-context-refresh caching) Map localMap = new HashMap<>(P * 2); for (String[] entry : equalRange) { localMap.put(entry[0], Boolean.parseBoolean(entry[1])); ops++; // map build cost } for (int q = 0; q < Q; q++) { ops++; // O(1) hash lookup localMap.containsKey(target); } return new long[]{ops}; } // ----------------------------------------------------------------------- // mysql-0003 — setup_fields iterator recovery: O(F²) std::find vs O(F) index loop // // Models sql/sql_base.cc:~9496 // for (auto it = fields->begin(); it != fields->end(); ++it) { // ...split_sum_func may append items to fields... // if (old_size != fields->size()) { // it = std::find(fields->begin(), fields->end(), item); // O(F) // } // } // // Worst case: every item triggers split_sum_func → O(F) recovery per item → O(F²) // Fix: use index-based loop; index is stable under append-only growth → O(F) total // ----------------------------------------------------------------------- static long setupFieldsSlow(int F) { List fields = new ArrayList<>(F * 2); for (int i = 0; i < F; i++) fields.add(i); long ops = 0; int limit = F; // process original F items for (int i = 0; i < limit; i++) { Integer item = fields.get(i); // split_sum_func: append one new item fields.add(-(i + 1)); // O(|fields|) std::find to re-discover current item's position for (int j = 0; j < fields.size(); j++) { ops++; if (fields.get(j).equals(item)) break; } } return ops; } static long setupFieldsFast(int F) { List fields = new ArrayList<>(F * 2); for (int i = 0; i < F; i++) fields.add(i); long ops = 0; // Index-based loop: no re-scan needed after append for (int idx = 0; idx < F; idx++) { ops++; // direct indexed access — O(1) // split_sum_func appends but doesn't affect idx fields.add(-(idx + 1)); } return ops; } // ----------------------------------------------------------------------- // mysql-0004 — dict_index_find_and_set_cols: O(F²) col_added vector dedup // // Models storage/innobase/dict/dict0dict.cc:dict_index_find_and_set_cols(): // std::vector col_added; // for (ulint i = 0; i < index->n_fields; i++) { // for (j = 0; j < table->n_cols; j++) { // if name matches { // bool exists = std::find(col_added.begin(), col_added.end(), j) // != col_added.end(); // O(|col_added|) = O(i) // if exists: goto dup_err; // col_added.push_back(j); // col_added grows // } // } // } // // F index fields: 0+1+...+(F-1) = O(F²) total comparison ops. // Fix: std::unordered_set → O(1) per lookup → O(F) total. // ----------------------------------------------------------------------- /** * Simulate dict_index_find_and_set_cols with O(F²) std::find duplicate check. * F = number of index fields (all distinct columns, no duplicates). * Returns total comparison operations. */ static long dictIndexColAddedSlow(int F) { List colAdded = new ArrayList<>(F); long ops = 0; for (int i = 0; i < F; i++) { int colIdx = i; // each field maps to a distinct column // std::find: O(|col_added|) scan boolean exists = false; for (Integer c : colAdded) { ops++; if (c.equals(colIdx)) { exists = true; break; } } if (!exists) { colAdded.add(colIdx); } } return ops; } /** * Fixed version: unordered_set for O(1) duplicate detection. * Models replacing std::vector + std::find with std::unordered_set. */ static long dictIndexColAddedFast(int F) { Set colAdded = new HashSet<>(F * 2); long ops = 0; for (int i = 0; i < F; i++) { int colIdx = i; ops++; // O(1) hash lookup if (!colAdded.contains(colIdx)) { colAdded.add(colIdx); } } return ops; } // ----------------------------------------------------------------------- // Main // ----------------------------------------------------------------------- public static void main(String[] args) { System.out.println("mysql CWE-407 benchmarks"); System.out.println("=".repeat(100)); int failures = 0; int total = 0; // --- mysql-0001 --- { int U = 500, G = 500, M = 100; long[] slowOps = new long[1], fastOps = new long[1]; Runnable slow = () -> { long[] r = showGrantsSlow(U, G, M); slowOps[0] = r[0]; }; Runnable fast = () -> { long[] r = showGrantsFast(U, G, M); fastOps[0] = r[0]; }; slow.run(); fast.run(); long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000; double speedup = fastOps[0] > 0 ? (double) slowOps[0] / fastOps[0] : 0; System.out.printf(" %-60s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.1fx%n", "mysql-0001 SHOW GRANTS USING roles O(U*G) vs O(U)", sMs, slowOps[0], fMs, fastOps[0], speedup); total++; boolean pass = slowOps[0] > fastOps[0] * 5L; if (!pass) { System.out.println(" FAIL: expected slowOps > fastOps * 5"); failures++; } } // --- mysql-0002 --- { int P = 500, Q = 1000; long[] slowOps = new long[1], fastOps = new long[1]; Runnable slow = () -> { long[] r = hasGlobalGrantSlow(P, Q); slowOps[0] = r[0]; }; Runnable fast = () -> { long[] r = hasGlobalGrantFast(P, Q); fastOps[0] = r[0]; }; slow.run(); fast.run(); long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000; double speedup = fastOps[0] > 0 ? (double) slowOps[0] / fastOps[0] : 0; System.out.printf(" %-60s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.1fx%n", "mysql-0002 has_global_grant O(P*Q) vs O(P+Q)", sMs, slowOps[0], fMs, fastOps[0], speedup); total++; // slow: Q*P ops; fast: P + Q ops; ratio ~ Q*P / (P+Q) ~ Q/2 at equal P,Q boolean pass = slowOps[0] > fastOps[0] * 10L; if (!pass) { System.out.println(" FAIL: expected slowOps > fastOps * 10"); failures++; } } // --- mysql-0003: setup_fields iterator recovery --- { int F = 500; long[] slowOps = new long[1], fastOps = new long[1]; Runnable slow = () -> slowOps[0] = setupFieldsSlow(F); Runnable fast = () -> fastOps[0] = setupFieldsFast(F); slow.run(); fast.run(); long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000; double speedup = fastOps[0] > 0 ? (double) slowOps[0] / fastOps[0] : 0; System.out.printf(" %-60s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.1fx%n", "mysql-0003 setup_fields O(F²) std::find vs O(F) index", sMs, slowOps[0], fMs, fastOps[0], speedup); total++; // slow: each of F items triggers O(F+i) scan → sum ~ F²/2; fast: F ops // At F=500: slow~125000+ ops, fast=500 ops → ratio >>10x boolean pass = slowOps[0] > fastOps[0] * 10L; if (!pass) { System.out.printf(" FAIL mysql-0003: slowOps=%,d fastOps=%,d (expected >10x)%n", slowOps[0], fastOps[0]); failures++; } } // --- mysql-0004: dict_index_find_and_set_cols col_added O(F²) --- { int F = 200; long[] slowOps = new long[1], fastOps = new long[1]; Runnable slow = () -> slowOps[0] = dictIndexColAddedSlow(F); Runnable fast = () -> fastOps[0] = dictIndexColAddedFast(F); slow.run(); fast.run(); long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000; double speedup = fastOps[0] > 0 ? (double) slowOps[0] / fastOps[0] : 0; System.out.printf(" %-60s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.1fx%n", "mysql-0004 dict_index_col_added O(F²) vs O(F)", sMs, slowOps[0], fMs, fastOps[0], speedup); total++; // At F=200: slow=0+1+...+199=19900 ops; fast=200 ops → ratio >50x boolean pass = slowOps[0] > fastOps[0] * 50L; if (!pass) { System.out.printf(" FAIL mysql-0004: slowOps=%,d fastOps=%,d (expected >50x)%n", slowOps[0], fastOps[0]); failures++; } else System.out.println(" PASS mysql-0004"); } System.out.println("=".repeat(100)); System.out.printf("%d/%d %s%n", total - failures, total, failures == 0 ? "PASS" : "FAIL"); if (failures > 0) System.exit(1); } }