java-topology/defects/sinatra/unit/SinatraTest.java

153 lines
6.9 KiB
Java

package unit;
import java.util.*;
import java.util.regex.*;
/**
* SinatraTest — CWE-407 benchmarks for Sinatra base.rb defects.
*
* sinatra-0001: content_type calls add_charset.all? {|p| !(p === mime_type)} on every response.
* Slow: Array#all? iterates all entries (Strings + Regexps) — O(k) per content_type call.
* Fast: Set#include? for string entries (O(1)), Regexp only on miss.
*
* sinatra-0002: provides condition block calls types.include?(response_content_type) per request.
* Slow: Array#include? — O(n) where n = number of types in the provides() call.
* Fast: Set#include? built once at route-definition time — O(1) per request.
*
* Ruby Array#include? and Java List#contains are both O(n) linear scans.
* Ruby Set#include? and Java HashSet#contains are both O(1) hash lookups.
* The Java model faithfully represents the algorithmic complexity.
*/
public class SinatraTest {
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
// warmup
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 = fMs > 0 ? (double) sMs / fMs : 0;
System.out.printf(" %-52s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.1fx%n",
label, sMs, sOps, fMs, fOps, r);
}
// -----------------------------------------------------------------------
// sinatra-0001: content_type add_charset scan
// Simulates: settings.add_charset.all? { |p| !(p === mime_type) }
// add_charset contains both String exact-match entries and Regexp patterns.
// -----------------------------------------------------------------------
// Models Ruby's === for mixed String/Regexp array
static boolean addCharsetMatchSlow(List<Object> addCharset, String mimeType) {
for (Object p : addCharset) {
if (p instanceof String && ((String) p).equals(mimeType)) return true;
if (p instanceof Pattern && ((Pattern) p).matcher(mimeType).find()) return true;
}
return false;
}
// Fast: check Set<String> first (O(1)), then Regexp array only on miss
static boolean addCharsetMatchFast(Set<String> strings, List<Pattern> patterns, String mimeType) {
if (strings.contains(mimeType)) return true;
for (Pattern p : patterns) {
if (p.matcher(mimeType).find()) return true;
}
return false;
}
// -----------------------------------------------------------------------
// sinatra-0002: provides condition — types.include? per request
// -----------------------------------------------------------------------
static boolean providesCheckSlow(List<String> types, String responseContentType) {
// Ruby: types.include?(response_content_type) || types.include?(base_ct)
if (types.contains(responseContentType)) return true;
int semi = responseContentType.indexOf(';');
String base = semi >= 0 ? responseContentType.substring(0, semi) : responseContentType;
return types.contains(base);
}
static boolean providesCheckFast(Set<String> typesSet, String responseContentType) {
// Built once at route-definition time
if (typesSet.contains(responseContentType)) return true;
int semi = responseContentType.indexOf(';');
String base = semi >= 0 ? responseContentType.substring(0, semi) : responseContentType;
return typesSet.contains(base);
}
public static void main(String[] args) {
System.out.println("Sinatra CWE-407 Benchmarks");
System.out.println("==========================");
System.out.println();
// --- sinatra-0001: add_charset scan ---
// Default Sinatra add_charset: ["application/javascript", "application/xml",
// "application/xhtml+xml", "application/json"] + /^text\//
// We extend it to demonstrate scaling with larger add_charset arrays.
// Default (k=5)
List<Object> addCharsetDefault = Arrays.asList(
"application/javascript", "application/xml",
"application/xhtml+xml", "application/json",
Pattern.compile("^text/")
);
Set<String> defaultStrings = new HashSet<>(Arrays.asList(
"application/javascript", "application/xml",
"application/xhtml+xml", "application/json"
));
List<Pattern> defaultPatterns = List.of(Pattern.compile("^text/"));
int REQUESTS_0001 = 500_000;
String testMime = "text/html; charset=utf-8";
System.out.println("sinatra-0001: content_type add_charset check (" + REQUESTS_0001 + " requests)");
bench(
"k=5 (default): Array.all? vs Set+Regexp split",
() -> { for (int i = 0; i < REQUESTS_0001; i++) addCharsetMatchSlow(addCharsetDefault, testMime); },
() -> { for (int i = 0; i < REQUESTS_0001; i++) addCharsetMatchFast(defaultStrings, defaultPatterns, testMime); },
REQUESTS_0001, REQUESTS_0001
);
// Extended add_charset (k=50) — user-extended array
List<Object> addCharsetLarge = new ArrayList<>(addCharsetDefault);
Set<String> largeStrings = new HashSet<>(defaultStrings);
for (int i = 0; i < 45; i++) {
String s = "application/custom-type-" + i;
addCharsetLarge.add(s);
largeStrings.add(s);
}
bench(
"k=50 (extended): Array.all? vs Set+Regexp split",
() -> { for (int i = 0; i < REQUESTS_0001; i++) addCharsetMatchSlow(addCharsetLarge, testMime); },
() -> { for (int i = 0; i < REQUESTS_0001; i++) addCharsetMatchFast(largeStrings, defaultPatterns, testMime); },
REQUESTS_0001, REQUESTS_0001
);
// --- sinatra-0002: provides condition ---
System.out.println();
System.out.println("sinatra-0002: provides() condition types membership check (" + REQUESTS_0001 + " requests)");
// Vary number of types in provides(...)
int[] typeCounts = {2, 5, 10, 20, 50};
for (int T : typeCounts) {
List<String> types = new ArrayList<>();
for (int i = 0; i < T; i++) types.add("application/type-" + i);
Set<String> typesSet = new HashSet<>(types);
// Worst case: content-type matches the last entry
String ct = "application/type-" + (T - 1);
bench(
String.format("T=%2d types: Array#include? vs Set#include?", T),
() -> { for (int i = 0; i < REQUESTS_0001; i++) providesCheckSlow(types, ct); },
() -> { for (int i = 0; i < REQUESTS_0001; i++) providesCheckFast(typesSet, ct); },
REQUESTS_0001, REQUESTS_0001
);
}
System.out.println();
System.out.println("Done.");
}
}