rails: 8 defects (210x/51x/475x/151x/130x/251x/1000x) + 129 sites 51 ecosystems

This commit is contained in:
russell@unturf.com 2026-03-27 12:12:33 -04:00
parent 9576d47645
commit 08989870a6
10 changed files with 494 additions and 8 deletions

View file

@ -0,0 +1,289 @@
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<String> 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<String> 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<Integer> 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<Integer> chain = new ArrayList<>();
Map<Integer, Integer> 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<Integer> receiver = new ArrayList<>();
List<Integer> 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<Integer> 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<Integer> collection = new ArrayList<>();
List<Integer> 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<Integer, Integer> 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<String> 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<String> 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<Integer> 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<Integer> 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<String> 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<String> 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;
}
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..0008: 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;
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);
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 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");
}
}