From 565831bba06145ffb520939d672b1c24599aef7e Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 29 Mar 2026 19:09:54 -0400 Subject: [PATCH] =?UTF-8?q?eclipse-jdt-0001/0002:=20Scope=20typesToVisit?= =?UTF-8?q?=20O(N=C2=B2)=20+=20TypeHierarchy=20missing=20superclass=20guar?= =?UTF-8?q?d;=20count=20659=E2=86=92661?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...e-getcommonsupertype-arraylist-contains.md | 84 +++++++++++++++++++ ...pehierarchy-allsupertypes-missing-guard.md | 84 +++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 defects/eclipse-jdt/patch/eclipse-jdt-0001-scope-getcommonsupertype-arraylist-contains.md create mode 100644 defects/eclipse-jdt/patch/eclipse-jdt-0002-typehierarchy-allsupertypes-missing-guard.md diff --git a/defects/eclipse-jdt/patch/eclipse-jdt-0001-scope-getcommonsupertype-arraylist-contains.md b/defects/eclipse-jdt/patch/eclipse-jdt-0001-scope-getcommonsupertype-arraylist-contains.md new file mode 100644 index 000000000..a1a43ba49 --- /dev/null +++ b/defects/eclipse-jdt/patch/eclipse-jdt-0001-scope-getcommonsupertype-arraylist-contains.md @@ -0,0 +1,84 @@ +# UNDF: (pending) +# eclipse-jdt-0001: Scope.getCommonSuperType — ArrayList.contains() O(N²) in BFS supertypes collection + +## CWE-407 — Algorithmic Complexity: O(N) ArrayList.contains() per node in BFS type hierarchy traversal + +| Field | Value | +|--------------|-------| +| ID | eclipse-jdt-0001 | +| Severity | HIGH | +| Ecosystem | eclipse-jdt | +| Package | org.eclipse.jdt.core.compiler.batch | +| File | `org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/Scope.java` | +| Lines | 4273–4383 | +| Complexity | O(N) per node; O(N²) to collect N-node supertype set | +| Hot path | Called during type inference and common-supertype computation in the JDT compiler | + +## Defect + +`Scope.getCommonSuperType` performs a BFS over the complete type hierarchy of `firstType` to collect +all supertypes into `typesToVisit`. The collection is an `ArrayList`, and the dedup +guard uses `typesToVisit.contains()` — an O(N) linear scan — at every BFS step: + +```java +// Scope.java:4273-4383 (DEFECT) +List typesToVisit = new ArrayList<>(); // ArrayList → contains() is O(N) +// ... +typesToVisit.add(firstType); +int max = 1; +for (int i = 0; i < max; i++) { + TypeBinding typeToVisit = typesToVisit.get(i); + // ... + // Each of these contains() calls is O(i) — grows as BFS expands: + if (!typesToVisit.contains(elementType)) { // O(N) scan + typesToVisit.add(elementType); max++; + } + // ... + if (!typesToVisit.contains(superType)) { // O(N) scan (×4 in different branches) + typesToVisit.add(superType); max++; + } + // ... (multiple more contains() calls for interfaces and superclass) +} +``` + +For N types in the hierarchy, the BFS loop runs N iterations; each iteration performs one or more +`contains()` calls that scan up to i elements. Total: O(N²) comparisons. + +`ReferenceBinding` confirms identity semantics (its comment says "ALL ReferenceBindings are unique +when created so equals() is the same as =="), so a `HashSet` using the existing +`hashCode()` (name-based) provides O(1) contains with correct dedup semantics. + +## Fix + +Add a parallel `Set` for O(1) membership testing alongside the `ArrayList` (kept for +indexed iteration via `.get(i)`): + +```java +// AFTER — O(N) total for N-node BFS +List typesToVisit = new ArrayList<>(); +Set typesToVisitSet = new HashSet<>(); // ADD: O(1) membership guard + +typesToVisit.add(firstType); +typesToVisitSet.add(firstType); // ADD +int max = 1; +for (int i = 0; i < max; i++) { + TypeBinding typeToVisit = typesToVisit.get(i); + // ... + // Replace all: if (!typesToVisit.contains(x)) { typesToVisit.add(x); max++; } + // With: if (typesToVisitSet.add(x)) { typesToVisit.add(x); max++; } + if (typesToVisitSet.add(elementType)) { // O(1) HashSet.add returns false if present + typesToVisit.add(elementType); max++; + } + // ... (apply same replacement to all ~7 contains() checks) +} +``` + +## Speedup + +| Types in hierarchy (N) | Before (comparisons) | After (comparisons) | Speedup | +|------------------------|----------------------|---------------------|---------| +| 20 | ~200 | 20 | 10× | +| 50 | ~1,250 | 50 | 25× | +| 100 | ~5,000 | 100 | 50× | + +Growth before: O(N²). Growth after: O(N). diff --git a/defects/eclipse-jdt/patch/eclipse-jdt-0002-typehierarchy-allsupertypes-missing-guard.md b/defects/eclipse-jdt/patch/eclipse-jdt-0002-typehierarchy-allsupertypes-missing-guard.md new file mode 100644 index 000000000..e05b73f86 --- /dev/null +++ b/defects/eclipse-jdt/patch/eclipse-jdt-0002-typehierarchy-allsupertypes-missing-guard.md @@ -0,0 +1,84 @@ +# UNDF: (pending) +# eclipse-jdt-0002: TypeHierarchy.getAllSupertypes0 — missing Set.add() guard on superclass recursion + +## CWE-407 — Algorithmic Complexity: Unconditional recursion into already-visited superclass + +| Field | Value | +|--------------|-------| +| ID | eclipse-jdt-0002 | +| Severity | MEDIUM | +| Ecosystem | eclipse-jdt | +| Package | org.eclipse.jdt.core | +| File | `org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/TypeHierarchy.java` | +| Lines | 483–498 | +| Complexity | Unnecessary superclass subtree re-traversal when superclass already visited via interface path | +| Hot path | Called during type hierarchy computation for IDE features (Open Type Hierarchy, completion, etc.) | + +## Defect + +`TypeHierarchy.getAllSupertypes0` recurses through both superinterfaces AND the superclass chain. +For superinterfaces it correctly guards with `if (supers.add(superinterface))` — if the interface +is already in the set, recursion is skipped. But for the superclass, `supers.add(superclass)` is +called without checking the return value: + +```java +// TypeHierarchy.java:483-498 (DEFECT) +private Set getAllSupertypes0(IType type, Set supers) { + IType[] superinterfaces = this.typeToSuperInterfaces.get(type); + if (superinterfaces == null) { + return supers; + } + for (IType superinterface : superinterfaces) { + if (supers.add(superinterface)) { // CORRECT: guard on interface recursion + supers = getAllSuperInterfaces0(superinterface, supers); + } + } + IType superclass = this.classToSuperclass.get(type); + if (superclass != null) { + supers.add(superclass); // DEFECT: ignores return value + supers = getAllSupertypes0(superclass, supers); // recurses unconditionally + } + return supers; +} +``` + +When a class's superclass was already added to `supers` via an interface path (e.g., in a hierarchy +where a mixin interface records the concrete class as its supertype in the model), this method recurses +into `getAllSupertypes0(superclass, supers)` even though `superclass` is already fully traversed. +The redundant traversal re-processes superclass's interfaces and its own superclass chain before the +`supers.add()` guards short-circuit each branch. + +Contrast with the CORRECT guard pattern used for interfaces on line 489. + +## Fix + +Apply the same guard pattern to the superclass recursion: + +```java +// AFTER — O(N+E) where N=types, E=hierarchy edges +private Set getAllSupertypes0(IType type, Set supers) { + IType[] superinterfaces = this.typeToSuperInterfaces.get(type); + if (superinterfaces == null) { + return supers; + } + for (IType superinterface : superinterfaces) { + if (supers.add(superinterface)) { + supers = getAllSuperInterfaces0(superinterface, supers); + } + } + IType superclass = this.classToSuperclass.get(type); + if (superclass != null) { + if (supers.add(superclass)) { // FIX: guard matches interface pattern + supers = getAllSupertypes0(superclass, supers); + } + } + return supers; +} +``` + +## Speedup + +The guard eliminates re-traversal of already-visited superclass subtrees. In deep hierarchies where +a superclass appears via both a class path and an interface model path, the entire subtree below +the superclass is traversed redundantly without the guard. With the guard, each type is traversed +at most once: O(N+E) instead of O((N+E) × revisit_count).