package unit; import java.util.*; /** * UnrealircdTest — CWE-407 benchmark for unrealircd-0001 * * Models has_common_channels(c1, c2): * SLOW: O(c1_channels × c2_channels) — IsMember = linked-list scan per channel * FAST: O(c1_channels + c2_channels) — pre-build HashSet of c2's channels * * Also models the WHO global scan: O(U × c1 × c2) vs O(U × (c1+c2)) */ public class UnrealircdTest { // ------------------------------------------------------------------------- // Data model // ------------------------------------------------------------------------- static class Channel { final int id; Channel(int id) { this.id = id; } } static class Client { final String nick; final List channels = new ArrayList<>(); Client(String nick) { this.nick = nick; } void join(Channel c) { channels.add(c); } } // ------------------------------------------------------------------------- // SLOW: IsMember = find_membership_link = O(n) list scan // ------------------------------------------------------------------------- /** Returns ops count (number of channel comparisons performed) */ static long hasCommonChannels_slow(Client c1, Client c2) { long ops = 0; for (Channel ch1 : c1.channels) { // IsMember(c2, ch1) = find_membership_link = O(c2.channels) for (Channel ch2 : c2.channels) { ops++; if (ch2 == ch1) break; } } return ops; } /** * WHO global scan: for each user in server, call hasCommonChannels_slow. * Returns total ops across all users. */ static long whoGlobalScan_slow(List users, Client requester) { long totalOps = 0; for (Client target : users) { if (target == requester) continue; totalOps += hasCommonChannels_slow(requester, target); } return totalOps; } // ------------------------------------------------------------------------- // FAST: pre-build HashSet of c2's channels → O(1) membership test // ------------------------------------------------------------------------- static long hasCommonChannels_fast(Client c1, Client c2) { long ops = 0; // Build O(c2) set Set c2set = new HashSet<>(c2.channels.size() * 2); for (Channel ch : c2.channels) { ops++; c2set.add(ch); } // O(c1) probe with O(1) set membership for (Channel ch : c1.channels) { ops++; if (c2set.contains(ch)) break; // found, same as early-exit in real code } return ops; } static long whoGlobalScan_fast(List users, Client requester) { long totalOps = 0; // Pre-build requester's channel set once Set reqSet = new HashSet<>(requester.channels.size() * 2); for (Channel ch : requester.channels) reqSet.add(ch); for (Client target : users) { if (target == requester) continue; long ops = reqSet.size(); // "build" cost amortised — count as 1 per target for (Channel ch : target.channels) { ops++; if (reqSet.contains(ch)) break; } totalOps += ops; } return totalOps; } // ------------------------------------------------------------------------- // Benchmark harness // ------------------------------------------------------------------------- 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(" %-52s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n", label, sMs, sOps, fMs, fOps, r); } // ------------------------------------------------------------------------- // Setup helpers // ------------------------------------------------------------------------- static List makeChannels(int n) { List list = new ArrayList<>(n); for (int i = 0; i < n; i++) list.add(new Channel(i)); return list; } static Client makeClient(String nick, List allChannels, int count, int offset) { Client c = new Client(nick); for (int i = 0; i < count; i++) c.join(allChannels.get((offset + i) % allChannels.size())); return c; } // ------------------------------------------------------------------------- // Main // ------------------------------------------------------------------------- public static void main(String[] args) { System.out.println("unrealircd-0001: has_common_channels CWE-407 benchmark"); System.out.println("======================================================="); // --- Scenario 1: single has_common_channels call, C=50 channels/user --- { int C = 50; List allChans = makeChannels(200); // c1 and c2 share ~half their channels (worst case: no early exit until middle) Client c1 = makeClient("alice", allChans, C, 0); Client c2 = makeClient("bob", allChans, C, C / 2); long[] sOps = {0}, fOps = {0}; Runnable slow = () -> { long ops = 0; for (int i = 0; i < 10_000; i++) ops += hasCommonChannels_slow(c1, c2); sOps[0] = ops; }; Runnable fast = () -> { long ops = 0; for (int i = 0; i < 10_000; i++) ops += hasCommonChannels_fast(c1, c2); fOps[0] = ops; }; slow.run(); fast.run(); bench("has_common_channels C=50 (10k calls)", slow, fast, sOps[0], fOps[0]); assert sOps[0] > fOps[0] * 5 : "Expected slow ops >> fast ops, got slow=" + sOps[0] + " fast=" + fOps[0]; } // --- Scenario 2: WHO global scan, U=500 users, C=30 channels/user --- { int U = 500, C = 30; List allChans = makeChannels(300); List users = new ArrayList<>(U); for (int i = 0; i < U; i++) users.add(makeClient("user" + i, allChans, C, i)); Client requester = users.get(0); long[] sOps = {0}, fOps = {0}; Runnable slow = () -> sOps[0] = whoGlobalScan_slow(users, requester); Runnable fast = () -> fOps[0] = whoGlobalScan_fast(users, requester); slow.run(); fast.run(); bench("who_global_scan U=500 C=30", slow, fast, sOps[0], fOps[0]); assert sOps[0] > fOps[0] * 3 : "Expected slow ops >> fast ops, got slow=" + sOps[0] + " fast=" + fOps[0]; } // --- Scenario 3: high channel density, C=100 channels/user --- { int C = 100; List allChans = makeChannels(500); Client c1 = makeClient("heavyuser1", allChans, C, 0); Client c2 = makeClient("heavyuser2", allChans, C, 50); long[] sOps = {0}, fOps = {0}; Runnable slow = () -> { long ops = 0; for (int i = 0; i < 5_000; i++) ops += hasCommonChannels_slow(c1, c2); sOps[0] = ops; }; Runnable fast = () -> { long ops = 0; for (int i = 0; i < 5_000; i++) ops += hasCommonChannels_fast(c1, c2); fOps[0] = ops; }; slow.run(); fast.run(); bench("has_common_channels C=100 (5k calls)", slow, fast, sOps[0], fOps[0]); assert sOps[0] > fOps[0] * 10 : "Expected slow ops >> fast ops, got slow=" + sOps[0] + " fast=" + fOps[0]; } // --- Scenario 4: unrealircd-0002 SJOIN member loop, M=500 members, C=50 chans/client --- { int M = 500, C = 50; List allChans = makeChannels(C + 50); Channel targetChan = allChans.get(0); // Create M clients each in C channels (targetChan is always one of them) List members = new ArrayList<>(M); for (int i = 0; i < M; i++) { Client cl = makeClient("member" + i, allChans, C - 1, i + 1); cl.join(targetChan); // each member is in targetChan members.add(cl); } // SLOW: for each member, find targetChan in their channel list = O(C) per member long[] sOps = {0}, fOps = {0}; Runnable slow = () -> { long ops = 0; for (Client cl : members) { // find_membership_link(cl->user->channel, targetChan) for (Channel ch : cl.channels) { ops++; if (ch == targetChan) break; } } sOps[0] = ops; }; // FAST: Member already has a direct reference to Membership (no search needed) Runnable fast = () -> { long ops = 0; // Direct access — O(1) per member because Member struct already holds // a reference to the client's Membership entry for this channel. for (Client cl : members) { ops++; // direct struct dereference, no scan } fOps[0] = ops; }; slow.run(); fast.run(); bench("sjoin-member-loop M=500 C=50", slow, fast, sOps[0], fOps[0]); assert sOps[0] > fOps[0] * 5 : "Expected slow ops >> fast ops, got slow=" + sOps[0] + " fast=" + fOps[0]; } System.out.println("\nAll assertions passed."); } }