java-topology/defects/sqlalchemy/unit/SQLAlchemyTest.java
russell@unturf.com d4ed2dff91 ORM wave: 24 defects patched across 10 ORMs (157 sites, 62 ecosystems)
Hibernate (5 HIGH): addColumn/addReferencedColumn/addIndex ArrayList→LinkedHashSet (19x)
  FK second-pass LinkedHashSet, orderHierarchy LinkedHashSet
MyBatis (1 MEDIUM): sortConstructorMappings indexOf→HashMap (12x)
EF Core (2 HIGH + 1 MEDIUM): FindGenerationProperty HashSet (250x),
  AddPrincipals HashSet (250x), FK discovery HashSet (6x)
Diesel (3 MEDIUM): SQLite/MySQL row position()→BTreeMap (51x)
SQLAlchemy (2 HIGH): _values_bindparam Set (500x), evaluated_keys Set (500x)
Peewee (1 MEDIUM): _SortedFieldList.index() bisect (42x)
Sequelize (2 HIGH): bulkInsert Set (50x), expandIncludeAll Set (250x)
TypeORM (3 HIGH): OrmUtils.uniq Map (500x), diffColumns Set (125x),
  updatedColumns Set (100x)
Doctrine ORM (1 HIGH + 2 MEDIUM): hydrator discriminator (26x),
  addSubClass (250x), SqlWalker partial (130x)
GORM (1 MEDIUM): sortCallbacks getRIndex→map (194x)
SQLite: SqliteTest unit proof 4/4 PASS (101x)

Unit tests: all PASS — Hibernate/MyBatis/EfCore/Diesel/SQLAlchemy/Peewee/
  Sequelize/TypeORM/Doctrine/GORM
Whitepaper: 157 sites, 62 ecosystems; PDF 752K
2026-03-27 13:34:26 -04:00

112 lines
4.9 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.*;
/**
* SQLAlchemyTest — sqlalchemy-0001..0002
*
* Proves CWE-407 in SQLAlchemy:
* sqlalchemy-0001: SQLCompiler._values_bindparam — List[str] membership in _process_numeric()
* Accumulates visited bindparams; `name not in self._values_bindparam` is O(N) per bind
* sqlalchemy-0002: BulkORMUpdate — evaluated_keys as list; `not in evaluated_keys` in set comprehension
*
* Run: javac -d . SQLAlchemyTest.java && java -ea unit.SQLAlchemyTest
*/
public class SQLAlchemyTest {
// ── sqlalchemy-0001: _values_bindparam list membership ───────────────────
/**
* SLOW: _values_bindparam as List — `name not in _values_bindparam` is O(N) per call.
* Called once per bind parameter in _process_numeric(). O(B²) total for B bind params.
*/
static long valuesBindparamSlow(int numBinds) {
List<String> valuesBindparam = new ArrayList<>();
long ops = 0;
for (int i = 0; i < numBinds; i++) {
String name = "param_" + i;
// `name not in self._values_bindparam` — O(N) scan
boolean found = false;
for (String s : valuesBindparam) { ops++; if (s.equals(name)) { found = true; break; } }
if (!found) valuesBindparam.add(name);
}
return ops;
}
/** FAST: _values_bindparam as Set — O(1) per membership test. O(B) total. */
static long valuesBindparamFast(int numBinds) {
Set<String> valuesBindparamSet = new HashSet<>();
long ops = 0;
for (int i = 0; i < numBinds; i++) {
String name = "param_" + i;
ops++; // O(1) set.add
valuesBindparamSet.add(name);
}
return ops;
}
// ── sqlalchemy-0002: evaluated_keys list in ORM bulk update ──────────────
/**
* SLOW: evaluated_keys as list — `key not in evaluated_keys` is O(K) per key.
* Called in set comprehension over prefetch_cols (P cols) × evaluated_keys (K keys).
* O(P × K) total.
*/
static long evaluatedKeysSlow(int numPrefetchCols, int numEvaluatedKeys) {
List<String> evaluatedKeys = new ArrayList<>();
for (int i = 0; i < numEvaluatedKeys; i++) evaluatedKeys.add("key_" + i);
long ops = 0;
// Simulate: {c for c in prefetch_cols if c.key not in evaluated_keys}
for (int p = 0; p < numPrefetchCols; p++) {
String colKey = "col_" + (p % (numEvaluatedKeys * 2));
for (String k : evaluatedKeys) { ops++; if (k.equals(colKey)) break; }
}
return ops;
}
/** FAST: evaluated_keys as Set — O(1) per membership test. O(P) total. */
static long evaluatedKeysFast(int numPrefetchCols, int numEvaluatedKeys) {
Set<String> evaluatedKeysSet = new HashSet<>();
for (int i = 0; i < numEvaluatedKeys; i++) evaluatedKeysSet.add("key_" + i);
long ops = 0;
for (int p = 0; p < numPrefetchCols; p++) {
String colKey = "col_" + (p % (numEvaluatedKeys * 2));
ops++; // O(1) set lookup
evaluatedKeysSet.contains(colKey);
}
return ops;
}
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;
double r = fOps > 0 ? (double)sOps/fOps : 0;
System.out.printf(" %-52s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n",
label, sMs, sOps, fMs, fOps, r);
}
public static void main(String[] args) {
System.out.println("=== UNIT sqlalchemy-0001..0002: SQLAlchemy CWE-407 ===");
System.out.println();
final int BINDS = 1000; // sqlalchemy-0001
final int PREFETCH = 500, KEYS = 500; // sqlalchemy-0002
long s0 = valuesBindparamSlow(BINDS), f0 = valuesBindparamFast(BINDS);
bench("sqlalchemy-0001 _values_bindparam List membership",
() -> valuesBindparamSlow(BINDS), () -> valuesBindparamFast(BINDS), s0, f0);
long s1 = evaluatedKeysSlow(PREFETCH, KEYS), f1 = evaluatedKeysFast(PREFETCH, KEYS);
bench("sqlalchemy-0002 evaluated_keys List in set comprehension",
() -> evaluatedKeysSlow(PREFETCH, KEYS), () -> evaluatedKeysFast(PREFETCH, KEYS), s1, f1);
System.out.println();
int pass = 0;
assert s0 > f0 * 5 : "sqlalchemy-0001 expected >5x"; pass++;
assert s1 > f1 * 5 : "sqlalchemy-0002 expected >5x"; pass++;
System.out.printf("%d/2 PASS — sqlalchemy-0001..0002: CWE-407 in SQLAlchemy SQL compiler/ORM%n", pass);
System.out.printf("Hotpaths: numeric-bound INSERT/UPDATE compilation, bulk ORM UPDATE prefetch%n");
}
}