153 lines
6.9 KiB
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.");
|
|
}
|
|
}
|