java-topology/defects/unrealircd/unit/UnrealircdTest.java

248 lines
9.9 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<Channel> 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<Client> 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<Channel> 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<Client> users, Client requester) {
long totalOps = 0;
// Pre-build requester's channel set once
Set<Channel> 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<Channel> makeChannels(int n) {
List<Channel> list = new ArrayList<>(n);
for (int i = 0; i < n; i++) list.add(new Channel(i));
return list;
}
static Client makeClient(String nick, List<Channel> 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<Channel> 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<Channel> allChans = makeChannels(300);
List<Client> 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<Channel> 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<Channel> allChans = makeChannels(C + 50);
Channel targetChan = allChans.get(0);
// Create M clients each in C channels (targetChan is always one of them)
List<Client> 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.");
}
}