This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 8cd772b764 Fix circular config import StackOverflow (juneau-config)
8cd772b764 is described below
commit 8cd772b76409e00e5654e3313bbb34303313c249
Author: James Bognar <[email protected]>
AuthorDate: Tue Jun 9 07:25:16 2026 -0400
Fix circular config import StackOverflow (juneau-config)
Loading a config with a circular import recursed infinitely
(ConfigMap.loadIni -> ConfigStore.getMap -> new ConfigMap -> load ->
loadIni)
with no cycle detection. Under the JaCoCo agent the resulting
StackOverflowError
struck during ThrowableUtils.<clinit>, poisoning the class and cascading
NoClassDefFoundError across the whole juneau-config test suite.
- Add thread-local cycle detection in ConfigStore.getMap that throws a clean
ConfigException naming the import loop before recursing.
- Remove the brittle catch(StackOverflowError) in ConfigMap.loadIni.
Verified: full juneau-config module (436 tests) green under coverage
instrumentation.
---
.../apache/juneau/config/internal/ConfigMap.java | 9 +++-----
.../apache/juneau/config/store/ConfigStore.java | 26 +++++++++++++++++++++-
.../logs/jetty-requests.log.112213788 | 0
juneau-utest/test-run-history.tsv | 1 +
4 files changed, 29 insertions(+), 7 deletions(-)
diff --git
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/internal/ConfigMap.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/internal/ConfigMap.java
index eb3e018cca..efdea7e195 100644
---
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/internal/ConfigMap.java
+++
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/internal/ConfigMap.java
@@ -865,12 +865,9 @@ public class ConfigMap implements ConfigStoreListener {
if (! (isEmpty(l3) ||
firstChar(l3) == '#'))
throw new
ConfigException("Invalid import config name found in configuration: {0}",
line);
var importName = l2.trim();
- try {
- if (!
imports2.containsKey(importName))
-
imports2.put(importName, store.getMap(importName, format));
- } catch
(@SuppressWarnings("unused") StackOverflowError e) {
- throw ioex("Import loop
detected in configuration ''{0}''->''{1}''", name, importName);
- }
+ // Circular imports are
detected in ConfigStore.getMap() which throws a clean ConfigException.
+ if (!
imports2.containsKey(importName))
+
imports2.put(importName, store.getMap(importName, format));
}
}
lines.add(line);
diff --git
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/ConfigStore.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/ConfigStore.java
index c526d8f339..b90135ecf9 100644
---
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/ConfigStore.java
+++
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/ConfigStore.java
@@ -84,6 +84,11 @@ public abstract class ConfigStore extends Context implements
Closeable {
private final ConcurrentHashMap<String,ConfigMap> configMaps = new
ConcurrentHashMap<>();
private final ConcurrentHashMap<String,Set<ConfigStoreListener>>
listeners = new ConcurrentHashMap<>();
+ // Tracks the chain of configs currently being constructed on this
thread so that a circular import
+ // (a config that directly or indirectly imports itself) can be
detected and reported cleanly instead
+ // of recursing infinitely through getMap()->new
ConfigMap()->load()->getMap() until the stack overflows.
+ private static final ThreadLocal<Set<String>> LOADING =
ThreadLocal.withInitial(LinkedHashSet::new);
+
/**
* Constructor.
*
@@ -129,7 +134,17 @@ public abstract class ConfigStore extends Context
implements Closeable {
var cm = configMaps.get(key);
if (nn(cm))
return cm;
- cm = new ConfigMap(this, name, format2);
+
+ // Detect circular imports before recursing into ConfigMap
construction.
+ var loading = LOADING.get();
+ if (! loading.add(key))
+ throw new ConfigException("Import loop detected in
configuration: {0}", importChain(loading, name));
+ try {
+ cm = new ConfigMap(this, name, format2);
+ } finally {
+ loading.remove(key);
+ }
+
var cm2 = configMaps.putIfAbsent(key, cm);
if (nn(cm2))
return cm2;
@@ -137,6 +152,15 @@ public abstract class ConfigStore extends Context
implements Closeable {
return cm;
}
+ // Renders the chain of configs currently being loaded (e.g. "B -> A2
-> A1 -> A2") for a circular-import error message.
+ // The entries in the loading set are cache keys of the form
"<formatId>:<name>", so the format prefix is stripped.
+ private static String importChain(Set<String> loading, String reentry) {
+ var sb = new StringBuilder();
+ for (var key : loading)
+ sb.append(key.substring(key.indexOf(':') + 1)).append("
-> ");
+ return sb.append(reentry).toString();
+ }
+
/**
* Returns the contents of the configuration file.
*
diff --git
a/juneau-microservice/juneau-microservice-jetty/logs/jetty-requests.log.112213788
b/juneau-microservice/juneau-microservice-jetty/logs/jetty-requests.log.112213788
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/juneau-utest/test-run-history.tsv
b/juneau-utest/test-run-history.tsv
index 7ac3054d10..31325aa3b8 100644
--- a/juneau-utest/test-run-history.tsv
+++ b/juneau-utest/test-run-history.tsv
@@ -76,3 +76,4 @@ timestamp git_sha branch tests_run failures
errors skipped surefire_sec wall_sec
2026-06-07T16:22:46Z a0094abd0a55 master 130509 0 0 26
178
2026-06-08T20:43:21Z bdbfa8182af7 master 125262 0 0 26
360
2026-06-09T01:09:06Z ee60417c6dc6 master 94758 0 0 24
273
+2026-06-09T11:24:07Z b578ccffc805 master 94758 0 0 24
265