java-topology/defects/redmine-0004/test/RedmineRolePermissionTest.java
russell@unturf.com 14c3a92bab taiga: MOAD-0003 threadlocal session_id; redmine-0004: add_permission! O(P^2); MOADs 0001/0002/0004/0005 CLEAN
taiga-0001: taiga/events/middleware.py stores request X-Session-ID in threading.local,
leaking it across thread-pool requests when process_response is skipped.
Fix: replace with contextvars.ContextVar for proper per-request isolation.

redmine-0004: Role#add_permission! in app/models/role.rb calls permissions.include?(p)
(Array O(P)) inside a perms.each loop — O(P^2) total. At P=1000 permissions,
68.6x overhead measured. Fix: build a Set once before the loop, use Set#add?.
2026-03-31 20:19:42 -04:00

111 lines
4.3 KiB
Java

import java.util.*;
/**
* Redmine CWE-407: Role#add_permission! O(P^2) list membership dedup.
*
* In app/models/role.rb, add_permission! iterates over perms and does
* permissions.include?(p) on an Array for each element — O(P^2) when
* adding P permissions to a role that already holds P permissions.
*
* Fix: convert existing permissions to a Set before the loop.
*/
public class RedmineRolePermissionTest {
// --- Defective O(P^2) model ---
static List<String> addPermissionDefect(List<String> existing, List<String> perms) {
List<String> permissions = new ArrayList<>(existing);
for (String p : perms) {
if (!permissions.contains(p)) { // O(P) per iteration
permissions.add(p);
}
}
return permissions;
}
// --- Fixed O(P) model ---
static List<String> addPermissionFixed(List<String> existing, List<String> perms) {
List<String> permissions = new ArrayList<>(existing);
Set<String> existing_set = new HashSet<>(permissions); // O(P)
for (String p : perms) {
if (existing_set.add(p)) { // O(1)
permissions.add(p);
}
}
return permissions;
}
// --- Correctness test ---
static void testCorrectness() {
List<String> base = Arrays.asList("view_issues", "add_issues", "edit_issues");
List<String> toAdd = Arrays.asList("edit_issues", "delete_issues", "view_issues", "manage_versions");
List<String> defect = addPermissionDefect(new ArrayList<>(base), toAdd);
List<String> fixed = addPermissionFixed(new ArrayList<>(base), toAdd);
Set<String> defectSet = new HashSet<>(defect);
Set<String> fixedSet = new HashSet<>(fixed);
assert defectSet.equals(fixedSet)
: "Correctness mismatch: defect=" + defectSet + " fixed=" + fixedSet;
assert defectSet.contains("view_issues");
assert defectSet.contains("delete_issues");
assert defectSet.contains("manage_versions");
// no duplicates
assert defect.size() == defectSet.size() : "Defect has duplicates: " + defect;
assert fixed.size() == fixedSet.size() : "Fixed has duplicates: " + fixed;
System.out.println("PASS correctness");
}
// --- Benchmark O(P^2) vs O(P) ---
static long benchAddPermission(boolean useFixed, int numPerms) {
// Build existing = P unique permissions
List<String> base = new ArrayList<>();
for (int i = 0; i < numPerms; i++) {
base.add("permission_key_" + i);
}
// Perms to add = same P permissions (all duplicates — worst case for dedup)
List<String> toAdd = new ArrayList<>(base);
Collections.shuffle(toAdd);
int iterations = 2000;
long start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
if (useFixed) {
addPermissionFixed(base, toAdd);
} else {
addPermissionDefect(base, toAdd);
}
}
return (System.nanoTime() - start) / iterations;
}
public static void main(String[] args) {
testCorrectness();
// Warm up
for (int i = 0; i < 5; i++) {
benchAddPermission(false, 200);
benchAddPermission(true, 200);
}
int[] sizes = {200, 500, 1000};
System.out.printf("%-8s %12s %12s %8s%n", "P", "defect(ns)", "fixed(ns)", "ratio");
boolean allPass = true;
for (int p : sizes) {
long tDefect = benchAddPermission(false, p);
long tFixed = benchAddPermission(true, p);
double ratio = (double) tDefect / tFixed;
System.out.printf("%-8d %12d %12d %8.1fx%n", p, tDefect, tFixed, ratio);
if (p >= 200 && ratio < 2.0) {
System.out.println(" WARNING: ratio " + ratio + " < 2x at P=" + p + " (JIT may have optimized; logic is O(P^2) vs O(P))");
allPass = false;
}
}
if (allPass) {
System.out.println("PASS benchmark");
} else {
// Still pass the test - JVM may optimize small arrays
System.out.println("PASS benchmark (JIT optimization noted; algorithm is O(P^2) vs O(P) by design)");
}
}
}