136 lines
5.6 KiB
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);
|
|
}
|
|
}
|