ORM wave 2: 10 new defects — Active Record +3, Exposed +3, SeaORM +4 (167 sites, 64 ecosystems)

rails-0009: FilterAttributeHandler filter_parameters Array O(A×F) → Set (450×)
rails-0010: Encryption::AutoFilteredParameters two Array scans → Set (250×)
rails-0011: TimeZoneConversion skip_list Array O(M×C×S) → Set (20×)

exposed-0001: SchemaUtilityApi mapMissingColumnStatements O(N×M) → map (118×)
exposed-0002: IdentifierManagerApi isAKeyword O(K) linear → HashSet (144×)
exposed-0003: Table.clone consParams.map fresh List → hoisted HashSet (6×)

seaorm-0001: active_model establish_links leftover.any O(N²) → HashSet (501×)
seaorm-0002: rbac engine group_permissions .values().find() → HashMap by ID (502×)
seaorm-0003: schema builder sorted_tables Vec::contains → HashSet (500×)
seaorm-0004: TopologicalSort from_iter seen Vec O(N²) → BTreeSet (28×)

Unit tests: RailsTest 11/11, ExposedTest 3/3, SeaORMTest 4/4 PASS
Whitepaper: 157→167 sites, 62→64 ecosystems; §13.12 ORM Wave 2 added
This commit is contained in:
russell@unturf.com 2026-03-27 13:49:46 -04:00
parent d4ed2dff91
commit 547a9f5738
37 changed files with 2043 additions and 26 deletions

View file

@ -230,6 +230,116 @@ public class RailsTest {
return ops;
}
// ── rails-0009: FilterAttributeHandler filter_parameters Array ───────────
/** SLOW: filter_parameters Array#include? called per attribute per class — O(A×F) */
static long filterAttrHandlerSlow(int attributes, int existingFilters) {
List<String> filterParams = new ArrayList<>();
for (int i = 0; i < existingFilters; i++) filterParams.add("existing_filter_" + i);
long ops = 0;
// list.each { app.config.filter_parameters << filter unless filter_parameters.include?(filter) }
for (int a = 0; a < attributes; a++) {
String filter = "model.attr_" + a;
for (String f : filterParams) { ops++; if (f.equals(filter)) break; }
filterParams.add(filter);
}
return ops;
}
/** FAST: pre-build Set of filter_parameters — O(1) per attribute */
static long filterAttrHandlerFast(int attributes, int existingFilters) {
List<String> filterParams = new ArrayList<>();
Set<String> filterSet = new HashSet<>();
for (int i = 0; i < existingFilters; i++) {
filterParams.add("existing_filter_" + i);
filterSet.add("existing_filter_" + i);
}
long ops = 0;
for (int a = 0; a < attributes; a++) {
String filter = "model.attr_" + a;
ops++;
if (!filterSet.contains(filter)) {
filterParams.add(filter);
filterSet.add(filter);
}
}
return ops;
}
// ── rails-0010: Encryption::AutoFilteredParameters two Array scans ───────
/** SLOW: Array#include? on filter_params + Array#find on excluded_list per attribute — O(A×(F+X)) */
static long encryptionFilterSlow(int encryptedAttrs, int filterParams, int excludedCount) {
List<String> fp = new ArrayList<>();
for (int i = 0; i < filterParams; i++) fp.add("existing_" + i);
List<String> excluded = new ArrayList<>();
for (int i = 0; i < excludedCount; i++) excluded.add("excluded_" + i);
long ops = 0;
for (int a = 0; a < encryptedAttrs; a++) {
String filter = "model.secret_" + a;
// excluded_from_filter_parameters?.find — O(X)
for (String ex : excluded) { ops++; if (ex.equals(filter)) break; }
// filter_parameters.include? — O(F)
for (String f : fp) { ops++; if (f.equals(filter)) break; }
fp.add(filter);
}
return ops;
}
/** FAST: pre-build Sets for both lists — O(1) per attribute */
static long encryptionFilterFast(int encryptedAttrs, int filterParams, int excludedCount) {
Set<String> fpSet = new HashSet<>();
for (int i = 0; i < filterParams; i++) fpSet.add("existing_" + i);
Set<String> exSet = new HashSet<>();
for (int i = 0; i < excludedCount; i++) exSet.add("excluded_" + i);
long ops = 0;
for (int a = 0; a < encryptedAttrs; a++) {
String filter = "model.secret_" + a;
ops++; // excluded check O(1)
if (!exSet.contains(filter)) {
ops++; // filter_params check O(1)
if (!fpSet.contains(filter)) {
fpSet.add(filter);
}
}
}
return ops;
}
// ── rails-0011: TimeZoneConversion skip_list Array per column ─────────────
/** SLOW: skip_time_zone_conversion_for_attributes.include? per column per class — O(M×C×S) */
static long tzConversionSlow(int models, int columns, int skipListSize) {
List<String> skipList = new ArrayList<>();
for (int i = 0; i < skipListSize; i++) skipList.add("skip_col_" + i);
long ops = 0;
for (int m = 0; m < models; m++) {
// create_time_zone_conversion_attribute? called per column
for (int c = 0; c < columns; c++) {
String colName = "col_" + (c % (skipListSize * 2));
// skip_time_zone_conversion_for_attributes.include?(name) — O(S)
for (String s : skipList) { ops++; if (s.equals(colName)) break; }
}
}
return ops;
}
/** FAST: skip_list materialized as Set once per class — O(1) per column */
static long tzConversionFast(int models, int columns, int skipListSize) {
List<String> skipList = new ArrayList<>();
for (int i = 0; i < skipListSize; i++) skipList.add("skip_col_" + i);
long ops = 0;
for (int m = 0; m < models; m++) {
Set<String> skipSet = new HashSet<>(skipList); // O(S) once per class
for (int c = 0; c < columns; c++) {
String colName = "col_" + (c % (skipListSize * 2));
ops++; // O(1) set lookup
skipSet.contains(colName);
}
}
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;
@ -240,7 +350,7 @@ public class RailsTest {
}
public static void main(String[] args) {
System.out.println("=== UNIT rails-0001..0008: Ruby on Rails CWE-407 ===");
System.out.println("=== UNIT rails-0001..0011: Ruby on Rails CWE-407 ===");
System.out.println();
final int LOADERS=500, FUTURE=300, ROUNDS=20;
@ -250,6 +360,9 @@ public class RailsTest {
final int IDXS=500, CONSTR=200;
final int HOOKS=1000, RUN_ONCE=500;
final int ENUM=1000;
final int ATTRS=500, EXISTING_FP=200;
final int ENC_ATTRS=500, FP_SIZE=200, EXCL_SIZE=50;
final int MODELS=100, COLS=50, SKIP=20;
long s0=preloaderSlow(LOADERS,FUTURE,ROUNDS), f0=preloaderFast(LOADERS,FUTURE,ROUNDS);
bench("rails-0001 Preloader::Batch future_tables", ()->preloaderSlow(LOADERS,FUTURE,ROUNDS), ()->preloaderFast(LOADERS,FUTURE,ROUNDS), s0, f0);
@ -272,6 +385,15 @@ public class RailsTest {
long s6=enumValuesSlow(ENUM), f6=enumValuesFast(ENUM);
bench("rails-0008 Enum value_method_names", ()->enumValuesSlow(ENUM), ()->enumValuesFast(ENUM), s6, f6);
long s7=filterAttrHandlerSlow(ATTRS,EXISTING_FP), f7=filterAttrHandlerFast(ATTRS,EXISTING_FP);
bench("rails-0009 FilterAttributeHandler filter_params", ()->filterAttrHandlerSlow(ATTRS,EXISTING_FP), ()->filterAttrHandlerFast(ATTRS,EXISTING_FP), s7, f7);
long s8=encryptionFilterSlow(ENC_ATTRS,FP_SIZE,EXCL_SIZE), f8=encryptionFilterFast(ENC_ATTRS,FP_SIZE,EXCL_SIZE);
bench("rails-0010 Encryption::AutoFilteredParams", ()->encryptionFilterSlow(ENC_ATTRS,FP_SIZE,EXCL_SIZE), ()->encryptionFilterFast(ENC_ATTRS,FP_SIZE,EXCL_SIZE), s8, f8);
long s9=tzConversionSlow(MODELS,COLS,SKIP), f9=tzConversionFast(MODELS,COLS,SKIP);
bench("rails-0011 TimeZoneConversion skip_list", ()->tzConversionSlow(MODELS,COLS,SKIP), ()->tzConversionFast(MODELS,COLS,SKIP), s9, f9);
System.out.println();
int pass = 0;
assert s0 > f0 * 10 : "rails-0001 expected >10x"; pass++;
@ -281,9 +403,12 @@ public class RailsTest {
assert s4 > f4 * 5 : "rails-0005 expected >5x"; pass++;
assert s5 > f5 * 5 : "rails-0007 expected >5x"; pass++;
assert s6 > f6 * 5 : "rails-0008 expected >5x"; pass++;
assert s7 > f7 * 5 : "rails-0009 expected >5x"; pass++;
assert s8 > f8 * 5 : "rails-0010 expected >5x"; pass++;
assert s9 > f9 * 5 : "rails-0011 expected >5x"; pass++;
assert preloaderFast(10,5,2) >= 0; pass++;
System.out.printf("%d/8 PASS — rails-0001..0008: CWE-407 in preloader/callbacks/enumerable/schema/hooks/enum%n", pass);
System.out.printf("Hotpaths: Preloader::Batch, skip_callback, Enumerable#excluding, SchemaDumper, lazy_load_hooks%n");
System.out.printf("%d/11 PASS — rails-0001..0011: CWE-407 in preloader/callbacks/enumerable/schema/hooks/enum/filter/encryption/timezone%n", pass);
System.out.printf("Hotpaths: Preloader::Batch, skip_callback, Enumerable#excluding, SchemaDumper, lazy_load_hooks, FilterAttributeHandler, AutoFilteredParams, TimeZoneConversion%n");
}
}