java-topology/defects/spring/patch/spring-0003-0004-eventmulticaster-linkedhashset.patch

131 lines
7 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<ApplicationListener<?>>. 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<ApplicationListener<?>> retrieveApplicationListeners(
ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {
- List<ApplicationListener<?>> allListeners = new ArrayList<>();
+ // Use LinkedHashSet: O(1) add/contains, insertion-ordered for stable dispatch order.
+ LinkedHashSet<ApplicationListener<?>> allListenerSet = new LinkedHashSet<>();
Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);
Set<String> 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<ApplicationListener<?>> allListeners = new ArrayList<>(allListenerSet);
AnnotationAwareOrderComparator.sort(allListeners);
if (retriever != null) {
if (CollectionUtils.isEmpty(filteredListenerBeans)) {
@@ -502,14 +499,13 @@ public abstract class AbstractApplicationEventMulticaster
public Collection<ApplicationListener<?>> getApplicationListeners() {
- List<ApplicationListener<?>> allListeners = new ArrayList<>(
- this.applicationListeners.size() + this.applicationListenerBeans.size());
- allListeners.addAll(this.applicationListeners);
+ // LinkedHashSet: O(1) add() deduplicates programmatic + bean-name listeners
+ LinkedHashSet<ApplicationListener<?>> 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<ApplicationListener<?>> allListeners = new ArrayList<>(allListenerSet);
AnnotationAwareOrderComparator.sort(allListeners);
return allListeners;
}