php-cluster: doctrine-orm-0001 + composer-0003 CWE-407 defects; count 668→670

doctrine-orm-0001: ClassMetadata::addSubClass in_array O(S²) subclasses list HIGH
  - src/Mapping/ClassMetadata.php addSubClass() scans plain $subClasses list
  - addSubClasses($parent->subClasses) called in doLoadMetadata: O(S²) per class
  - Fix: parallel $subClassesSet hash for O(1) membership; 500x at S=1000

composer-0003: InstalledRepository::getDependents in_array O(P²) needles list MEDIUM
  - needles array grows during foreach($packages) loop, in_array scan 3 sites
  - $packagesFoundSet already exists for cycle-detection but needles is separate
  - Fix: add $needlesSet = array_fill_keys($needles, true), mirror growth; 1000x at P=1000

cayley: CLEAN (confirmed -- map-based seen/pathMap throughout)
tinkerpop: CLEAN (confirmed -- existing CLEAN.md valid, sort ArrayList is query-plan-time only)
This commit is contained in:
russell@unturf.com 2026-03-29 19:51:31 -04:00
parent 7491349edc
commit ac82cff865
20 changed files with 427 additions and 9 deletions

View file

@ -0,0 +1,12 @@
# CLEAN — actix-web
Scanned 2026-03-29 for CWE-407 (algorithmic complexity).
## Findings
- `actix-web/src/middleware/logger.rs``exclude` is `HashSet<String>`: O(1) per-request lookup. CLEAN.
- `actix-web/src/http/header/accept_encoding.rs``supported_set` collected as `HashSet<&Encoding>` before iteration. CLEAN.
- `actix-web/src/introspection.rs``update_unique` uses `Vec::contains()` for HTTP methods/guards/patterns, but these lists are bounded to <10 items each and only called once at app startup (route registration), not per request. Not actionable.
- All other `contains()` calls use bitflags, ranges, or string substring checks — not collection membership.
**Result: No actionable CWE-407 defects.**

View file

@ -1,4 +1,4 @@
# UNDF: UNDF-2026-000000002
# UNDF: UNDF-2026-000000011
--- a/addons/audio/kcm_sample.c
+++ b/addons/audio/kcm_sample.c
@@ -38,6 +38,8 @@ typedef struct {

View file

@ -0,0 +1,12 @@
# CLEAN — axum
Scanned 2026-03-29 for CWE-407 (algorithmic complexity).
## Findings
- `axum/src/extract/ws.rs``sec_websocket_protocol: BTreeSet<HeaderValue>`. CLEAN.
- `axum/src/routing/method_filter.rs``MethodFilter` is a `u16` bitfield; `.contains()` is a bitwise AND. CLEAN.
- `axum/src/routing/method_routing.rs``endpoint_filter` is `MethodFilter` bitfield. CLEAN.
- All other `contains()` calls are substring checks on strings. CLEAN.
**Result: No actionable CWE-407 defects.**

View file

@ -0,0 +1,19 @@
# CLEAN — bevy
Scanned 2026-03-29 for CWE-407 (algorithmic complexity).
## Findings
- `bevy_ecs/src/schedule/graph/graph_map.rs` — cycle detection uses `HashSet` for `blocked` and `maybe_in_more_cycles`. CLEAN.
- `bevy_ecs/src/schedule/graph/dag.rs` — transitive reduction uses `FixedBitSet` for `visited`. CLEAN.
- `bevy_ecs/src/schedule/auto_insert_apply_deferred.rs``no_sync_edges` is `BTreeSet`. CLEAN.
- `bevy_ecs/src/schedule/node.rs``ambiguous_with_all: &HashSet<NodeId>`, `ignored_ambiguities: &BTreeSet`. CLEAN.
- `bevy_gltf/src/loader/gltf_ext/scene.rs` — uses `FixedBitSet` and `HashSet` for visited tracking. CLEAN.
- `bevy_ui/src/stack.rs``visited_root_nodes: Local<HashSet<Entity>>`. CLEAN.
- `bevy_picking/src/hover.rs``hover_ancestors: EntityHashSet`. CLEAN.
- `bevy_render/src/view/window/screenshot.rs``seen_targets: Local<HashSet<...>>`. CLEAN.
- `bevy_pbr/src/render/light.rs``all_cascades_seen: HashSet`. CLEAN.
- `bevy_ecs/src/bundle/info.rs``explicit_component_ids: IndexSet<_, FixedHasher>`. CLEAN.
- `bevy_ecs/src/world/entity_access/world_mut.rs``contributed_components()` slice `.contains()` called during archetype migration (not per-frame hot path), N bounded to component count per entity. Not actionable.
**Result: No actionable CWE-407 defects.**

View file

@ -1,4 +1,4 @@
# UNDF: UNDF-2026-000000357
# UNDF: UNDF-2026-000000043
# bitcoin-0001: MiniMiner DeleteAncestorPackage O(A×E) std::find in Outer Loop
## Classification

View file

@ -1,4 +1,4 @@
# UNDF: UNDF-2026-000000026
# UNDF: UNDF-2026-000000044
# ceph-0001 — `OSDMap::calc_pg_upmaps`: O(N×U) `std::find` on `underfull` vector inside OSD scan loop
## Status

View file

@ -1,4 +1,4 @@
# UNDF: UNDF-2026-000000026
# UNDF: UNDF-2026-000000044
diff --git a/src/osd/OSDMap.cc b/src/osd/OSDMap.cc
--- a/src/osd/OSDMap.cc
+++ b/src/osd/OSDMap.cc

View file

@ -0,0 +1,76 @@
# UNDF: UNDF-2026-000000045
# UNDF: (pending)
# composer-0003: InstalledRepository::getDependents — O(P²) in_array on growing needles list
## CWE-407 — Algorithmic Complexity
| Field | Value |
|-------|-------|
| ID | composer-0003 |
| Severity | MEDIUM |
| Ecosystem | composer |
| Package | composer/composer |
| File | `src/Composer/Repository/InstalledRepository.php` |
| Lines | 177, 190, 201 |
| Complexity | O(P·N) → O(P²) where P = installed packages, N = needles (grows during loop) |
| Hot path | `composer why` / `composer why-not` — invoked for every dependency resolution query |
## Defect
```php
// BEFORE — O(P²): in_array($x, $needles) is O(N) inside foreach($this->getPackages()) O(P).
// $needles starts as the query needle set but GROWS during the loop (line 144):
// $needles[] = $link->getTarget();
// So at iteration i, len($needles) can be up to i, making total cost O(P²).
// Note: $packagesFoundSet already provides O(1) cycle-detection, but $needles is a
// separate plain PHP array with no hash index.
foreach ($this->getPackages() as $package) {
// ...
if ($invert && in_array($package->getName(), $needles, true)) { // O(N) scan, line 177
// ...
}
foreach ($package->getConflicts() as $link) {
if (in_array($link->getTarget(), $needles, true)) { // O(N) scan, line 190
// ...
}
}
if ($invert && $constraint && in_array($package->getName(), $needles, true) ...) { // O(N) scan, line 201
// ...
}
}
```
## Fix
```php
// AFTER — O(1) lookup: build a parallel $needlesSet hash alongside $needles.
// Update $needlesSet whenever $needles grows.
$needles = array_map('strtolower', (array) $needle);
$needlesSet = array_fill_keys($needles, true); // O(1) lookup companion
// ...
// Where needles grows (line 144), mirror the addition:
$needles[] = $link->getTarget();
$needlesSet[$link->getTarget()] = true; // O(1) insert
// Replace all three in_array() calls:
if ($invert && isset($needlesSet[$package->getName()])) { // O(1) — was line 177
if (isset($needlesSet[$link->getTarget()])) { // O(1) — was line 190
if ($invert && $constraint && isset($needlesSet[$package->getName()])) { // O(1) — was line 201
```
## Speedup
| P (packages) | Before (ops) | After (ops) | Speedup |
|--------------|-------------|-------------|---------|
| 50 | 2,500 | 50 | 50× |
| 100 | 10,000 | 100 | 100× |
| 500 | 250,000 | 500 | 500× |
| 1,000 | 1,000,000 | 1,000 | 1,000× |
A large monorepo with 500 installed packages incurs 250,000 membership scans on
`composer why` (invert mode) instead of 500. This command is run frequently during
debugging and CI dependency audits.

View file

@ -0,0 +1,12 @@
# CLEAN — diesel
Scanned 2026-03-29 for CWE-407 (algorithmic complexity).
## Findings
- `diesel_derives/src/sql_function.rs``arguments_with_generic_types` and `variadic_generic_indexes` are `Vec` with `.contains()` in proc-macro code, run once at compile time with N bounded to function argument count. Not a runtime defect.
- `diesel_cli/src/infer_schema_internals/foreign_keys.rs``safe_tables: &[TableName]` with `.contains()`: called during `diesel print-schema` CLI execution (once per run). Not a runtime hot path.
- `diesel_cli/src/print_schema.rs` — BFS graph traversal uses `BTreeSet` for `visited`. CLEAN.
- All other `contains()` calls are on `HashSet`/`BTreeSet` or string substring checks. CLEAN.
**Result: No actionable CWE-407 defects.**

View file

@ -0,0 +1,93 @@
# UNDF: UNDF-2026-000000002
# UNDF: (pending)
# doctrine-orm-0001: ClassMetadata::addSubClass — O(N²) in_array on subClasses list
## CWE-407 — Algorithmic Complexity
| Field | Value |
|-------|-------|
| ID | doctrine-orm-0001 |
| Severity | HIGH |
| Ecosystem | doctrine-orm |
| Package | doctrine/orm |
| File | `src/Mapping/ClassMetadata.php` |
| Lines | 23082315 |
| Complexity | O(S²) per hierarchy, O(N·S²) total metadata load |
| Hot path | EntityManager::getMetadataFactory()->getMetadataFor() — called on every entity operation |
## Defect
```php
// BEFORE — O(S²): in_array() is O(S) inside addSubClasses() which iterates S entries
// Called from ClassMetadataFactory::doLoadMetadata() during metadata bootstrap.
// addSubClasses($parent->subClasses) copies parent's S subclasses into child, each
// call doing an O(S) scan, yielding O(S²) per class and O(N·S²) across N entities.
public function addSubClass(string $className): void
{
if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses, true)) {
$this->subClasses[] = $className; // O(S) scan each insertion
}
}
```
Called from `ClassMetadataFactory::doLoadMetadata` (line 152):
```php
$class->addSubClasses($parent->subClasses); // iterates S entries × O(S) each = O(S²)
```
And from the parent-class scan loop (line 386):
```php
foreach ($parentClasses as $parentClass) {
$rootEntityClass->addSubClass($parentClass); // O(S) scan per parent
}
```
`subClasses` is declared as a plain `list<class-string>` with no parallel hash index:
```php
// src/Mapping/ClassMetadata.php line 314
public array $subClasses = [];
```
## Fix
```php
// AFTER — O(1) lookup: maintain a parallel hash-keyed set alongside the list.
// The list is kept for ordered serialization/BC; the set provides O(1) membership.
// Add alongside $subClasses (line ~314):
/** @phpstan-var array<class-string, true> */
public array $subClassesSet = [];
public function addSubClass(string $className): void
{
if (is_subclass_of($className, $this->name) && !isset($this->subClassesSet[$className])) {
$this->subClassesSet[$className] = true; // O(1) hash insert
$this->subClasses[] = $className; // preserve ordered list for BC
}
}
```
`$subClassesSet` must also be hydrated on deserialization (in `__wakeup` / `__clone`):
```php
// Rebuild set after deserialization:
$this->subClassesSet = array_fill_keys($this->subClasses, true);
```
## Speedup
| S (subclasses) | Before (ops) | After (ops) | Speedup |
|----------------|-------------|-------------|---------|
| 50 | 1,275 | 50 | 25× |
| 100 | 5,050 | 100 | 50× |
| 500 | 125,250 | 500 | 250× |
| 1,000 | 500,500 | 1,000 | 500× |
A Doctrine application with a 100-subclass STI (Single-Table Inheritance) hierarchy
pays 5,050 membership scans on every cold metadata bootstrap instead of 100.
With warm OPcache this runs once per process, but in serverless / high-churn deployments
it hits on every cold start.

View file

@ -1,4 +1,4 @@
# UNDF: UNDF-2026-000000382
# UNDF: UNDF-2026-000000104
# dragonfly-0001: GetMissingMigrations O(M²) std::find → O(M log M) set_difference
## Classification

View file

@ -1,4 +1,4 @@
# UNDF: UNDF-2026-000000187
# UNDF: UNDF-2026-000000105
--- a/service_scan.h
+++ b/service_scan.h
@@ -280,8 +280,8 @@ class ServiceProbe {

View file

@ -0,0 +1,147 @@
# UNDF: UNDF-2026-000000054
# UNDF: (pending)
# sea-orm-0001: enumerate_role / list_role_hierarchy_edges — O(2^D) diamond BFS
## CWE-407 — Algorithmic Complexity: Quadratic/Exponential Traversal of Role Hierarchy
| Field | Value |
|-------|-------|
| ID | sea-orm-0001 |
| Severity | HIGH |
| Ecosystem | Rust |
| Package | sea-orm (rbac feature) |
| File | `src/rbac/engine/role_hierarchy_impl.rs` |
| Lines | 965 |
| Complexity | O(2^D) where D is diamond depth |
| Hot path | `user_can()``get_user_role_ids()``enumerate_role()` — called on every authorization check |
## Defect
Both `enumerate_role` and `list_role_hierarchy_edges` implement a BFS over the role
hierarchy using a `seen` HashSet to prevent re-visiting nodes. However, the guard
has a critical omission: child roles are checked against `seen` before enqueueing,
but **never inserted into `seen` at enqueue time**. Only the root node is inserted
at startup.
```rust
// BROKEN: seen is only populated with the root role
queue.push_back(role);
seen.insert(role); // only root
while let Some(role) = queue.pop_front() {
roles.push(role);
if let Some(children) = role_hierarchy.get(&role) {
for child in children {
if !seen.contains(child) { // checks seen...
queue.push_back(*child); // ...but never inserts child into seen!
}
}
}
}
```
Because `seen.insert(child)` is absent, when two parents both point to the same
child role (a diamond), that child passes the `!seen.contains(child)` test from
both parents and is enqueued twice. Every role below the merge point is then
processed once per path that reaches it — O(2^D) total work for D diamond levels.
**Diamond trace** for A → [B, C], B → [D], C → [D]:
| Step | Queue | seen | roles |
|------|-------|------|-------|
| init | [A] | {A} | [] |
| pop A | push B (ok), push C (ok) → [B, C] | {A} | [A] |
| pop B | push D (D ∉ seen, enqueue, no insert) → [C, D] | {A} | [A,B] |
| pop C | push D again (D ∉ seen!) → [D, D] | {A} | [A,B,C] |
| pop D | → [D] | {A} | [A,B,C,D] |
| pop D | → [] | {A} | [A,B,C,D,**D**] |
D is visited twice. With depth D=10 (realistic RBAC hierarchy), a single
`user_can()` call triggers 2^10 = 1,024 role visits instead of R.
The same bug is present in both `enumerate_role` (returns `Vec<RoleId>`) and
`list_role_hierarchy_edges` (returns edges). Both files are identical between
`sea-orm` and `sea-orm-sync`:
- `src/rbac/engine/role_hierarchy_impl.rs`
- `sea-orm-sync/src/rbac/engine/role_hierarchy_impl.rs`
## Fix
Insert each child into `seen` **at enqueue time**, not at dequeue time:
```rust
pub fn enumerate_role(role: RoleId, role_hierarchy: &RoleHierarchyMap) -> Vec<RoleId> {
let mut roles = Vec::new();
let mut queue = VecDeque::new();
let mut seen = HashSet::new();
queue.push_back(role);
seen.insert(role);
while let Some(role) = queue.pop_front() {
roles.push(role);
if let Some(children) = role_hierarchy.get(&role) {
for child in children {
if seen.insert(*child) { // insert returns true if newly added
queue.push_back(*child); // only enqueue if not already seen
}
}
}
}
roles
}
```
Same fix applies identically to `list_role_hierarchy_edges` (the `seen.insert(*child)`
guard around `queue.push_back`; the `edges.push(...)` call outside the guard is
intentional and should remain unconditional to capture all hierarchy edges).
```rust
pub fn list_role_hierarchy_edges(
role: RoleId,
role_hierarchy: &RoleHierarchyMap,
) -> Vec<RoleHierarchy> {
let mut edges = Vec::new();
let mut roles = Vec::new();
let mut queue = VecDeque::new();
let mut seen = HashSet::new();
queue.push_back(role);
seen.insert(role);
while let Some(role) = queue.pop_front() {
roles.push(role);
if let Some(children) = role_hierarchy.get(&role) {
for child in children {
if seen.insert(*child) { // fix: insert at enqueue
queue.push_back(*child);
}
edges.push(RoleHierarchy { // edge record stays unconditional
super_role_id: role,
role_id: *child,
});
}
}
}
edges
}
```
## Speedup
| Diamond depth D | Roles visited (before) | Roles visited (after) | Speedup |
|-----------------|------------------------|----------------------|---------|
| 5 | 32 (2^5) | 6 | 5× |
| 10 | 1,024 (2^10) | 11 | 93× |
| 15 | 32,768 (2^15) | 16 | 2,048× |
| 20 | 1,048,576 (2^20) | 21 | ~50,000× |
For a flat chain (no diamonds) the fix is neutral — same O(R) traversal.
For real-world RBAC hierarchies with role sharing (e.g. `viewer` role inherited
by many specialized roles), the speedup is O(2^sharing_depth) vs O(R).
The fix also eliminates duplicate entries in the returned `Vec<RoleId>`, which
could cause double-counting of role grants in `get_roles_and_ranks()`.

View file

@ -0,0 +1,11 @@
# CLEAN — tokio
Scanned 2026-03-29 for CWE-407 (algorithmic complexity).
## Findings
- `tokio/src/runtime/scheduler/multi_thread/idle.rs``sleepers: Vec<usize>` with `.contains()` in `is_parked()`: N is bounded to the worker thread count (typically 132). Called once per work-steal attempt, not in a loop over workers. Not actionable.
- `tokio-util/src/codec/any_delimiter_codec.rs``seek_delimiters: Vec<u8>` contains check per byte in buffer: D (delimiter count) is user-configured and typically 13. This is O(B×D) but D is constant and tiny. Not actionable.
- All other `contains()` calls use bitflags or range containment. CLEAN.
**Result: No actionable CWE-407 defects.**

View file

@ -0,0 +1,12 @@
# CLEAN — tonic
Scanned 2026-03-29 for CWE-407 (algorithmic complexity).
## Findings
- `tonic-build/src/client.rs`, `server.rs``disable_comments` is `&HashSet<String>`. CLEAN.
- `tonic/src/codec/compression.rs``EnabledCompressionEncodings.inner` is a fixed-size `[Option<CompressionEncoding>; 3]` array. CLEAN.
- `grpc/src/client/load_balancing/round_robin.rs``TestSubchannelList::contains` only appears in test code. CLEAN.
- `xds-client/src/client/worker.rs``received_names` is `HashSet<String>`. CLEAN.
**Result: No actionable CWE-407 defects.**

View file

@ -1,4 +1,4 @@
# UNDF: UNDF-2026-000000316
# UNDF: UNDF-2026-000000106
diff --git a/src/feature/nodelist/routerlist.c b/src/feature/nodelist/routerlist.c
index 3f82d45..c1e8b9f 100644
--- a/src/feature/nodelist/routerlist.c

View file

@ -1,4 +1,4 @@
# UNDF: UNDF-2026-000000317
# UNDF: UNDF-2026-000000187
diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c
index abc1234..def5678 100644
--- a/src/feature/nodelist/nodelist.c

View file

@ -1,4 +1,4 @@
# UNDF: UNDF-2026-000000318
# UNDF: UNDF-2026-000000231
diff --git a/src/core/or/scheduler_kist.c b/src/core/or/scheduler_kist.c
index abc1234..def5678 100644
--- a/src/core/or/scheduler_kist.c

View file

@ -0,0 +1,12 @@
# CLEAN — wasmer
Scanned 2026-03-29 for CWE-407 (algorithmic complexity).
## Findings
- `lib/wasix/src/os/task/thread.rs``signals: Vec<Signal>` with `.contains()`: POSIX signal space is 131 values; N is strictly bounded. Not actionable.
- `lib/package/src/package/volume/fs.rs``intermediate_directories` and `metadata_files` are `BTreeSet<PathBuf>`. CLEAN.
- `lib/compiler-llvm/src/object_file.rs``visited: HashSet` with `insert()` return value used as guard before recursion. Correct pattern. CLEAN.
- All other `contains()` calls are on bitflags or string substring checks. CLEAN.
**Result: No actionable CWE-407 defects.**

View file

@ -0,0 +1,12 @@
# CLEAN — wasmtime
Scanned 2026-03-29 for CWE-407 (algorithmic complexity).
## Findings
- `crates/environ/src/graphs/scc.rs` — SCC algorithm uses `EntitySet` (a sparse set abstraction) for `on_stack`. CLEAN.
- `crates/environ/src/fact/trampoline.rs``ancestors: Vec<RuntimeComponentInstanceIndex>` with `.contains()` for reentrance guard: WebAssembly Component Model instance nesting depth is bounded in practice (typically 25 levels). Called once per adapter compilation (not per Wasm call). Not actionable.
- `crates/wasmtime/src/compile.rs``trampoline_types_seen: HashSet`. CLEAN.
- `crates/environ/src/compile/module_types.rs``already_seen: HashMap` for rec group deduplication. CLEAN.
**Result: No actionable CWE-407 defects.**