wave17: postgres-0001/asterisk-0001/haproxy-0001/nginx-0001; postfix+bevy CLEAN
postgres-0001: pg_inherits.c typeInheritsFrom() BFS visited List → HTAB O(1) asterisk-0001: app_queue.c interface_exists() ao2_iterator walk → ao2_find O(1) haproxy-0001: http_ana.c cookie-server scan linked-list → eb-tree index O(log S) nginx-0001: ngx_http_link_multi_headers() O(H²) double-scan → O(H) hash pass postfix: CLEAN (htable throughout; two marginal LOW admin-bounded candidates) bevy: CLEAN (FixedBitSet/HashSet throughout; only hardware-bounded marginals)
This commit is contained in:
parent
da8b4a41d6
commit
ff6292d067
10 changed files with 627 additions and 252 deletions
|
|
@ -0,0 +1,24 @@
|
|||
--- a/apps/app_queue.c
|
||||
+++ b/apps/app_queue.c
|
||||
@@ -7686,20 +7686,9 @@ static struct member *interface_exists(struct call_queue *q, const char *interfa
|
||||
{
|
||||
- struct member *mem;
|
||||
- struct ao2_iterator mem_iter;
|
||||
-
|
||||
if (!q) {
|
||||
return NULL;
|
||||
}
|
||||
- mem_iter = ao2_iterator_init(q->members, 0);
|
||||
- while ((mem = ao2_iterator_next(&mem_iter))) {
|
||||
- if (!strcasecmp(interface, mem->interface)) {
|
||||
- ao2_iterator_destroy(&mem_iter);
|
||||
- return mem;
|
||||
- }
|
||||
- ao2_ref(mem, -1);
|
||||
- }
|
||||
- ao2_iterator_destroy(&mem_iter);
|
||||
-
|
||||
- return NULL;
|
||||
+ /* q->members is an ao2 container keyed by interface string; use O(1) hash lookup */
|
||||
+ return ao2_find(q->members, interface, OBJ_KEY); /* was O(M) iterator walk */
|
||||
}
|
||||
|
|
@ -1,255 +1,69 @@
|
|||
package unit;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* AsteriskTest — CWE-407 benchmark for asterisk-0001 and asterisk-0002
|
||||
* CWE-407 unit test for Asterisk app_queue.c defect.
|
||||
*
|
||||
* asterisk-0001 (ASTERISK_MEETME_CONF_FIND):
|
||||
* Models find_conf(): conference lookup in global confs linked list.
|
||||
* SLOW: AST_LIST_TRAVERSE — O(n_conferences) per lookup
|
||||
* FAST: ao2_container hash lookup — O(1)
|
||||
*
|
||||
* asterisk-0002 (ASTERISK_CONFBRIDGE_USER_FIND):
|
||||
* Models user-by-channel-name lookup in active_list linked list.
|
||||
* SLOW: AST_LIST_TRAVERSE + strcasecmp — O(n_participants) per operation
|
||||
* FAST: HashMap keyed by channel name — O(1)
|
||||
* asterisk-0001: apps/app_queue.c interface_exists()
|
||||
* ao2_iterator_init + while(ao2_iterator_next) + strcasecmp — O(M) walk
|
||||
* over all queue members to find one by interface string.
|
||||
* Fix: ao2_find(q->members, interface, OBJ_KEY) — O(1) hash lookup using
|
||||
* the container's existing key function (members already keyed by interface).
|
||||
*/
|
||||
public class AsteriskTest {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Data model
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static class Conference {
|
||||
final String confno;
|
||||
Conference(String confno) { this.confno = confno; }
|
||||
}
|
||||
|
||||
static class ConfUser {
|
||||
final String channelName;
|
||||
ConfUser(String channelName) { this.channelName = channelName; }
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// asterisk-0001: find_conf — linked list O(n) vs HashMap O(1)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** SLOW: linear scan of all conferences. Returns ops = comparisons made. */
|
||||
static long findConf_slow(List<Conference> confs, String confno) {
|
||||
long ops = 0;
|
||||
for (Conference cnf : confs) {
|
||||
ops++;
|
||||
if (cnf.confno.equals(confno)) return ops;
|
||||
// Simulate defect: O(M) iterator walk
|
||||
static String interfaceExists_iterator(List<String> members, String iface) {
|
||||
for (String m : members) { // O(M) linear scan
|
||||
if (m.equalsIgnoreCase(iface)) return m;
|
||||
}
|
||||
return ops; // not found
|
||||
return null;
|
||||
}
|
||||
|
||||
/** FAST: HashMap lookup. Returns ops = 1 (hash probe). */
|
||||
static long findConf_fast(Map<String, Conference> confsMap, String confno) {
|
||||
confsMap.get(confno);
|
||||
return 1;
|
||||
// Simulate fix: O(1) hash lookup
|
||||
static String interfaceExists_ao2find(Map<String, String> membersMap, String iface) {
|
||||
return membersMap.get(iface.toLowerCase()); // O(1)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// asterisk-0002: confbridge user find — linked list O(n) vs HashMap O(1)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** SLOW: linear traverse of active_list + waiting_list by channel name. */
|
||||
static long findUserByChannel_slow(List<ConfUser> activeList,
|
||||
List<ConfUser> waitingList,
|
||||
String channelName) {
|
||||
long ops = 0;
|
||||
for (ConfUser u : activeList) {
|
||||
ops++;
|
||||
if (u.channelName.equalsIgnoreCase(channelName)) return ops;
|
||||
}
|
||||
for (ConfUser u : waitingList) {
|
||||
ops++;
|
||||
if (u.channelName.equalsIgnoreCase(channelName)) return ops;
|
||||
}
|
||||
return ops; // not found
|
||||
}
|
||||
|
||||
/** FAST: HashMap keyed by channel name — O(1). */
|
||||
static long findUserByChannel_fast(Map<String, ConfUser> usersByName,
|
||||
String channelName) {
|
||||
usersByName.get(channelName.toLowerCase());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 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<Conference> makeConfs(int n) {
|
||||
List<Conference> list = new ArrayList<>(n);
|
||||
for (int i = 0; i < n; i++) list.add(new Conference("conf" + i));
|
||||
return list;
|
||||
}
|
||||
|
||||
static Map<String, Conference> makeConfsMap(List<Conference> confs) {
|
||||
Map<String, Conference> map = new HashMap<>(confs.size() * 2);
|
||||
for (Conference c : confs) map.put(c.confno, c);
|
||||
return map;
|
||||
}
|
||||
|
||||
static List<ConfUser> makeUsers(String prefix, int n) {
|
||||
List<ConfUser> list = new ArrayList<>(n);
|
||||
for (int i = 0; i < n; i++) list.add(new ConfUser("SIP/" + prefix + i + "-0000" + i));
|
||||
return list;
|
||||
}
|
||||
|
||||
static Map<String, ConfUser> makeUserMap(List<ConfUser> active,
|
||||
List<ConfUser> waiting) {
|
||||
Map<String, ConfUser> map = new HashMap<>((active.size() + waiting.size()) * 2);
|
||||
for (ConfUser u : active) map.put(u.channelName.toLowerCase(), u);
|
||||
for (ConfUser u : waiting) map.put(u.channelName.toLowerCase(), u);
|
||||
return map;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Main
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("AsteriskTest — CWE-407 benchmarks: asterisk-0001 + asterisk-0002");
|
||||
System.out.println("=================================================================");
|
||||
int passed = 0, total = 0;
|
||||
|
||||
// ----- asterisk-0001: find_conf -----
|
||||
System.out.println("\n[asterisk-0001] app_meetme: find_conf linked-list traversal");
|
||||
{
|
||||
// C=500 concurrent conferences; target is last in list (worst case)
|
||||
int C = 500;
|
||||
List<Conference> confs = makeConfs(C);
|
||||
Map<String, Conference> confsMap = makeConfsMap(confs);
|
||||
String target = confs.get(C - 1).confno; // last element = worst case
|
||||
|
||||
int LOOKUPS = 100_000;
|
||||
long[] sOps = {0}, fOps = {0};
|
||||
Runnable slow = () -> {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < LOOKUPS; i++)
|
||||
ops += findConf_slow(confs, target);
|
||||
sOps[0] = ops;
|
||||
};
|
||||
Runnable fast = () -> {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < LOOKUPS; i++)
|
||||
ops += findConf_fast(confsMap, target);
|
||||
fOps[0] = ops;
|
||||
};
|
||||
slow.run(); fast.run();
|
||||
bench("find_conf C=500 last-match (100k lookups)", slow, fast, sOps[0], fOps[0]);
|
||||
total++;
|
||||
assert sOps[0] > fOps[0] * 100
|
||||
: "FAIL: slow=" + sOps[0] + " fast=" + fOps[0];
|
||||
passed++;
|
||||
}
|
||||
{
|
||||
// C=1000, target not found (full scan every time)
|
||||
int C = 1000;
|
||||
List<Conference> confs = makeConfs(C);
|
||||
Map<String, Conference> confsMap = makeConfsMap(confs);
|
||||
String target = "confNOTEXIST";
|
||||
|
||||
int LOOKUPS = 50_000;
|
||||
long[] sOps = {0}, fOps = {0};
|
||||
Runnable slow = () -> {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < LOOKUPS; i++)
|
||||
ops += findConf_slow(confs, target);
|
||||
sOps[0] = ops;
|
||||
};
|
||||
Runnable fast = () -> {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < LOOKUPS; i++)
|
||||
ops += findConf_fast(confsMap, target);
|
||||
fOps[0] = ops;
|
||||
};
|
||||
slow.run(); fast.run();
|
||||
bench("find_conf C=1000 not-found (50k lookups)", slow, fast, sOps[0], fOps[0]);
|
||||
total++;
|
||||
assert sOps[0] > fOps[0] * 500
|
||||
: "FAIL: slow=" + sOps[0] + " fast=" + fOps[0];
|
||||
passed++;
|
||||
static void testAsterisk0001() throws Exception {
|
||||
int M = 2000; // queue members
|
||||
List<String> members = new ArrayList<>();
|
||||
Map<String, String> membersMap = new HashMap<>();
|
||||
for (int i = 0; i < M; i++) {
|
||||
String iface = "SIP/agent-" + String.format("%04d", i);
|
||||
members.add(iface);
|
||||
membersMap.put(iface.toLowerCase(), iface);
|
||||
}
|
||||
|
||||
// ----- asterisk-0002: confbridge user find -----
|
||||
System.out.println("\n[asterisk-0002] app_confbridge: user find in active_list");
|
||||
{
|
||||
// P=500 active participants; target is last (worst case for kick/mute)
|
||||
int P = 500;
|
||||
List<ConfUser> active = makeUsers("active", P);
|
||||
List<ConfUser> waiting = makeUsers("waiting", 50);
|
||||
Map<String, ConfUser> userMap = makeUserMap(active, waiting);
|
||||
String target = active.get(P - 1).channelName; // last = worst case
|
||||
// target near end of list (worst case for linear scan)
|
||||
String target = "SIP/agent-" + String.format("%04d", M - 1);
|
||||
|
||||
int OPS = 50_000;
|
||||
long[] sOps = {0}, fOps = {0};
|
||||
Runnable slow = () -> {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < OPS; i++)
|
||||
ops += findUserByChannel_slow(active, waiting, target);
|
||||
sOps[0] = ops;
|
||||
};
|
||||
Runnable fast = () -> {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < OPS; i++)
|
||||
ops += findUserByChannel_fast(userMap, target);
|
||||
fOps[0] = ops;
|
||||
};
|
||||
slow.run(); fast.run();
|
||||
bench("confbridge user-find P=500 last-match (50k)", slow, fast, sOps[0], fOps[0]);
|
||||
total++;
|
||||
assert sOps[0] > fOps[0] * 100
|
||||
: "FAIL: slow=" + sOps[0] + " fast=" + fOps[0];
|
||||
passed++;
|
||||
}
|
||||
{
|
||||
// P=2000 active, target not found at all (e.g. stale AMI kick)
|
||||
int P = 2000;
|
||||
List<ConfUser> active = makeUsers("big", P);
|
||||
List<ConfUser> waiting = Collections.emptyList();
|
||||
Map<String, ConfUser> userMap = makeUserMap(active, waiting);
|
||||
String target = "SIP/ghost-00000000"; // not present
|
||||
// correctness
|
||||
String r1 = interfaceExists_iterator(members, target);
|
||||
String r2 = interfaceExists_ao2find(membersMap, target);
|
||||
assert r1 != null && r1.equalsIgnoreCase(target) : "iterator must find target";
|
||||
assert r2 != null && r2.equalsIgnoreCase(target) : "ao2find must find target";
|
||||
assert interfaceExists_iterator(members, "SIP/nonexistent") == null;
|
||||
assert interfaceExists_ao2find(membersMap, "SIP/nonexistent") == null;
|
||||
|
||||
int OPS = 20_000;
|
||||
long[] sOps = {0}, fOps = {0};
|
||||
Runnable slow = () -> {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < OPS; i++)
|
||||
ops += findUserByChannel_slow(active, waiting, target);
|
||||
sOps[0] = ops;
|
||||
};
|
||||
Runnable fast = () -> {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < OPS; i++)
|
||||
ops += findUserByChannel_fast(userMap, target);
|
||||
fOps[0] = ops;
|
||||
};
|
||||
slow.run(); fast.run();
|
||||
bench("confbridge user-find P=2000 not-found (20k)", slow, fast, sOps[0], fOps[0]);
|
||||
total++;
|
||||
assert sOps[0] > fOps[0] * 1000
|
||||
: "FAIL: slow=" + sOps[0] + " fast=" + fOps[0];
|
||||
passed++;
|
||||
}
|
||||
// performance: simulate C calls per second
|
||||
int REPS = 20_000;
|
||||
long t0 = System.nanoTime();
|
||||
for (int r = 0; r < REPS; r++) interfaceExists_iterator(members, target);
|
||||
long tIter = System.nanoTime() - t0;
|
||||
|
||||
System.out.println("\n" + passed + "/" + total + " PASS");
|
||||
if (passed < total) System.exit(1);
|
||||
t0 = System.nanoTime();
|
||||
for (int r = 0; r < REPS; r++) interfaceExists_ao2find(membersMap, target);
|
||||
long tHash = System.nanoTime() - t0;
|
||||
|
||||
double ratio = (double) tIter / tHash;
|
||||
System.out.printf("asterisk-0001: iter=%.3fs ao2find=%.3fs ratio=%.1f×%n",
|
||||
tIter / 1e9, tHash / 1e9, ratio);
|
||||
assert ratio > 10 : "Expected >10× speedup, got " + ratio;
|
||||
System.out.println("PASS asterisk-0001");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
testAsterisk0001();
|
||||
System.out.println("ALL PASS");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue