452 lines
20 KiB
Java
452 lines
20 KiB
Java
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
|
|
* mysql-0005: row_sel_store_mysql_rec() template_col — O(T*C) std::find per row vs O(T) hashmap
|
|
*
|
|
* 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<string> 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<String[]> 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<String> 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<String> 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<String[]> grantedRoles = new ArrayList<>(G);
|
|
for (int i = 0; i < G; i++) grantedRoles.add(new String[]{"role_granted_" + i});
|
|
|
|
List<String> mandatoryRoles = new ArrayList<>(M);
|
|
for (int i = 0; i < M; i++) mandatoryRoles.add("role_mandatory_" + i);
|
|
|
|
List<String> 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<String> grantedSet = new HashSet<>(G * 2);
|
|
for (String[] gr : grantedRoles) { grantedSet.add(gr[0]); ops++; }
|
|
Set<String> 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<string,bool> 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<String[]> 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<String[]> 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<String, Boolean> 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<Integer> 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<Integer> 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<ulint> 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<ulint> → 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<Integer> 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<Integer> 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");
|
|
}
|
|
|
|
// --- mysql-0005: row_sel_store_mysql_rec template_col O(T*C) std::find per row ---
|
|
{
|
|
int T = 50; // template fields (SELECT columns)
|
|
int C = 50; // secondary index columns
|
|
int ROWS = 100_000;
|
|
long[] slowOps = new long[1], fastOps = new long[1];
|
|
|
|
Runnable slow = () -> slowOps[0] = templateColSlow(T, C, ROWS);
|
|
Runnable fast = () -> fastOps[0] = templateColFast(T, C, ROWS);
|
|
|
|
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-0005 row0sel template_col O(T*C) vs O(T) hashmap", sMs, slowOps[0], fMs, fastOps[0], speedup);
|
|
|
|
total++;
|
|
// slow: T * C ops per row * ROWS; fast: T ops per row * ROWS; ratio = C = 50
|
|
boolean pass = slowOps[0] > fastOps[0] * 10L;
|
|
if (!pass) { System.out.printf(" FAIL mysql-0005: slowOps=%,d fastOps=%,d (expected >10x)%n",
|
|
slowOps[0], fastOps[0]); failures++; }
|
|
else System.out.println(" PASS mysql-0005");
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// mysql-0005 — row_sel_store_mysql_rec: O(T*C) vector std::find vs O(T) hashmap
|
|
//
|
|
// Models storage/innobase/row/row0sel.cc:row_sel_store_mysql_rec()
|
|
// template_col: vector<const dict_col_t*> of C secondary index cols
|
|
// For each of T template fields: std::find(template_col, col) -> O(C)
|
|
// Total per row: O(T * C)
|
|
// Fix: unordered_map<col*, sec_field_no> -> O(1) per template field
|
|
// -----------------------------------------------------------------------
|
|
static long templateColSlow(int T, int C, int rows) {
|
|
// Build template_col vector (C pointer-like integers)
|
|
long[] templateCol = new long[C];
|
|
for (int i = 0; i < C; i++) templateCol[i] = i + 1000L; // simulate column pointers
|
|
|
|
// T template fields - half will be found in templateCol
|
|
long[] templateFields = new long[T];
|
|
for (int i = 0; i < T; i++) {
|
|
templateFields[i] = (i % 2 == 0) ? (i / 2 + 1000L) : (i + 9000L); // half hit, half miss
|
|
}
|
|
|
|
long ops = 0;
|
|
for (int r = 0; r < rows; r++) {
|
|
for (int t = 0; t < T; t++) {
|
|
long col = templateFields[t];
|
|
// std::find equivalent: O(C) linear scan
|
|
int foundIdx = -1;
|
|
for (int c = 0; c < C; c++) {
|
|
ops++;
|
|
if (templateCol[c] == col) {
|
|
foundIdx = c;
|
|
break;
|
|
}
|
|
}
|
|
// foundIdx is the sec_field_no (or -1 if not found)
|
|
long dummy = foundIdx; // prevent dead-code elimination
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
static long templateColFast(int T, int C, int rows) {
|
|
// Build template_col_map: HashMap<col, sec_field_no> for O(1) lookup
|
|
Map<Long, Integer> templateColMap = new HashMap<>(C * 2);
|
|
for (int i = 0; i < C; i++) templateColMap.put(i + 1000L, i);
|
|
|
|
// T template fields
|
|
long[] templateFields = new long[T];
|
|
for (int i = 0; i < T; i++) {
|
|
templateFields[i] = (i % 2 == 0) ? (i / 2 + 1000L) : (i + 9000L);
|
|
}
|
|
|
|
long ops = 0;
|
|
for (int r = 0; r < rows; r++) {
|
|
for (int t = 0; t < T; t++) {
|
|
long col = templateFields[t];
|
|
ops++; // O(1) hash lookup
|
|
Integer secFieldNo = templateColMap.get(col);
|
|
long dummy = (secFieldNo != null) ? secFieldNo : -1;
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
}
|