603 lines
28 KiB
Java
603 lines
28 KiB
Java
package unit;
|
||
|
||
import java.util.*;
|
||
|
||
/**
|
||
* RailsTest — rails-0001..0016
|
||
*
|
||
* 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²)
|
||
* rails-0009: FilterAttributeHandler — filter_params.include? in each loop O(A×F)
|
||
* rails-0010: Encryption::AutoFilteredParams — filter_parameters.include? in each O(A×F)
|
||
* rails-0011: TimeZoneConversion — skip_list.include? per column per model O(M×C×S)
|
||
* rails-0012: options_for_select — Array(selected).include? in container.map O(N×S)
|
||
* rails-0013: CollectionHelpers — Array(current_value).include? in render_collection O(C×V×4)
|
||
* rails-0014: ActiveJob::Arguments — symbol_keys.include? in transform_keys loop O(H×S)
|
||
* rails-0015: schema_statements — inserting.count(v) in detect loop O(V²) dupe check
|
||
* rails-0016: SQLite3Adapter — to_column_names.include? in copy_table_indexes O(I×C×N)
|
||
*
|
||
* 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;
|
||
}
|
||
|
||
// ── 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;
|
||
}
|
||
|
||
// ── rails-0012: options_for_select selected/disabled array ───────────────
|
||
|
||
/** SLOW: selected/disabled as Array — include? O(S) per option element */
|
||
static long optionsForSelectSlow(int numOptions, int numSelected) {
|
||
List<String> selected = new ArrayList<>();
|
||
for (int i = 0; i < numSelected; i++) selected.add("v" + i);
|
||
long ops = 0;
|
||
// container.map — per element check Array#include?
|
||
for (int i = 0; i < numOptions; i++) {
|
||
String value = "v" + (i % (numSelected * 2));
|
||
// Array(selected).include? value — O(S) scan
|
||
for (String s : selected) { ops++; if (s.equals(value)) break; }
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
/** FAST: selected/disabled as Set — O(1) per option */
|
||
static long optionsForSelectFast(int numOptions, int numSelected) {
|
||
Set<String> selectedSet = new HashSet<>();
|
||
for (int i = 0; i < numSelected; i++) selectedSet.add("v" + i);
|
||
long ops = 0;
|
||
for (int i = 0; i < numOptions; i++) {
|
||
String value = "v" + (i % (numSelected * 2));
|
||
ops++; // O(1) set lookup
|
||
selectedSet.contains(value);
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
// ── rails-0013: CollectionHelpers render_collection selected set ─────────
|
||
|
||
/** SLOW: Array(current_value).map.include? rebuilt per item per option type */
|
||
static long collectionHelpersSlow(int collSize, int optionValues, int optionTypes) {
|
||
// simulate [:checked, :selected, :disabled, :readonly] × collection size
|
||
List<String> optArr = new ArrayList<>();
|
||
for (int i = 0; i < optionValues; i++) optArr.add("item_" + i);
|
||
long ops = 0;
|
||
for (int item = 0; item < collSize; item++) {
|
||
String value = "item_" + (item % (optionValues * 2));
|
||
for (int t = 0; t < optionTypes; t++) {
|
||
// Array(current_value).map(&:to_s).include?(value.to_s) per option type
|
||
for (String v : optArr) { ops++; if (v.equals(value)) break; }
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
/** FAST: Sets pre-built before render_collection loop */
|
||
static long collectionHelpersFast(int collSize, int optionValues, int optionTypes) {
|
||
Set<String> optSet = new HashSet<>();
|
||
for (int i = 0; i < optionValues; i++) optSet.add("item_" + i);
|
||
long ops = 0;
|
||
for (int item = 0; item < collSize; item++) {
|
||
String value = "item_" + (item % (optionValues * 2));
|
||
for (int t = 0; t < optionTypes; t++) {
|
||
ops++; // O(1) set lookup
|
||
optSet.contains(value);
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
// ── rails-0014: ActiveJob::Arguments symbol_keys array in transform_keys ──
|
||
|
||
/** SLOW: symbol_keys.include? Array scan inside transform_keys loop */
|
||
static long symbolKeysSlow(int hashKeys, int symbolKeys) {
|
||
List<String> symArr = new ArrayList<>();
|
||
for (int i = 0; i < symbolKeys; i++) symArr.add("key_" + i);
|
||
long ops = 0;
|
||
// hash.to_h.transform_keys — iterate all hash keys, check each against symbol_keys
|
||
for (int k = 0; k < hashKeys; k++) {
|
||
String key = "key_" + (k % (symbolKeys * 2));
|
||
for (String s : symArr) { ops++; if (s.equals(key)) break; }
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
/** FAST: convert symbol_keys to Set before the loop */
|
||
static long symbolKeysFast(int hashKeys, int symbolKeys) {
|
||
Set<String> symSet = new HashSet<>();
|
||
for (int i = 0; i < symbolKeys; i++) symSet.add("key_" + i);
|
||
long ops = 0;
|
||
for (int k = 0; k < hashKeys; k++) {
|
||
String key = "key_" + (k % (symbolKeys * 2));
|
||
ops++; // O(1) set lookup
|
||
symSet.contains(key);
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
// ── rails-0015: schema_statements detect+count duplicate versions ─────────
|
||
|
||
/** SLOW: inserting.detect { |v| inserting.count(v) > 1 } — O(V²) */
|
||
static long duplicateVersionSlow(int versions) {
|
||
List<Integer> inserting = new ArrayList<>();
|
||
for (int i = 0; i < versions; i++) inserting.add(i * 100);
|
||
// no actual duplicate; worst case scans all
|
||
long ops = 0;
|
||
Integer dup = null;
|
||
for (Integer v : inserting) {
|
||
int c = 0;
|
||
for (Integer x : inserting) { ops++; if (x.equals(v)) c++; }
|
||
if (c > 1) { dup = v; break; }
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
/** FAST: tally-based frequency map, O(V) */
|
||
static long duplicateVersionFast(int versions) {
|
||
List<Integer> inserting = new ArrayList<>();
|
||
for (int i = 0; i < versions; i++) inserting.add(i * 100);
|
||
long ops = 0;
|
||
// build frequency map in one pass
|
||
Map<Integer, Integer> freq = new HashMap<>();
|
||
for (Integer v : inserting) { ops++; freq.merge(v, 1, Integer::sum); }
|
||
// scan for duplicate once
|
||
Integer dup = null;
|
||
for (Map.Entry<Integer, Integer> e : freq.entrySet()) {
|
||
ops++;
|
||
if (e.getValue() > 1) { dup = e.getKey(); break; }
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
// ── rails-0016: SQLite3 copy_table_indexes/copy_table_contents ───────────
|
||
|
||
/** SLOW: to_column_names.include? Array scan inside indexes.each × columns.select */
|
||
static long sqlite3CopyIndexesSlow(int numIndexes, int colsPerIndex, int tableColumns) {
|
||
List<String> toColumns = new ArrayList<>();
|
||
for (int i = 0; i < tableColumns; i++) toColumns.add("col_" + i);
|
||
long ops = 0;
|
||
for (int idx = 0; idx < numIndexes; idx++) {
|
||
// per index: to_column_names rebuilt as Array, then scanned per column
|
||
for (int c = 0; c < colsPerIndex; c++) {
|
||
String col = "col_" + (c % (tableColumns * 2));
|
||
for (String tc : toColumns) { ops++; if (tc.equals(col)) break; }
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
/** FAST: to_column_names as Set — built once per (or before) index loop */
|
||
static long sqlite3CopyIndexesFast(int numIndexes, int colsPerIndex, int tableColumns) {
|
||
Set<String> toColSet = new HashSet<>();
|
||
for (int i = 0; i < tableColumns; i++) toColSet.add("col_" + i);
|
||
long ops = 0;
|
||
for (int idx = 0; idx < numIndexes; idx++) {
|
||
for (int c = 0; c < colsPerIndex; c++) {
|
||
String col = "col_" + (c % (tableColumns * 2));
|
||
ops++; // O(1) set lookup
|
||
toColSet.contains(col);
|
||
}
|
||
}
|
||
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..0016: 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);
|
||
|
||
final int OPT_N=500, OPT_S=50;
|
||
long s10=optionsForSelectSlow(OPT_N,OPT_S), f10=optionsForSelectFast(OPT_N,OPT_S);
|
||
bench("rails-0012 options_for_select selected Array", ()->optionsForSelectSlow(OPT_N,OPT_S), ()->optionsForSelectFast(OPT_N,OPT_S), s10, f10);
|
||
|
||
final int COLL_SIZE=200, COLL_VALS=20, OPT_TYPES=4;
|
||
long s11=collectionHelpersSlow(COLL_SIZE,COLL_VALS,OPT_TYPES), f11=collectionHelpersFast(COLL_SIZE,COLL_VALS,OPT_TYPES);
|
||
bench("rails-0013 CollectionHelpers option Array rebuild", ()->collectionHelpersSlow(COLL_SIZE,COLL_VALS,OPT_TYPES), ()->collectionHelpersFast(COLL_SIZE,COLL_VALS,OPT_TYPES), s11, f11);
|
||
|
||
final int HASH_KEYS=100, SYM_KEYS=30;
|
||
long s12=symbolKeysSlow(HASH_KEYS,SYM_KEYS), f12=symbolKeysFast(HASH_KEYS,SYM_KEYS);
|
||
bench("rails-0014 ActiveJob symbol_keys Array", ()->symbolKeysSlow(HASH_KEYS,SYM_KEYS), ()->symbolKeysFast(HASH_KEYS,SYM_KEYS), s12, f12);
|
||
|
||
final int DUP_VERS=500;
|
||
long s13=duplicateVersionSlow(DUP_VERS), f13=duplicateVersionFast(DUP_VERS);
|
||
bench("rails-0015 schema_statements detect+count O(V²)", ()->duplicateVersionSlow(DUP_VERS), ()->duplicateVersionFast(DUP_VERS), s13, f13);
|
||
|
||
final int IDXS2=20, COLS_PER=10, TCOLS=50;
|
||
long s14=sqlite3CopyIndexesSlow(IDXS2,COLS_PER,TCOLS), f14=sqlite3CopyIndexesFast(IDXS2,COLS_PER,TCOLS);
|
||
bench("rails-0016 SQLite3 copy_table to_column_names Array", ()->sqlite3CopyIndexesSlow(IDXS2,COLS_PER,TCOLS), ()->sqlite3CopyIndexesFast(IDXS2,COLS_PER,TCOLS), s14, f14);
|
||
|
||
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 s10 > f10 * 5 : "rails-0012 expected >5x"; pass++;
|
||
assert s11 > f11 * 5 : "rails-0013 expected >5x"; pass++;
|
||
assert s12 > f12 * 5 : "rails-0014 expected >5x"; pass++;
|
||
assert s13 > f13 * 5 : "rails-0015 expected >5x"; pass++;
|
||
assert s14 > f14 * 5 : "rails-0016 expected >5x"; pass++;
|
||
assert preloaderFast(10,5,2) >= 0; pass++;
|
||
|
||
System.out.printf("%d/16 PASS — rails-0001..0016: CWE-407 in preloader/callbacks/enumerable/schema/hooks/enum/filter/encryption/timezone/view/job/sqlite%n", pass);
|
||
System.out.printf("Hotpaths: Preloader::Batch, skip_callback, Enumerable#excluding, SchemaDumper, lazy_load_hooks, FilterAttributeHandler, AutoFilteredParams, TimeZoneConversion, options_for_select, CollectionHelpers, ActiveJob::Arguments, schema_statements, SQLite3Adapter%n");
|
||
}
|
||
}
|