dagger-0001 + jackson-0001/0002: Deque.contains O(N×D) + two O(N²) type-hierarchy scans; count 656→659

This commit is contained in:
russell@unturf.com 2026-03-29 19:03:31 -04:00
parent 7cb8abc236
commit e610b42a21
4 changed files with 314 additions and 0 deletions

View file

@ -0,0 +1,14 @@
# CLEAN — Apache Commons Lang
Scanned 2026-03-29 for CWE-407 (algorithmic complexity).
## Findings
- `ClassUtils.getAllInterfaces()` — CLEAN: uses `LinkedHashSet` with `if (interfacesFound.add(i))` guard; O(N+E) correct
- `ClassUtils.getAllSuperclasses()` — CLEAN: simple linear superclass chain, no diamond issue
- `TypeUtils.getClosestParentType()` / mutual recursion with `isAssignable()` — CLEAN: each recursive call reduces `cls` to ONE closest parent type; linear O(depth) not O(2^D)
- `TypeUtils.determineTypeArguments()` `typeVarList.contains()` — N is bounded by generic arity (typically 1–3); negligible
- `InheritanceUtils.distance()` — CLEAN: simple linear recursion
- `AnnotationUtils` — CLEAN: no hierarchy traversal
**Result: No actionable CWE-407 defects.**

View file

@ -0,0 +1,99 @@
# UNDF: (pending)
# dagger-0001: LegacyBindingGraphFactory.resolve — cycleStack Deque.contains() O(N) cycle detection
## CWE-407 — Algorithmic Complexity: O(N) Deque membership scan in recursive dependency resolution
| Field | Value |
|--------------|-------|
| ID | dagger-0001 |
| Severity | MEDIUM |
| Ecosystem | dagger |
| Package | dagger-compiler |
| File | `dagger-compiler/main/java/dagger/internal/codegen/binding/LegacyBindingGraphFactory.java` |
| Lines | 312, 512, 723 |
| Complexity | O(N) per call; O(N×D) total over depth-D resolution chain |
| Hot path | Called during Dagger component binding resolution (annotation processing) |
## Defect
`LegacyBindingGraphFactory.Resolver.resolve(Key)` uses a `Deque<Key> cycleStack` for cycle detection.
`Deque.contains()` is O(N) — it performs a linear scan over all elements in the deque:
```java
// LegacyBindingGraphFactory.java:312
final Deque<Key> cycleStack = new ArrayDeque<>();
// LegacyBindingGraphFactory.java:720-740 (DEFECT)
void resolve(Key key) {
if (cycleStack.contains(key)) { // O(N) linear scan of Deque
return;
}
if (resolvedContributionBindings.containsKey(key)) { // O(1) — HashMap
return;
}
cycleStack.push(key);
try {
LegacyResolvedBindings bindings = lookUpBindings(key);
resolvedContributionBindings.put(key, bindings);
resolveDependencies(bindings); // recurses
} finally {
cycleStack.pop();
}
}
```
Also at line 512 in `createDelegateBinding`:
```java
if (cycleStack.contains(delegateKey)) { // O(N) linear scan of Deque
return bindingFactory.unresolvedDelegateBinding(delegateDeclaration);
}
```
For a dependency graph of N bindings with max chain depth D, `cycleStack.contains()` is called N times
at an average depth of D/2, giving O(N×D) total comparisons instead of O(N).
For a large Dagger component with 200 bindings in a chain of depth 20:
- Before: 200 × 10 = 2,000 contains-comparisons
- After: 200 × 1 = 200 contains-comparisons (O(1) HashSet lookup)
## Fix
Add a parallel `Set<Key> cycleSet` that mirrors the Deque's membership for O(1) contains:
```java
// AFTER — O(1) cycle detection
final Deque<Key> cycleStack = new ArrayDeque<>();
final Set<Key> cycleSet = new HashSet<>(); // ADD: mirrors cycleStack membership
void resolve(Key key) {
if (cycleSet.contains(key)) { // O(1) HashSet lookup
return;
}
if (resolvedContributionBindings.containsKey(key)) {
return;
}
cycleStack.push(key);
cycleSet.add(key); // ADD: keep mirror in sync
try {
LegacyResolvedBindings bindings = lookUpBindings(key);
resolvedContributionBindings.put(key, bindings);
resolveDependencies(bindings);
} finally {
cycleStack.pop();
cycleSet.remove(key); // ADD: remove on pop
}
}
```
Same fix for `createDelegateBinding` line 512: replace `cycleStack.contains(delegateKey)` with
`cycleSet.contains(delegateKey)`.
## Speedup
| Bindings (N) × Depth (D) | Before (comparisons) | After (comparisons) | Speedup |
|--------------------------|----------------------|---------------------|---------|
| 50 × 10 | 250 | 50 | 5× |
| 100 × 20 | 1,000 | 100 | 10× |
| 200 × 50 | 5,000 | 200 | 25× |
Growth before: O(N×D). Growth after: O(N).

View file

@ -0,0 +1,110 @@
# UNDF: (pending)
# jackson-0001: AnnotatedClassResolver._addSuperTypes/Interfaces — O(N²) custom _contains() list scan in type hierarchy collection
## CWE-407 — Algorithmic Complexity: O(N) custom list scan per type in recursive supertype/interface collection
| Field | Value |
|--------------|-------|
| ID | jackson-0001 |
| Severity | MEDIUM |
| Ecosystem | jackson-databind |
| Package | com.fasterxml.jackson.databind |
| File | `src/main/java/tools/jackson/databind/introspect/AnnotatedClassResolver.java` |
| Lines | 164–216 |
| Complexity | O(N) per type; O(N²) to collect N-type hierarchy |
| Hot path | Called during Jackson class binding — once per deserialized/serialized class |
## Defect
`AnnotatedClassResolver._addSuperTypes` and `_addSuperInterfaces` collect the full type hierarchy into a
`List<JavaType> result`. To avoid duplicates they call a custom `_contains(result, cls)` that performs an
O(N) linear scan using reference equality on each element's raw class:
```java
// AnnotatedClassResolver.java:164-216 (DEFECT)
private static void _addSuperTypes(JavaType type, List<JavaType> result, boolean addClassItself) {
// ...
if (addClassItself) {
if (_contains(result, cls)) { // O(N) linear scan
return;
}
result.add(type);
}
for (JavaType intCls : type.getInterfaces()) {
_addSuperInterfaces(intCls, result, true); // recurses
}
_addSuperTypes(type.getSuperClass(), result, true); // recurses
}
private static void _addSuperInterfaces(JavaType type, List<JavaType> result, boolean addClassItself) {
// ...
if (addClassItself) {
if (_contains(result, cls)) { // O(N) linear scan
return;
}
result.add(type);
// ...
}
for (JavaType intCls : type.getInterfaces()) {
_addSuperInterfaces(intCls, result, true); // recurses
}
}
private static boolean _contains(List<JavaType> found, Class<?> raw) {
for (int i = 0, end = found.size(); i < end; ++i) {
if (found.get(i).getRawClass() == raw) { // O(N) scan
return true;
}
}
return false;
}
```
For a type hierarchy with N supertypes/interfaces, `_contains` is called N times against a growing list,
giving O(N²) total scan operations. Diamond hierarchies (interface A extends B and C; both extend D)
are deduped correctly, but the dedup cost is O(N) per check.
## Fix
Add a parallel `Set<Class<?>>` for O(1) membership testing alongside the result list (which must be
kept for ordered output):
```java
// AFTER — O(N) total for N-type hierarchy
// Entry point: allocate seen set
private static List<JavaType> collectSuperTypes(JavaType type) {
List<JavaType> result = new ArrayList<>();
Set<Class<?>> seen = new HashSet<>(); // ADD: O(1) membership guard
_addSuperTypes(type, result, seen, false);
return result;
}
private static void _addSuperTypes(JavaType type, List<JavaType> result, Set<Class<?>> seen,
boolean addClassItself)
{
if (type == null) { return; }
final Class<?> cls = type.getRawClass();
if ((cls == CLS_OBJECT) || (cls == CLS_ENUM)) { return; }
if (addClassItself) {
if (!seen.add(cls)) { // O(1): add returns false if already present
return;
}
result.add(type);
}
for (JavaType intCls : type.getInterfaces()) {
_addSuperInterfaces(intCls, result, seen, true);
}
_addSuperTypes(type.getSuperClass(), result, seen, true);
}
// _contains() helper no longer needed
```
## Speedup
| Types in hierarchy (N) | Before (comparisons) | After (comparisons) | Speedup |
|------------------------|----------------------|---------------------|---------|
| 10 | 55 | 10 | 5.5× |
| 30 | 465 | 30 | 15.5× |
| 50 | 1,275 | 50 | 25.5× |
Growth before: O(N²). Growth after: O(N).

View file

@ -0,0 +1,91 @@
# UNDF: (pending)
# jackson-0002: ClassUtil._addRawSuperTypes — ArrayList.contains() O(N²) in recursive supertype collection
## CWE-407 — Algorithmic Complexity: O(N) ArrayList.contains() per type in recursive class hierarchy traversal
| Field | Value |
|--------------|-------|
| ID | jackson-0002 |
| Severity | MEDIUM |
| Ecosystem | jackson-databind |
| Package | com.fasterxml.jackson.databind |
| File | `src/main/java/tools/jackson/databind/util/ClassUtil.java` |
| Lines | 61–107 |
| Complexity | O(N) per type; O(N²) to collect N-type hierarchy |
| Hot path | Called from `findRawSuperTypes()` during mixin class resolution |
## Defect
`ClassUtil._addRawSuperTypes` recursively collects the full class/interface hierarchy. To deduplicate it
calls `result.contains(cls)` where `result` is an `ArrayList<Class<?>>` — an O(N) linear scan:
```java
// ClassUtil.java:61-107 (DEFECT)
public static List<Class<?>> findRawSuperTypes(Class<?> cls, Class<?> endBefore, boolean addClassItself) {
// ...
List<Class<?>> result = new ArrayList<Class<?>>(8); // ArrayList → contains() is O(N)
_addRawSuperTypes(cls, endBefore, result, addClassItself);
return result;
}
private static void _addRawSuperTypes(Class<?> cls, Class<?> endBefore,
Collection<Class<?>> result, boolean addClassItself) {
if (cls == endBefore || cls == null || cls == Object.class) { return; }
if (addClassItself) {
if (result.contains(cls)) { // O(N) linear scan
return;
}
result.add(cls);
}
for (Class<?> intCls : cls.getInterfaces()) {
_addRawSuperTypes(intCls, endBefore, result, true); // recurses into each interface
}
_addRawSuperTypes(cls.getSuperclass(), endBefore, result, true); // recurses up superclass chain
}
```
For a class hierarchy with N types (classes + interfaces), `result.contains()` is called N times against
a growing ArrayList, giving O(N²) total comparisons. Diamond hierarchies (A implements B and C, both
extend D) are correctly deduplicated, but the dedup cost is O(N) per call.
## Fix
Change `findRawSuperTypes` to use a `LinkedHashSet` internally for O(1) membership and preserved
insertion order, then convert to List before returning:
```java
// AFTER — O(N) total for N-type hierarchy
public static List<Class<?>> findRawSuperTypes(Class<?> cls, Class<?> endBefore, boolean addClassItself) {
if ((cls == null) || (cls == endBefore) || (cls == Object.class)) {
return Collections.emptyList();
}
LinkedHashSet<Class<?>> result = new LinkedHashSet<>(8); // O(1) contains, preserves order
_addRawSuperTypes(cls, endBefore, result, addClassItself);
return new ArrayList<>(result);
}
private static void _addRawSuperTypes(Class<?> cls, Class<?> endBefore,
Collection<Class<?>> result, boolean addClassItself) {
if (cls == endBefore || cls == null || cls == Object.class) { return; }
if (addClassItself) {
if (!result.add(cls)) { // O(1): LinkedHashSet.add() returns false if already present
return;
}
// No separate result.add(cls) needed — add() already inserted it
}
for (Class<?> intCls : cls.getInterfaces()) {
_addRawSuperTypes(intCls, endBefore, result, true);
}
_addRawSuperTypes(cls.getSuperclass(), endBefore, result, true);
}
```
## Speedup
| Types in hierarchy (N) | Before (comparisons) | After (comparisons) | Speedup |
|------------------------|----------------------|---------------------|---------|
| 10 | 55 | 10 | 5.5× |
| 30 | 465 | 30 | 15.5× |
| 50 | 1,275 | 50 | 25.5× |
Growth before: O(N²). Growth after: O(N).