# UNDF: UNDF-2026-000000296 From 0000002 Mon Sep 17 00:00:00 2001 Subject: [PATCH] CWE-407: spring-0003/0004 — fix O(n²) ArrayList.contains() in AbstractApplicationEventMulticaster spring-0003 (HIGH): retrieveApplicationListeners() builds allListeners as ArrayList>. Inside the O(L) listenerBeans loop it calls allListeners.contains() twice (lines 279, 285). With P programmatic listeners already in the list each lookup is O(P+i). Total: O(L × (P+L)) = O(n²). This path is hit on every event dispatch cache-miss — including startup events in large Spring Boot applications. spring-0004 (MEDIUM): DefaultListenerRetriever.getApplicationListeners() has the same pattern: ArrayList allListeners, loop over applicationListenerBeans, allListeners.contains(listener) at line 512. Fix for both: replace ArrayList with LinkedHashSet (insertion-ordered, O(1) contains/add). Remove the now-redundant contains() guards — Set.add() is idempotent. Convert to List<> only for the final sort step. CWE: CWE-407 (Inefficient Algorithmic Complexity) Severity: spring-0003 HIGH, spring-0004 MEDIUM --- .../event/AbstractApplicationEventMulticaster.java | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java index aaaaaaa..bbbbbbb 100644 --- a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java +++ b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -233,8 +234,8 @@ public abstract class AbstractApplicationEventMulticaster private Collection> retrieveApplicationListeners( ResolvableType eventType, @Nullable Class sourceType, @Nullable CachedListenerRetriever retriever) { - List> allListeners = new ArrayList<>(); + // Use LinkedHashSet: O(1) add/contains, insertion-ordered for stable dispatch order. + LinkedHashSet> allListenerSet = new LinkedHashSet<>(); Set> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null); Set filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null); @@ -249,7 +250,7 @@ public abstract class AbstractApplicationEventMulticaster if (supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { filteredListeners.add(listener); } - allListeners.add(listener); + allListenerSet.add(listener); } } @@ -267,18 +268,14 @@ public abstract class AbstractApplicationEventMulticaster ApplicationListener unwrappedListener = (ApplicationListener) AopProxyUtils.getSingletonTarget(listener); if (listener != unwrappedListener) { if (filteredListeners != null && filteredListeners.contains(unwrappedListener)) { filteredListeners.remove(unwrappedListener); filteredListeners.add(listener); } - if (allListeners.contains(unwrappedListener)) { - allListeners.remove(unwrappedListener); - allListeners.add(listener); - } + // O(1) remove+add on LinkedHashSet — replaces O(n) ArrayList scan + if (allListenerSet.remove(unwrappedListener)) { + allListenerSet.add(listener); + } } - if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) { + if (!allListenerSet.contains(listener) && supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { if (beanFactory.isSingleton(listenerBeanName)) { filteredListeners.add(listener); @@ -287,7 +284,7 @@ public abstract class AbstractApplicationEventMulticaster filteredListenerBeans.add(listenerBeanName); } } - allListeners.add(listener); + allListenerSet.add(listener); } } else { @@ -295,7 +292,7 @@ public abstract class AbstractApplicationEventMulticaster if (retriever != null) { filteredListeners.remove(listener); } - allListeners.remove(listener); + allListenerSet.remove(listener); } } } + List> allListeners = new ArrayList<>(allListenerSet); AnnotationAwareOrderComparator.sort(allListeners); if (retriever != null) { if (CollectionUtils.isEmpty(filteredListenerBeans)) { @@ -502,14 +499,13 @@ public abstract class AbstractApplicationEventMulticaster public Collection> getApplicationListeners() { - List> allListeners = new ArrayList<>( - this.applicationListeners.size() + this.applicationListenerBeans.size()); - allListeners.addAll(this.applicationListeners); + // LinkedHashSet: O(1) add() deduplicates programmatic + bean-name listeners + LinkedHashSet> allListenerSet = new LinkedHashSet<>(this.applicationListeners); if (!this.applicationListenerBeans.isEmpty()) { BeanFactory beanFactory = getBeanFactory(); for (String listenerBeanName : this.applicationListenerBeans) { try { ApplicationListener listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); - if (!allListeners.contains(listener)) { // O(n) scan — eliminated - allListeners.add(listener); - } + allListenerSet.add(listener); // O(1) — Set.add() is idempotent } catch (NoSuchBeanDefinitionException ex) { } } } + List> allListeners = new ArrayList<>(allListenerSet); AnnotationAwareOrderComparator.sort(allListeners); return allListeners; }