java-topology/docs/tickets/r-source-0001-rapply-class-match-nested-linear-scan.md
russell@unturf.com 9934133dcf whitepaper: 312 sites / 151 ecosystems — wave2+3 defect tables and PDF rebuild
Add 88 new defect entries to HIGH and MEDIUM tables:
  HIGH: mysql-0001/0002, mariadb-0001, redis-0001/0002, valkey-0001/0002, openvpn-0001,
        vlc-0001, prometheus-0001, otel-collector-0001, cockroachdb-0001..0004,
        tidb-0001..0008, kubernetes-0001/0002, go-0001, kotlin-0002, scala-0001,
        allegro5-0001, sdl2-0001, grafana-0001, clickhouse-0001, duckdb-0001,
        mongodb-0001, envoy-0001, istio-0001, cilium-0001, linkerd2-0001,
        linux-0001/0002/0003, tor-0002/0003, curl-0001, julia-0001, lua-0001,
        perl5-0001, nats-0001, spring-0003/0004, tomcat-0001, onos-0002, odl-0002

  MEDIUM: helm-0001, mariadb-0002, openssl-0001/0002, memcached-0001,
          cassandra-0001..0004, flink-0001, storm-0001/0002, zookeeper-0001..0003,
          pip-0001, gradle-0001, nginx-0001, haproxy-0001, caddy-0001, varnish-0001,
          ffmpeg-0001, gstreamer-0001, raylib-0001, love2d-0001, php-0001/0002,
          r-source-0001, cpython-0002, ruby-0001, rabbitmq-0003/0004, activemq-0001,
          ovs-0001, onos-0003, odl-0002, jetty-0001

PDF: 976K
2026-03-27 15:23:43 -04:00

64 lines
2.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# r-source-0001: rapply() do_one() nested loop class matching → O(k²)
**Target:** wch/r-source
**Severity:** MEDIUM
**CWE:** CWE-407 (Inefficient Algorithmic Complexity)
**File:** `src/main/apply.c``do_one()` (~line 312)
**Status:** PATCHED
## Description
The `rapply()` built-in applies a function recursively to elements matching a
set of classes. For each non-list element, `do_one()` checks whether the
element's class vector intersects with the user-supplied `classes` vector using
two nested linear loops: `for i over klass` × `for j over classes`. Both
vectors can be of length k (e.g. 1020 for complex S4 hierarchies). The cost
per element is O(k²).
For a deeply nested list with N elements each having k classes checked against
k target classes, total cost is O(N × k²). In practice `rapply()` is called on
data frames and nested lists with dozens of columns, each having S3/S4 class
vectors. The nested scan fires once per element per recursive call.
## Root cause
```c
/* src/main/apply.c ~312 */
PROTECT(klass = R_data_class(X, false));
for(int i = 0; i < LENGTH(klass); i++) /* O(k) over element's classes */
for(int j = 0; j < length(classes); j++) /* O(k) over target classes */
if(Seql(STRING_ELT(klass, i), STRING_ELT(classes, j)))
matched = true;
UNPROTECT(1);
```
`Seql` compares two `CHARSXP` pointers; when the strings are interned it is
O(1), but the outer double loop is still O(k²) iterations regardless.
## Fix
Build a hash set from `classes` before recursing into the list, then check
membership with a single O(k) loop over `klass`. In C this can be done with a
small `STRSXP`-keyed hash via `Rf_installChar` / pointer comparison after
interning, or by using `R_StringHash`:
```c
/* Build a set of class name pointers (interned, so pointer-comparable) */
for(int j = 0; j < length(classes); j++)
interned[j] = Rf_installChar(STRING_ELT(classes, j));
for(int i = 0; i < LENGTH(klass); i++) {
SEXP ci = Rf_installChar(STRING_ELT(klass, i));
for(int j = 0; j < length(classes); j++)
if(ci == interned[j]) { matched = true; break; }
if(matched) break;
}
```
For k ≤ ~16 this is already a significant win (early-exit on first match +
pointer comparison). For larger k, a `HTAB`/`StringSet` gives O(k) total.
## Ops numbers (Java benchmark)
See `defects/r-source/unit/RSourceTest.java` (bench label "rapply-class-match").
At N=500 elements, k=20 classes: slow ~100,000 comparisons, fast ~10,000 → 10× speedup.