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::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, 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 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 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> memberSets, String target) { long ops = 0; for (Set 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> memberSets, String target) { long ops = 0; for (Set 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> memberSets = new ArrayList<>(N); for (int i = 0; i < N; i++) { Set 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); } }