evolution+pidgin: 5-MOAD scan; 3 defects (evolution-0001 CWE-407, pidgin-0001 CWE-407, pidgin-0002 CWE-312)

This commit is contained in:
russell@unturf.com 2026-03-31 21:37:39 -04:00
parent a512770591
commit d9d4b30def
8 changed files with 552 additions and 0 deletions

View file

@ -0,0 +1,58 @@
# evolution-0001: EXDATE dedup O(E^2) in EDateTimeList
## Target
GNOME Evolution email/calendar client
## Severity
LOW-MEDIUM
## MOAD
0001 (CWE-407: Inefficient Algorithmic Complexity)
## Location
`src/calendar/gui/e-date-time-list.c` — `e_date_time_list_append()`
Called from `src/calendar/gui/e-comp-editor-page-recurrence.c` — `ecep_recurrence_fill_exception_widgets()`
## Description
`e_date_time_list_append()` deduplicates exception dates (EXDATE properties
on a recurring calendar event) using `g_list_find_custom()` on a GList.
When loading a recurring calendar event with E exception dates,
`ecep_recurrence_fill_exception_widgets()` calls append E times in a for loop.
Each call scans up to E-1 entries already stored: O(1) + O(2) + ... + O(E-1)
= O(E^2/2) total comparisons.
Real events can accumulate hundreds of exception dates over years (e.g., a
weekly recurring meeting with individual cancellations).
## Fix
Maintain a parallel `GHashTable` in `EDateTimeListPrivate` keyed on the
ISO date string (via `i_cal_time_as_ical_string()`). Membership test in
`e_date_time_list_append()` becomes O(1). Clear the hash table in
`e_date_time_list_clear()`.
## Speedup
| E | Ops (defective) | Ops (fixed) | Ratio |
|---|-----------------|-------------|-------|
| 50 | 1225 | 50 | 24.5x |
| 100 | 4950 | 100 | 49.5x |
| 200 | 19900 | 200 | 99.5x |
| 500 | 124750 | 500 | 249.5x |
| 1000 | 499500 | 1000 | 499.5x |
## MOAD-0002 (Intertangle)
`src/mail/mail-send-recv.c`: static globals `send_data`, `send_recv_dialog`,
`glob_ongoing_downsyncs` are shared mutable state across mail send/receive
operations and UI callbacks. No explicit mutex guards these. Architectural
MOAD-0002, no single-function patch.
## MOAD-0003
CLEAN. No ThreadLocal or GPrivate request-scoped identity leakage found.
## MOAD-0004
CLEAN. No credential values (passwords, tokens) logged verbatim. Debug
messages reference password operations by description only.
## MOAD-0005
CLEAN. Evolution uses GLib main loop (single UI thread). No concurrent
cache-get+put race conditions.

View file

@ -0,0 +1,43 @@
# UNDF:
--- a/src/calendar/gui/e-date-time-list.c
+++ b/src/calendar/gui/e-date-time-list.c
@@ -56,6 +56,7 @@ struct _EDateTimeListPrivate {
GList *list;
gboolean use_24_hour_format;
gint stamp;
+ GHashTable *date_set; /* ISO-date string keys for O(1) dedup */
};
/* EDateTimeList is a GtkTreeModel implementation that stores ICalTime
@@ -580,8 +580,21 @@ e_date_time_list_append (EDateTimeList *date_time_list,
g_return_if_fail (i_cal_time_is_valid_time ((ICalTime *) itt));
- if (g_list_find_custom (date_time_list->priv->list, itt, (GCompareFunc) compare_datetime) == NULL) {
+ if (date_time_list->priv->date_set == NULL)
+ date_time_list->priv->date_set = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ /* Build a canonical key: YYYYMMDD or YYYYMMDDTHHMMSS[Z] */
+ gchar *key = i_cal_time_as_ical_string ((ICalTime *) itt);
+ if (key == NULL)
+ return;
+
+ if (!g_hash_table_contains (date_time_list->priv->date_set, key)) {
+ g_hash_table_add (date_time_list->priv->date_set, key); /* takes ownership */
date_time_list->priv->list = g_list_append (date_time_list->priv->list, i_cal_time_clone (itt));
row_added (date_time_list, g_list_length (date_time_list->priv->list) - 1);
+ } else {
+ g_free (key);
}
if (iter) {
@@ -598,6 +611,10 @@ e_date_time_list_clear (EDateTimeList *date_time_list)
g_return_if_fail (E_IS_DATE_TIME_LIST (date_time_list));
+ if (date_time_list->priv->date_set) {
+ g_hash_table_destroy (date_time_list->priv->date_set);
+ date_time_list->priv->date_set = NULL;
+ }
+
g_list_free_full (date_time_list->priv->list, g_object_unref);
date_time_list->priv->list = NULL;
date_time_list->priv->stamp++;

View file

@ -0,0 +1,112 @@
import java.util.*;
/**
* Models evolution-0001: GNOME Evolution e-date-time-list.c
*
* e_date_time_list_append() deduplicates exception dates (EXDATE) using
* g_list_find_custom() on a GList. When loading a recurring calendar event
* with E exception dates, the fill loop calls append E times, each scanning
* up to E-1 entries already in the list: O(E^2) comparisons total.
*
* Fix: maintain a parallel GHashTable keyed on ISO date string for O(1)
* membership test. Append stays O(1) amortized; fill loop is O(E) total.
*/
public class EDateTimeListTest {
// --- Defective model: GList linear scan on each append ---
static int appendDefective(List<String> list, String date) {
for (String existing : list) { // O(N) scan
if (existing.equals(date)) return 0; // duplicate found
}
list.add(date);
return 1;
}
static long fillExceptionsDefective(List<String> dates) {
List<String> store = new ArrayList<>();
long ops = 0;
for (String d : dates) {
ops += store.size(); // scan cost
appendDefective(store, d);
}
return ops;
}
// --- Fixed model: HashSet for O(1) membership ---
static long fillExceptionsFixed(List<String> dates) {
Set<String> seen = new HashSet<>();
List<String> store = new ArrayList<>();
long ops = 0;
for (String d : dates) {
ops += 1; // O(1) hash lookup
if (seen.add(d)) store.add(d);
}
return ops;
}
static List<String> generateDates(int n) {
List<String> dates = new ArrayList<>();
for (int i = 0; i < n; i++) {
// simulate EXDATE strings like 20260101, 20260102, ...
int day = i % 28 + 1;
int month = (i / 28) % 12 + 1;
int year = 2026 + (i / 336);
dates.add(String.format("%04d%02d%02d", year, month, day));
}
return dates;
}
public static void main(String[] args) {
System.out.println("evolution-0001: e-date-time-list EXDATE dedup O(E^2) vs O(E)");
System.out.println("=============================================================");
int[] sizes = {50, 100, 200, 500, 1000};
for (int n : sizes) {
List<String> dates = generateDates(n);
long tDefStart = System.nanoTime();
long opsDefective = fillExceptionsDefective(new ArrayList<>(dates));
long tDefEnd = System.nanoTime();
long tFixStart = System.nanoTime();
long opsFixed = fillExceptionsFixed(new ArrayList<>(dates));
long tFixEnd = System.nanoTime();
long timeDefective = tDefEnd - tDefStart;
long timeFixed = tFixEnd - tFixStart;
double ratio = (double) opsDefective / opsFixed;
System.out.printf("E=%4d: defective=%6d ops (%6.3f ms) | fixed=%6d ops (%6.3f ms) | ratio=%.1fx%n",
n,
opsDefective, timeDefective / 1_000_000.0,
opsFixed, timeFixed / 1_000_000.0,
ratio);
}
// Correctness: verify both produce same unique list
List<String> testDates = new ArrayList<>();
testDates.add("20260101");
testDates.add("20260102");
testDates.add("20260101"); // duplicate
testDates.add("20260103");
List<String> defStore = new ArrayList<>();
for (String d : testDates) appendDefective(defStore, d);
Set<String> fixSeen = new HashSet<>();
List<String> fixStore = new ArrayList<>();
for (String d : testDates) { if (fixSeen.add(d)) fixStore.add(d); }
assert defStore.equals(fixStore) : "Mismatch: " + defStore + " vs " + fixStore;
System.out.println("\nCorrectness: PASS (both produce same deduplicated list)");
// Verify ratio at E=1000
List<String> bigDates = generateDates(1000);
long bigDef = fillExceptionsDefective(new ArrayList<>(bigDates));
long bigFix = fillExceptionsFixed(new ArrayList<>(bigDates));
double bigRatio = (double) bigDef / bigFix;
assert bigRatio > 200 : "Expected >200x ratio at E=1000, got " + bigRatio;
System.out.printf("Speedup at E=1000: %.0fx -- PASS%n", bigRatio);
}
}