java-topology/defects/gorm/unit/GORMTest.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

91 lines
3.6 KiB
Java
Raw Permalink 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.*;
/**
* GORMTest — gorm-0001
*
* Proves CWE-407 in GORM (Go ORM):
* gorm-0001: callbacks.go sortCallbacks() — getRIndex() O(N) linear scan called 13×
* per callback per sort; O(N²) per sortCallbacks call, O(N³) for N registrations.
* Fix: pre-build map[string]int for O(1) index lookup.
*
* Run: javac -d . GORMTest.java && java -ea unit.GORMTest
*/
public class GORMTest {
// ── gorm-0001: sortCallbacks getRIndex linear scan ────────────────────────
/** SLOW: getRIndex([]string, str) O(N) per call; called 13× per callback per sort.
* Full startup (N registrations × O(N²) sort each) = O(N³). */
static long sortCallbacksSlow(int numCallbacks) {
List<String> names = new ArrayList<>();
for (int i = 0; i < numCallbacks; i++) names.add("callback_" + i);
// Simulate sortCallbacks: for each callback in names,
// getRIndex is called ~13 times on names (each O(N))
long ops = 0;
List<String> sorted = new ArrayList<>();
for (String name : names) {
// 13 getRIndex calls per callback
for (int call = 0; call < 13; call++) {
String target = "callback_" + (call % numCallbacks);
// getRIndex — scan from end
for (int i = names.size() - 1; i >= 0; i--) {
ops++;
if (names.get(i).equals(target)) break;
}
}
sorted.add(name);
}
return ops;
}
/** FAST: pre-build namesMap and sortedMap; each getRIndex call becomes O(1). */
static long sortCallbacksFast(int numCallbacks) {
List<String> names = new ArrayList<>();
for (int i = 0; i < numCallbacks; i++) names.add("callback_" + i);
// Build index map once: O(N)
Map<String, Integer> namesMap = new HashMap<>();
for (int i = 0; i < names.size(); i++) namesMap.put(names.get(i), i);
long ops = 0;
for (String name : names) {
// 13 map lookups per callback — each O(1)
for (int call = 0; call < 13; call++) {
String target = "callback_" + (call % numCallbacks);
ops++;
namesMap.get(target); // O(1)
}
}
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 gorm-0001: GORM CWE-407 ===");
System.out.println();
final int CALLBACKS = 200; // production-scale with many plugins
long s0 = sortCallbacksSlow(CALLBACKS), f0 = sortCallbacksFast(CALLBACKS);
bench("gorm-0001 sortCallbacks getRIndex linear scan × 13",
() -> sortCallbacksSlow(CALLBACKS), () -> sortCallbacksFast(CALLBACKS), s0, f0);
System.out.println();
int pass = 0;
assert s0 > f0 * 5 : "gorm-0001 expected >5x"; pass++;
System.out.printf("%d/1 PASS — gorm-0001: CWE-407 in GORM callback registration%n", pass);
System.out.printf("Hotpath: DB.AutoMigrate(), plugin Register() calls, test setup with gorm.Open()%n");
}
}