java-topology/defects/sequelize/unit/SequelizeTest.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

120 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.*;
/**
* SequelizeTest — sequelize-0001..0002
*
* Proves CWE-407 in Sequelize ORM:
* sequelize-0001: query-generator.js bulkInsertQuery — allAttributes.includes() O(C) in double loop
* O(rows × cols²) for bulk INSERT with many rows and columns
* sequelize-0002: model.js _expandIncludeAll — all.includes(type_) O(T) inside for-of loop
* O(T²) when expanding association types
*
* Run: javac -d . SequelizeTest.java && java -ea unit.SequelizeTest
*/
public class SequelizeTest {
// ── sequelize-0001: bulkInsertQuery allAttributes dedup ──────────────────
/**
* SLOW: allAttributes.includes(key) O(C) inside double loop (rows × cols).
* allAttributes grows incrementally; each new key requires O(C) scan.
* O(rows × cols × C) total where C = unique cols seen so far.
*/
static long bulkInsertSlow(int numRows, int numCols) {
List<String> allAttributes = new ArrayList<>();
long ops = 0;
for (int r = 0; r < numRows; r++) {
for (int c = 0; c < numCols; c++) {
String key = "col_" + c;
// allAttributes.includes(key)
boolean found = false;
for (String a : allAttributes) { ops++; if (a.equals(key)) { found = true; break; } }
if (!found) allAttributes.add(key);
}
}
return ops;
}
/** FAST: allAttributesSet.has(key) O(1) — shadow Set tracks already-seen cols. */
static long bulkInsertFast(int numRows, int numCols) {
Set<String> allAttributesSet = new HashSet<>();
List<String> allAttributes = new ArrayList<>();
long ops = 0;
for (int r = 0; r < numRows; r++) {
for (int c = 0; c < numCols; c++) {
String key = "col_" + c;
ops++; // O(1) set.has
if (allAttributesSet.add(key)) allAttributes.add(key);
}
}
return ops;
}
// ── sequelize-0002: _expandIncludeAll type dedup ─────────────────────────
/**
* SLOW: all.includes(type_) O(T) per expansion step.
* all grows as types are added; membership test is O(T) per type.
* O(T²) total for T association types.
*/
static long expandIncludeAllSlow(int numTypes) {
List<String> all = new ArrayList<>();
long ops = 0;
for (int i = 0; i < numTypes; i++) {
String type = "type_" + i;
// all.includes(type_)
boolean found = false;
for (String a : all) { ops++; if (a.equals(type)) { found = true; break; } }
if (!found) { all.add(0, type); } // unshift — O(T)
}
return ops;
}
/** FAST: allSet.has(type_) O(1) with shadow Set; O(T) total. */
static long expandIncludeAllFast(int numTypes) {
Set<String> allSet = new HashSet<>();
List<String> all = new ArrayList<>();
long ops = 0;
for (int i = 0; i < numTypes; i++) {
String type = "type_" + i;
ops++; // O(1)
if (allSet.add(type)) all.add(0, type);
}
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 sequelize-0001..0002: Sequelize CWE-407 ===");
System.out.println();
final int ROWS = 500, COLS = 100; // sequelize-0001
final int TYPES = 500; // sequelize-0002
long s0 = bulkInsertSlow(ROWS, COLS), f0 = bulkInsertFast(ROWS, COLS);
bench("sequelize-0001 bulkInsert allAttributes.includes()",
() -> bulkInsertSlow(ROWS, COLS), () -> bulkInsertFast(ROWS, COLS), s0, f0);
long s1 = expandIncludeAllSlow(TYPES), f1 = expandIncludeAllFast(TYPES);
bench("sequelize-0002 expandIncludeAll all.includes(type_)",
() -> expandIncludeAllSlow(TYPES), () -> expandIncludeAllFast(TYPES), s1, f1);
System.out.println();
int pass = 0;
assert s0 > f0 * 5 : "sequelize-0001 expected >5x"; pass++;
assert s1 > f1 * 5 : "sequelize-0002 expected >5x"; pass++;
System.out.printf("%d/2 PASS — sequelize-0001..0002: CWE-407 in Sequelize query builder/model%n", pass);
System.out.printf("Hotpaths: Model.bulkCreate() with many rows, Model.findAll() with include: 'all'%n");
}
}