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,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 | 164216 |
| 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 | 61107 |
| 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).