java-topology/defects/jami-daemon/unit/JamiDaemonTest.java

136 lines
5.6 KiB
Java

package unit;
import java.util.*;
/**
* JamiDaemonTest — CWE-407 benchmark
*
* Defects:
* 0001: std::find on replies vector O(n) per git commit in conversation history load
* (conversation.cpp:832, :837)
* 0002: std::find (algorithm) on std::set<string>::iterator — bypasses O(log n) set.find()
* (conversation_module.cpp:2341, :2501, :2797)
*/
public class JamiDaemonTest {
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(" %-56s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n",
label, sMs, sOps, fMs, fOps, r);
}
// -------------------------------------------------------------------------
// Defect 0001: replies is vector<string>, std::find used per commit
// Models the emplaceCb called once per git commit in loadMessages()
// C = commits, R = reply-chain IDs tracked at peak
// -------------------------------------------------------------------------
static long slowRepliesLoad(int commits, int maxReplies) {
List<String> replies = new ArrayList<>();
long ops = 0;
for (int i = 0; i < commits; i++) {
String replyTo = "reply-" + (i % maxReplies);
// std::find on replies vector: O(R)
boolean found = false;
for (String r : replies) { ops++; if (r.equals(replyTo)) { found = true; break; } }
if (!found) replies.add(replyTo);
String msgId = "msg-" + i;
// second std::find: O(R)
for (int j = 0; j < replies.size(); j++) {
ops++;
if (replies.get(j).equals(msgId)) { replies.remove(j); break; }
}
}
return ops;
}
static long fastRepliesLoad(int commits, int maxReplies) {
Set<String> replies = new HashSet<>();
long ops = 0;
for (int i = 0; i < commits; i++) {
String replyTo = "reply-" + (i % maxReplies);
ops++;
replies.add(replyTo); // O(1) insert (dedup automatic)
String msgId = "msg-" + i;
ops++;
replies.remove(msgId); // O(1) remove
}
return ops;
}
// -------------------------------------------------------------------------
// Defect 0002: std::find on std::set instead of set.find()
// Models needsSyncingWith() — loop over N conversations, each with M members
// -------------------------------------------------------------------------
static long slowSetFind(List<Set<String>> memberSets, String target) {
long ops = 0;
for (Set<String> members : memberSets) {
// std::find degrades to O(M) linear scan on set iterators
for (String m : members) {
ops++;
if (m.equals(target)) break;
}
}
return ops;
}
static long fastSetFind(List<Set<String>> memberSets, String target) {
long ops = 0;
for (Set<String> members : memberSets) {
ops++;
members.contains(target); // O(1) — uses set's own find
}
return ops;
}
public static void main(String[] args) {
System.out.println("JamiDaemonTest — CWE-407");
final int C = 2000; // commits in conversation history
final int R = 300; // distinct reply-chain IDs
final int N = 500; // conversations in needsSyncingWith
final int M = 50; // members per conversation
// Build data for defect 0002
List<Set<String>> memberSets = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
Set<String> s = new TreeSet<>(); // TreeSet ~ std::set ordering
for (int j = 0; j < M; j++) s.add("uri-" + i + "-" + j);
memberSets.add(s);
}
String target = "uri-250-25";
System.out.println("\n [0001] replies vector std::find per commit (C=" + C + ", R=" + R + ")");
bench("0001 replies vector.find vs unordered_set.find",
() -> slowRepliesLoad(C, R),
() -> fastRepliesLoad(C, R),
(long) C * R / 2, // avg scan depth ~R/2
C * 2); // 2 O(1) ops per commit
System.out.println("\n [0002] std::find on set vs set.count() per conversation (N=" + N + ", M=" + M + ")");
bench("0002 set linear-scan via iterator vs set.contains()",
() -> slowSetFind(memberSets, target),
() -> fastSetFind(memberSets, target),
(long) N * M,
N);
// Assertions
int pass = 0, total = 2;
long slow0001 = slowRepliesLoad(C, R);
long fast0001 = fastRepliesLoad(C, R);
if (slow0001 > fast0001 * 10) { pass++; System.out.println(" PASS 0001: slow ops >> fast ops (ratio ~" + slow0001/fast0001 + "x)"); }
else System.out.println(" FAIL 0001: slow=" + slow0001 + " fast=" + fast0001);
long slow0002 = slowSetFind(memberSets, target);
long fast0002 = fastSetFind(memberSets, target);
if (slow0002 > fast0002 * (M / 2)) { pass++; System.out.println(" PASS 0002: slow ops >> fast ops (ratio ~" + slow0002/fast0002 + "x)"); }
else System.out.println(" FAIL 0002: slow=" + slow0002 + " fast=" + fast0002);
System.out.printf("%n%d/%d PASS%n", pass, total);
if (pass < total) System.exit(1);
}
}