Add patch replacing linear TAILQ_FOREACH scan in mta_handle_envelope() with tree_get() on a per-relay task_by_msgid splay-tree index; assign UNDF-2026-000000201. Unit test confirms 100x op-count improvement at N=100 tasks/relay.
156 lines
5.6 KiB
Java
156 lines
5.6 KiB
Java
/**
|
||
* CWE-407 unit test: mattermost-0001
|
||
* notification.go Push StringArray O(N²) vs map O(N)
|
||
*
|
||
* Simulates the notificationsForCRT.Push.Contains(id) pattern from
|
||
* server/channels/app/notification.go (lines 541, 593).
|
||
*
|
||
* model.StringArray.Contains() delegates to slices.Contains() which is
|
||
* O(P) where P = len(Push). Called once per user in mentionedUsersList
|
||
* and once per user in allActivityPushUserIds, the total cost is O((N+M)×P).
|
||
*
|
||
* The fix builds a map[string]bool once (O(P)) and then all lookups are O(1),
|
||
* reducing total cost to O(N+M+P).
|
||
*/
|
||
public class MattermostTest {
|
||
|
||
/** Simulate StringArray.Contains: linear scan of a String array. */
|
||
static boolean stringArrayContains(String[] arr, String target) {
|
||
for (String s : arr) {
|
||
if (s.equals(target)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Count total element comparisons made by the defective pattern:
|
||
* for each id in userList, scan pushArray linearly.
|
||
*/
|
||
static long countListOps(String[] userList, String[] pushArray) {
|
||
long ops = 0;
|
||
for (String id : userList) {
|
||
for (String p : pushArray) {
|
||
ops++;
|
||
if (p.equals(id)) break; // early-exit on match (worst case: no match)
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
/**
|
||
* Count total operations for the fixed map pattern:
|
||
* build the map once, then each lookup is O(1).
|
||
* We count 1 op per map build entry + 1 op per lookup.
|
||
*/
|
||
static long countMapOps(String[] userList, String[] pushArray) {
|
||
// Build phase: O(P)
|
||
java.util.Map<String, Boolean> pushSet = new java.util.HashMap<>(pushArray.length * 2);
|
||
long ops = 0;
|
||
for (String id : pushArray) {
|
||
pushSet.put(id, true);
|
||
ops++; // one insert op per entry
|
||
}
|
||
// Lookup phase: O(1) per user
|
||
for (String id : userList) {
|
||
ops++; // one hash lookup
|
||
pushSet.containsKey(id);
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
static boolean testRatioExceeds10x() {
|
||
int n = 1000; // mentioned users
|
||
int p = 1000; // push CRT users (no overlap — worst case for scan)
|
||
|
||
String[] userList = new String[n];
|
||
String[] pushArray = new String[p];
|
||
|
||
for (int i = 0; i < n; i++) userList[i] = "user-mentioned-" + i;
|
||
for (int i = 0; i < p; i++) pushArray[i] = "user-crt-" + i;
|
||
|
||
long listOps = countListOps(userList, pushArray);
|
||
long mapOps = countMapOps(userList, pushArray);
|
||
double ratio = (double) listOps / Math.max(mapOps, 1);
|
||
|
||
System.out.printf(" N=%d P=%d: list_ops=%d map_ops=%d ratio=%.1fx%n",
|
||
n, p, listOps, mapOps, ratio);
|
||
|
||
if (ratio < 10.0) {
|
||
System.out.printf(" FAIL: expected ratio >= 10x, got %.1fx%n", ratio);
|
||
return false;
|
||
}
|
||
System.out.println(" PASS");
|
||
return true;
|
||
}
|
||
|
||
static boolean testListGrowsQuadratically() {
|
||
// Double N and P: op count should 4x for list, 2x for map
|
||
int n1 = 500, p1 = 500;
|
||
int n2 = 1000, p2 = 1000;
|
||
|
||
String[] u1 = new String[n1]; for (int i=0;i<n1;i++) u1[i]="u"+i;
|
||
String[] k1 = new String[p1]; for (int i=0;i<p1;i++) k1[i]="k"+i;
|
||
String[] u2 = new String[n2]; for (int i=0;i<n2;i++) u2[i]="u"+i;
|
||
String[] k2 = new String[p2]; for (int i=0;i<p2;i++) k2[i]="k"+i;
|
||
|
||
long listSmall = countListOps(u1, k1);
|
||
long listLarge = countListOps(u2, k2);
|
||
double listRatio = (double) listLarge / Math.max(listSmall, 1);
|
||
|
||
long mapSmall = countMapOps(u1, k1);
|
||
long mapLarge = countMapOps(u2, k2);
|
||
double mapRatio = (double) mapLarge / Math.max(mapSmall, 1);
|
||
|
||
System.out.printf(" list doubling-ratio=%.2fx (expect ~4x) map doubling-ratio=%.2fx (expect ~2x)%n",
|
||
listRatio, mapRatio);
|
||
|
||
if (listRatio < 3.0) {
|
||
System.out.printf(" FAIL: list should grow ~4x when inputs double, got %.2fx%n", listRatio);
|
||
return false;
|
||
}
|
||
if (mapRatio > 3.0) {
|
||
System.out.printf(" FAIL: map should grow ~2x when inputs double, got %.2fx%n", mapRatio);
|
||
return false;
|
||
}
|
||
System.out.println(" PASS");
|
||
return true;
|
||
}
|
||
|
||
static boolean testMapAlwaysFewerOps() {
|
||
int[] sizes = {100, 250, 500, 1000};
|
||
for (int n : sizes) {
|
||
String[] u = new String[n]; for (int i=0;i<n;i++) u[i]="u"+i;
|
||
String[] k = new String[n]; for (int i=0;i<n;i++) k[i]="k"+i;
|
||
long l = countListOps(u, k);
|
||
long m = countMapOps(u, k);
|
||
if (m >= l) {
|
||
System.out.printf(" FAIL: N=%d map_ops=%d >= list_ops=%d%n", n, m, l);
|
||
return false;
|
||
}
|
||
}
|
||
System.out.println(" map_ops < list_ops for N in {100, 250, 500, 1000} PASS");
|
||
return true;
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
System.out.println("mattermost-0001 CWE-407 unit test — notification.go Push StringArray vs map");
|
||
System.out.println("=".repeat(75));
|
||
|
||
boolean[] results = {
|
||
testRatioExceeds10x(),
|
||
testListGrowsQuadratically(),
|
||
testMapAlwaysFewerOps(),
|
||
};
|
||
|
||
int passed = 0;
|
||
for (boolean r : results) if (r) passed++;
|
||
|
||
System.out.println();
|
||
if (passed == results.length) {
|
||
System.out.println("ALL PASS (" + passed + "/" + results.length + ")");
|
||
} else {
|
||
System.out.println("FAILED: " + (results.length - passed) + "/" + results.length + " tests failed");
|
||
System.exit(1);
|
||
}
|
||
}
|
||
}
|