whitepaper: 366/178 — wave5 defect tables + PDF rebuild
This commit is contained in:
parent
835ae73b0f
commit
a4b0cf4edd
79 changed files with 3829 additions and 17 deletions
56
defects/mysql/patch/mysql-0003-setup-fields-find.md
Normal file
56
defects/mysql/patch/mysql-0003-setup-fields-find.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# mysql-0003 — `setup_fields()`: O(F²) std::find iterator recovery inside item loop
|
||||
|
||||
## Status
|
||||
PATCHED
|
||||
|
||||
## Severity
|
||||
MEDIUM (conditional path — only triggers when split_sum_func fires; bounded in
|
||||
practice to aggregate-heavy queries with wide SELECT lists)
|
||||
|
||||
## Location
|
||||
`sql/sql_base.cc`, function `setup_fields()`, line ~9496
|
||||
|
||||
## Description
|
||||
`setup_fields` iterates over the `fields` deque (size F) to fix and resolve
|
||||
each Item. After calling `item->split_sum_func(...)`, the deque may grow
|
||||
(new items appended), invalidating the range-based iterator `it`. The current
|
||||
recovery code is:
|
||||
|
||||
```cpp
|
||||
if (old_size != fields->size()) {
|
||||
it = std::find(fields->begin(), fields->end(), item);
|
||||
}
|
||||
```
|
||||
|
||||
`std::find` performs an O(F) linear scan to rediscover the current item's
|
||||
position. In a query with F columns where every column has an aggregate that
|
||||
triggers `split_sum_func`, this recovery fires F times, each scanning O(F)
|
||||
elements — O(F²) total.
|
||||
|
||||
## Fix
|
||||
Replace the `std::find` recovery with an index-based approach: replace the
|
||||
range-for with an explicit index loop. After `split_sum_func` fires, the new
|
||||
items are always appended to the end, so the current item's physical position
|
||||
is unchanged — we only need to update the end sentinel. With an index loop,
|
||||
no re-scan is necessary.
|
||||
|
||||
```cpp
|
||||
// Replace:
|
||||
for (auto it = fields->begin(); it != fields->end(); ++it) {
|
||||
...
|
||||
if (old_size != fields->size()) {
|
||||
it = std::find(fields->begin(), fields->end(), item); // O(F)
|
||||
}
|
||||
}
|
||||
|
||||
// With:
|
||||
for (size_t idx = 0; idx < fields->size(); ++idx) {
|
||||
Item *item = (*fields)[idx];
|
||||
// split_sum_func may append to fields; idx stays valid because
|
||||
// mem_root_deque is stable for existing indices after push_back.
|
||||
// No re-scan needed.
|
||||
}
|
||||
```
|
||||
|
||||
## Patch file
|
||||
See `mysql-0003-setup-fields-find.patch`
|
||||
36
defects/mysql/patch/mysql-0003-setup-fields-find.patch
Normal file
36
defects/mysql/patch/mysql-0003-setup-fields-find.patch
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
--- a/sql/sql_base.cc
|
||||
+++ b/sql/sql_base.cc
|
||||
@@ -9376,8 +9376,12 @@ bool setup_fields(THD *thd, Access_bitmask want_privilege, bool allow_sum_func,
|
||||
Ref_item_array ref = ref_item_array;
|
||||
|
||||
- for (auto it = fields->begin(); it != fields->end(); ++it) {
|
||||
- const size_t old_size = fields->size();
|
||||
- Item *item = *it;
|
||||
+ // CWE-407 fix (mysql-0003): use index-based loop so that iterator
|
||||
+ // recovery after split_sum_func() does not require an O(F) std::find
|
||||
+ // scan. split_sum_func() only ever appends new items to the END of
|
||||
+ // `fields`, so the index of the current item is stable; the loop
|
||||
+ // re-evaluates fields->size() each iteration to pick up appended items.
|
||||
+ for (size_t field_idx = 0; field_idx < fields->size(); ++field_idx) {
|
||||
+ Item *item = (*fields)[field_idx];
|
||||
assert(!item->hidden);
|
||||
- Item **item_pos = &*it;
|
||||
+ Item **item_pos = &(*fields)[field_idx];
|
||||
if ((!item->fixed && item->fix_fields(thd, item_pos)) ||
|
||||
(item = *item_pos)->check_cols(1)) {
|
||||
DBUG_PRINT("info",
|
||||
@@ -9460,9 +9464,7 @@ bool setup_fields(THD *thd, Access_bitmask want_privilege, bool allow_sum_func,
|
||||
}
|
||||
}
|
||||
|
||||
- select->select_list_tables |= item->used_tables();
|
||||
-
|
||||
- if (old_size != fields->size()) {
|
||||
- // Items have been added (either by fix_fields or by split_sum_func), so
|
||||
- // our iterator is invalidated. Reconstruct it.
|
||||
- it = std::find(fields->begin(), fields->end(), item);
|
||||
- }
|
||||
+ select->select_list_tables |= item->used_tables();
|
||||
+ // No iterator recovery needed: index loop is stable under append-only
|
||||
+ // growth of fields (split_sum_func only appends to the back).
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import java.util.*;
|
|||
*
|
||||
* 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
|
||||
*
|
||||
* No JUnit. Prints N/N PASS.
|
||||
*/
|
||||
|
|
@ -149,6 +150,55 @@ public class MysqlTest {
|
|||
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;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Main
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
@ -200,6 +250,28 @@ public class MysqlTest {
|
|||
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++; }
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue