dagger-0001 + jackson-0001/0002: Deque.contains O(N×D) + two O(N²) type-hierarchy scans; count 656→659
This commit is contained in:
parent
7cb8abc236
commit
e610b42a21
4 changed files with 314 additions and 0 deletions
14
defects/commons-lang/patch/CLEAN.md
Normal file
14
defects/commons-lang/patch/CLEAN.md
Normal 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.**
|
||||
|
|
@ -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).
|
||||
|
|
@ -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).
|
||||
|
|
@ -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).
|
||||
Loading…
Add table
Add a link
Reference in a new issue