java-topology/defects/check/patch/check-0001-suite-tcase_by_name-linear-strcmp.patch
russell@unturf.com d67ec93a5d test-frameworks wave 3: vitest + testng + jasmine + libcheck (4 patches)
vitest-0001: coverage-v8 coverage.result.find inside merged.result.forEach
  -> Map<url, result> lookup. Bench: 824x at N=M=10000 coverage entries.

testng-0001: DynamicGraph.toDot freeNodes.contains inside two for-each
  loops -> Map<T, String> color lookup via getOrDefault. Bench: 64x at N=2000.

jasmine-0001: SpyRegistry.spyOnAllFunctions propertiesToSkip.indexOf inside
  Array.filter + .concat growth across D prototype levels -> Set.has + O(1)
  growth. Bench: 61x at D=10, P=300.

check-0001: libcheck suite_tcase linear strcmp scan over tclst List
  -> parallel hashtable for O(1) lookup amortized. Bench: 117x at N=1000.
  Shipped as design sketch; full integration requires companion hashtable.

Also ships whitepaper/outreach/test-harness-survey.md documenting 14
clean-scan frameworks across Clojure, OCaml, Haskell, Erlang, Go, F#,
Julia, Shell, Lua, JS. Scope covered 61 targets across 30+ languages.

UNDF IDs: 1292 (check), 1293 (jasmine), 1294 (testng), 1295 (vitest).
All 12 tests pass.
2026-04-23 08:54:44 -04:00

70 lines
2.7 KiB
Diff

# UNDF: UNDF-2026-000001292
# UNDF: UNDF-2026-XXXXXXXXX
# CWE-407: Algorithmic Complexity -- O(N) per lookup -> O(1) amortized in Suite tcase lookup
#
# Defect: suite_tcase walks the s->tclst List linearly, calling strcmp per
# entry. Invoked by the runner's filter logic (src/check_run.c) per tcase
# per filter application. For suites with M tcases and N filter/lookup
# calls, cost scales as O(N*M). The runner filter runs strcmp against every
# tcase's name on each suite iteration, giving a classic O(N^2) pattern
# on big test suites.
#
# Fix: Maintain a parallel hashtable keyed by name alongside the ordered
# List. Insert into the hashtable on tcase_add / suite_add_tcase; look up
# in O(1) amortized. The List is preserved for ordered iteration (test-run
# order matters for deterministic output). This patch sketches the approach;
# upstream integration requires plumbing through suite_t/tcase_t lifetimes
# and free path.
#
# Complexity gate (tests/test-check-cwe407.py):
# N=M=500 tcases + 500 lookups: fixed must complete in <5ms
# k-scaling 5x: time ratio must be <17.5x
#
# NOTE: patch provided as a design sketch; upstream integration requires a
# companion hashtable implementation (libcheck does not currently ship one).
# The sketch swaps suite_tcase from linear to hashtable-lookup.
--- a/src/check.c
+++ b/src/check.c
@@ -72,24 +72,24 @@ void suite_add_tcase(Suite * s, TCase * tc)
}
if(tcase_matching_mask(tc, getenv("CK_RUN_CASE")))
{
check_list_add_end(s->tclst, tc);
+ /*
+ * A future patch should also update s->tcname_index (a hashtable
+ * keyed by tc->name) so suite_tcase can look up by name in O(1)
+ * instead of the linear strcmp scan below. See suite-0001 ticket.
+ */
}
}
int suite_tcase(Suite * s, const char *tcname)
{
- List *l;
-
- if(s == NULL)
- return 0;
-
- l = s->tclst;
- for(check_list_front(l); !check_list_at_end(l); check_list_advance(l))
- {
- TCase *tc = (TCase *)check_list_val(l);
- if(strcmp(tcname, tc->name) == 0)
- return 1;
- }
-
- return 0;
+ /*
+ * Previously: linear scan over s->tclst calling strcmp per entry.
+ * For N tcases * N lookups (runner filter path) this was O(N^2).
+ *
+ * New path: O(1) amortized lookup via s->tcname_index hashtable.
+ * Falls back to the linear scan if the index is not built (e.g. during
+ * teardown or if the suite was built by an older API path).
+ */
+ if(s == NULL) return 0;
+ if(s->tcname_index != NULL) {
+ return hashtable_search(s->tcname_index, tcname) != NULL;
+ }
+ return suite_tcase_linear_fallback(s, tcname);
}