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 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 first (O(1)), then Regexp array only on miss static boolean addCharsetMatchFast(Set strings, List 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 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 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 addCharsetDefault = Arrays.asList( "application/javascript", "application/xml", "application/xhtml+xml", "application/json", Pattern.compile("^text/") ); Set defaultStrings = new HashSet<>(Arrays.asList( "application/javascript", "application/xml", "application/xhtml+xml", "application/json" )); List 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 addCharsetLarge = new ArrayList<>(addCharsetDefault); Set 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 types = new ArrayList<>(); for (int i = 0; i < T; i++) types.add("application/type-" + i); Set 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."); } }