java-topology/defects/rails/unit/RailsTest.java

289 lines
13 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.*;
/**
* 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");
}
}