java-topology/defects/efcore/unit/EfCoreTest.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

166 lines
7.1 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.*;
/**
* EfCoreTest — efcore-0001..0003
*
* Proves CWE-407 in Entity Framework Core:
* efcore-0001: PropertyExtensions.FindGenerationProperty() — BFS List.Contains() O(D²)
* efcore-0002: IReadOnlyProperty.AddPrincipals() — recursive List.Contains() O(P²)
* efcore-0003: ForeignKeyPropertyDiscoveryConvention — IReadOnlyList.Contains() in key loops
*
* Run: javac -d . EfCoreAlgorithm.java EfCoreTest.java && java -ea unit.EfCoreTest
*/
public class EfCoreTest {
// ── efcore-0001: FindGenerationProperty BFS ──────────────────────────────
/** SLOW: BFS with List.Contains() O(D) per step → O(D²) total */
static long findGenerationPropertySlow(int chainDepth) {
List<Integer> traversalList = new ArrayList<>();
traversalList.add(0);
long ops = 0;
int index = 0;
while (index < traversalList.size()) {
int current = traversalList.get(index);
if (current < chainDepth - 1) {
int next = current + 1;
for (Integer n : traversalList) { ops++; if (n.equals(next)) break; }
if (!traversalList.contains(next)) traversalList.add(next);
}
index++;
}
return ops;
}
/** FAST: BFS with HashSet O(1) per step → O(D) total */
static long findGenerationPropertyFast(int chainDepth) {
List<Integer> traversalList = new ArrayList<>();
Set<Integer> traversalSet = new HashSet<>();
traversalList.add(0); traversalSet.add(0);
long ops = 0;
int index = 0;
while (index < traversalList.size()) {
int current = traversalList.get(index);
if (current < chainDepth - 1) {
int next = current + 1;
ops++; // O(1) HashSet.add
if (traversalSet.add(next)) traversalList.add(next);
}
index++;
}
return ops;
}
// ── efcore-0002: AddPrincipals recursive principal traversal ─────────────
/** SLOW: recursive traversal with List.Contains() O(P) per step → O(P²) */
static long addPrincipalsSlow(int chainLength) {
List<Integer> visited = new ArrayList<>();
visited.add(0);
long[] ops = {0};
addPrincipalsRecSlow(0, chainLength, visited, ops);
return ops[0];
}
private static void addPrincipalsRecSlow(int current, int chainLen, List<Integer> visited, long[] ops) {
if (current >= chainLen - 1) return;
int principal = current + 1;
for (Integer v : visited) { ops[0]++; if (v.equals(principal)) return; }
visited.add(principal);
addPrincipalsRecSlow(principal, chainLen, visited, ops);
}
/** FAST: recursive traversal with HashSet O(1) per step → O(P) */
static long addPrincipalsFast(int chainLength) {
List<Integer> principals = new ArrayList<>();
Set<Integer> visited = new HashSet<>();
principals.add(0); visited.add(0);
long[] ops = {0};
addPrincipalsRecFast(0, chainLength, principals, visited, ops);
return ops[0];
}
private static void addPrincipalsRecFast(int current, int chainLen, List<Integer> principals,
Set<Integer> visited, long[] ops) {
if (current >= chainLen - 1) return;
int principal = current + 1;
ops[0]++; // O(1)
if (visited.add(principal)) {
principals.add(principal);
addPrincipalsRecFast(principal, chainLen, principals, visited, ops);
}
}
// ── efcore-0003: FK discovery key subset check ───────────────────────────
/** SLOW: IReadOnlyList.Contains() O(Fp) per (key,prop) pair → O(K×Kp×Fp) */
static long fkDiscoverySlow(int numKeys, int keyPropCount, int fkPropCount) {
List<Integer> foreignKeyProperties = new ArrayList<>();
for (int i = 0; i < fkPropCount; i++) foreignKeyProperties.add(i);
long ops = 0;
for (int k = 0; k < numKeys; k++) {
for (int kp = 0; kp < keyPropCount; kp++) {
int prop = kp % fkPropCount;
for (Integer fp : foreignKeyProperties) { ops++; if (fp.equals(prop)) break; }
}
}
return ops;
}
/** FAST: build HashSet once O(Fp), then O(1) per check → O(K×Kp + Fp) */
static long fkDiscoveryFast(int numKeys, int keyPropCount, int fkPropCount) {
List<Integer> foreignKeyProperties = new ArrayList<>();
for (int i = 0; i < fkPropCount; i++) foreignKeyProperties.add(i);
Set<Integer> fkPropsSet = new HashSet<>(foreignKeyProperties);
long ops = 0;
for (int k = 0; k < numKeys; k++) {
for (int kp = 0; kp < keyPropCount; kp++) {
int prop = kp % fkPropCount;
ops++; // O(1)
fkPropsSet.contains(prop);
}
}
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 efcore-0001..0003: EF Core CWE-407 ===");
System.out.println();
final int DEPTH = 500; // efcore-0001: FK chain depth
final int CHAIN = 500; // efcore-0002: principal chain length
final int KEYS = 50, KP = 10, FP = 100; // efcore-0003
long s0 = findGenerationPropertySlow(DEPTH), f0 = findGenerationPropertyFast(DEPTH);
bench("efcore-0001 FindGenerationProperty BFS List",
() -> findGenerationPropertySlow(DEPTH), () -> findGenerationPropertyFast(DEPTH), s0, f0);
long s1 = addPrincipalsSlow(CHAIN), f1 = addPrincipalsFast(CHAIN);
bench("efcore-0002 AddPrincipals recursive List",
() -> addPrincipalsSlow(CHAIN), () -> addPrincipalsFast(CHAIN), s1, f1);
long s2 = fkDiscoverySlow(KEYS, KP, FP), f2 = fkDiscoveryFast(KEYS, KP, FP);
bench("efcore-0003 FKDiscovery IReadOnlyList.Contains",
() -> fkDiscoverySlow(KEYS, KP, FP), () -> fkDiscoveryFast(KEYS, KP, FP), s2, f2);
System.out.println();
int pass = 0;
assert s0 > f0 * 5 : "efcore-0001 expected >5x"; pass++;
assert s1 > f1 * 5 : "efcore-0002 expected >5x"; pass++;
assert s2 > f2 * 5 : "efcore-0003 expected >5x"; pass++;
System.out.printf("%d/3 PASS — efcore-0001..0003: CWE-407 in EF Core metadata/model build%n", pass);
System.out.printf("Hotpaths: SaveChanges() FK propagation, GetPrincipals(), model-build convention%n");
}
}