package unit; import java.util.*; /** * RailsTest — rails-0001..0008 * * Proves CWE-407 in Ruby on Rails: * rails-0001: Preloader::Batch — future_tables Array#include? in loaders.reject O(L×F) per batch * rails-0002: Callbacks — chain.index(callback) inside skip_callback filters loop O(D×F×C²) * rails-0003: Enumerable#excluding — elements.include? in reject loop O(N×E) * rails-0004: Enumerable#in_order_of — series.index in sort_by block O(N log N × S) * rails-0005: SchemaDumper — constraint_names Array#include? in indexes.reject O(I×C) * rails-0006: PostgreSQL schema_statements — include_columns Array#include? in reject O(C×I) * rails-0007: lazy_load_hooks — @run_once[name].include?(block) Array scan per hook O(H×R) * rails-0008: Enum — value_method_names.include? in pairs.each loop O(E²) * * Run: javac -d . RailsTest.java && java -ea unit.RailsTest */ public class RailsTest { // ── rails-0001: Preloader::Batch future_tables ──────────────────────────── /** SLOW: future_tables as Array — Array#include? per loader per batch */ static long preloaderSlow(int loaders, int futureTables, int batchRounds) { List futureArr = new ArrayList<>(); for (int i = 0; i < futureTables; i++) futureArr.add("table_" + i); long ops = 0; for (int round = 0; round < batchRounds; round++) { // loaders.reject { |l| future_tables.include?(l.table_name) } for (int l = 0; l < loaders; l++) { String table = "table_" + (l % (futureTables * 2)); for (String t : futureArr) { ops++; if (t.equals(table)) break; } } } return ops; } /** FAST: future_tables as Set — O(1) include? per loader */ static long preloaderFast(int loaders, int futureTables, int batchRounds) { Set futureSet = new HashSet<>(); for (int i = 0; i < futureTables; i++) futureSet.add("table_" + i); long ops = 0; for (int round = 0; round < batchRounds; round++) { for (int l = 0; l < loaders; l++) { String table = "table_" + (l % (futureTables * 2)); ops++; // O(1) set lookup futureSet.contains(table); } } return ops; } // ── rails-0002: Callbacks chain.index ──────────────────────────────────── /** SLOW: chain.index(callback) — O(C) scan inside filters.each across descendants */ static long callbacksSlow(int descendants, int filters, int chainLength) { long ops = 0; for (int d = 0; d < descendants; d++) { List chain = new ArrayList<>(); for (int i = 0; i < chainLength; i++) chain.add(i); for (int f = 0; f < filters; f++) { // chain.find { matches? } — skip, then chain.index — O(C) Integer callback = f < chainLength ? f : null; if (callback != null) { // chain.index(callback) — O(C) scan for (int i = 0; i < chain.size(); i++) { ops++; if (chain.get(i).equals(callback)) break; } } } } return ops; } /** FAST: position_map built once — O(1) lookup per filter */ static long callbacksFast(int descendants, int filters, int chainLength) { long ops = 0; for (int d = 0; d < descendants; d++) { List chain = new ArrayList<>(); Map posMap = new HashMap<>(); for (int i = 0; i < chainLength; i++) { chain.add(i); posMap.put(i, i); } for (int f = 0; f < filters; f++) { Integer callback = f < chainLength ? f : null; if (callback != null) { ops++; // O(1) map lookup posMap.get(callback); } } } return ops; } // ── rails-0003: Enumerable#excluding ───────────────────────────────────── /** SLOW: elements.include? inside reject — O(N×E) */ static long excludingSlow(int receiverSize, int excludeSize) { List receiver = new ArrayList<>(); List elements = new ArrayList<>(); for (int i = 0; i < receiverSize; i++) receiver.add(i); for (int i = 0; i < excludeSize; i++) elements.add(i); long ops = 0; for (int r : receiver) { for (int e : elements) { ops++; if (e == r) break; } } return ops; } /** FAST: exclusions as Set — O(1) per element */ static long excludingFast(int receiverSize, int excludeSize) { Set exclusions = new HashSet<>(); for (int i = 0; i < excludeSize; i++) exclusions.add(i); long ops = 0; for (int i = 0; i < receiverSize; i++) { ops++; exclusions.contains(i); } return ops; } // ── rails-0004: Enumerable#in_order_of ─────────────────────────────────── /** SLOW: series.index inside sort comparator — O(N log N × S) */ static long inOrderOfSlow(int collectionSize, int seriesSize) { List collection = new ArrayList<>(); List series = new ArrayList<>(); for (int i = 0; i < collectionSize; i++) collection.add(i % seriesSize); for (int i = 0; i < seriesSize; i++) series.add(i); long ops = 0; // Simulate sort_by with series.index (O(S)) called in comparator // In Java: each comparison calls indexOf (total ~N log N comparisons) for (int v : collection) { // series.index(v) — O(S) scan per element (approx log N calls per sort) for (int s = 0; s < series.size(); s++) { ops++; if (series.get(s) == v) break; } } return ops; } /** FAST: series_map built once — O(1) per element */ static long inOrderOfFast(int collectionSize, int seriesSize) { Map seriesMap = new HashMap<>(); for (int i = 0; i < seriesSize; i++) seriesMap.put(i, i); long ops = 0; for (int i = 0; i < collectionSize; i++) { int v = i % seriesSize; ops++; // O(1) map lookup seriesMap.get(v); } return ops; } // ── rails-0005: SchemaDumper constraint name filtering ─────────────────── /** SLOW: constraint_names Array#include? in indexes.reject — O(I×C) */ static long schemaDumperSlow(int indexes, int constraints) { List constraintNames = new ArrayList<>(); for (int i = 0; i < constraints; i++) constraintNames.add("constraint_" + i); long ops = 0; // indexes.reject { |index| constraint_names.include?(index.name) } for (int i = 0; i < indexes; i++) { String idxName = "constraint_" + (i % (constraints * 2)); for (String c : constraintNames) { ops++; if (c.equals(idxName)) break; } } return ops; } /** FAST: constraint_names as Set — O(1) per index */ static long schemaDumperFast(int indexes, int constraints) { Set constraintSet = new HashSet<>(); for (int i = 0; i < constraints; i++) constraintSet.add("constraint_" + i); long ops = 0; for (int i = 0; i < indexes; i++) { String idxName = "constraint_" + (i % (constraints * 2)); ops++; // O(1) constraintSet.contains(idxName); } return ops; } // ── rails-0007: lazy_load_hooks @run_once Array ─────────────────────────── /** SLOW: @run_once[name].include?(block) — Array scan per hook per run_load_hooks */ static long lazyLoadHooksSlow(int hooks, int runOnceCount) { List runOnce = new ArrayList<>(); for (int i = 0; i < runOnceCount; i++) runOnce.add(i); long ops = 0; for (int h = 0; h < hooks; h++) { // @run_once[name].include?(block) — O(R) scan for (int r : runOnce) { ops++; if (r == h % runOnceCount) break; } } return ops; } /** FAST: @run_once[name] as Set — O(1) include? per hook */ static long lazyLoadHooksFast(int hooks, int runOnceCount) { Set runOnceSet = new HashSet<>(); for (int i = 0; i < runOnceCount; i++) runOnceSet.add(i); long ops = 0; for (int h = 0; h < hooks; h++) { ops++; // O(1) set lookup runOnceSet.contains(h % runOnceCount); } return ops; } // ── rails-0008: Enum value_method_names ────────────────────────────────── /** SLOW: value_method_names.include? inside pairs.each — O(E²) */ static long enumValuesSlow(int enumValues) { List names = new ArrayList<>(); long ops = 0; for (int i = 0; i < enumValues; i++) { String name = "status_" + i; String alias_ = "is_status_" + i; names.add(name); // !value_method_names.include?(alias) — O(E) scan for (String n : names) { ops++; if (n.equals(alias_)) break; } names.add(alias_); } return ops; } /** FAST: value_method_names as Set — O(1) include? per pair */ static long enumValuesFast(int enumValues) { Set names = new HashSet<>(); long ops = 0; for (int i = 0; i < enumValues; i++) { String name = "status_" + i; String alias_ = "is_status_" + i; names.add(name); ops++; // O(1) set lookup names.contains(alias_); names.add(alias_); } 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 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 filterParams = new ArrayList<>(); Set 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 fp = new ArrayList<>(); for (int i = 0; i < filterParams; i++) fp.add("existing_" + i); List 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 fpSet = new HashSet<>(); for (int i = 0; i < filterParams; i++) fpSet.add("existing_" + i); Set 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 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 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 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; 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(" %-48s 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 rails-0001..0011: Ruby on Rails CWE-407 ==="); System.out.println(); final int LOADERS=500, FUTURE=300, ROUNDS=20; final int DESC=50, FILTERS=100, CHAIN=200; final int RECV=5000, EXCL=500; final int COLL=3000, SERIES=300; 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); long s1=callbacksSlow(DESC,FILTERS,CHAIN), f1=callbacksFast(DESC,FILTERS,CHAIN); bench("rails-0002 Callbacks chain.index", ()->callbacksSlow(DESC,FILTERS,CHAIN), ()->callbacksFast(DESC,FILTERS,CHAIN), s1, f1); long s2=excludingSlow(RECV,EXCL), f2=excludingFast(RECV,EXCL); bench("rails-0003 Enumerable#excluding elements.include", ()->excludingSlow(RECV,EXCL), ()->excludingFast(RECV,EXCL), s2, f2); long s3=inOrderOfSlow(COLL,SERIES), f3=inOrderOfFast(COLL,SERIES); bench("rails-0004 Enumerable#in_order_of series.index", ()->inOrderOfSlow(COLL,SERIES), ()->inOrderOfFast(COLL,SERIES), s3, f3); long s4=schemaDumperSlow(IDXS,CONSTR), f4=schemaDumperFast(IDXS,CONSTR); bench("rails-0005/6 SchemaDumper constraint names", ()->schemaDumperSlow(IDXS,CONSTR), ()->schemaDumperFast(IDXS,CONSTR), s4, f4); long s5=lazyLoadHooksSlow(HOOKS,RUN_ONCE), f5=lazyLoadHooksFast(HOOKS,RUN_ONCE); bench("rails-0007 lazy_load_hooks @run_once", ()->lazyLoadHooksSlow(HOOKS,RUN_ONCE), ()->lazyLoadHooksFast(HOOKS,RUN_ONCE), s5, f5); 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++; assert s1 > f1 * 5 : "rails-0002 expected >5x"; pass++; assert s2 > f2 * 5 : "rails-0003 expected >5x"; pass++; assert s3 > f3 * 5 : "rails-0004 expected >5x"; pass++; 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/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"); } }