You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/10/21 05:52:59 UTC

(maven) branch maven-4.0.x updated: Detect recursive configuration property references (#13643, fixes #13638) (#13654)

This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch maven-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/maven-4.0.x by this push:
     new ef9466b98a Detect recursive configuration property references (#13643, fixes #13638) (#13654)
ef9466b98a is described below

commit ef9466b98aec7b808cb73aeb50d26f3a7284eca9
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Wed Oct 21 07:52:52 2026 +0200

    Detect recursive configuration property references (#13643, fixes #13638) (#13654)
    
    Cyclic `configProps` references (e.g. `a=${a}` or mutually referencing
    `a=${b}, b=${a}`) caused a `StackOverflowError` because `resolveVariable`
    returned raw, uninterpolated values from `configProps` without passing
    them through the cycle-detection machinery.
    
    Fix: when a `configProps` lookup returns a value that itself contains
    `${...}`, pass it through `doSubstVars` with the existing `cycleMap`.
    This hooks the recursive interpolation into the same cycle-detection path
    already used for direct variable loops, turning infinite recursion into a
    clean `InterpolatorException`.
    
    New tests in `DefaultInterpolatorTest`:
    - `testSelfReferencingConfigPropertyDifferentCurrentKey` — direct
      self-reference with a different current key
    - `testSelfReferencingConfigPropertyNullCurrentKey` — direct
      self-reference when `currentKey` is null
    - `testIndirectCycleConfigProperties` — indirect two-variable cycle
      (`a→b→a`)
    - `testValidChainedReferenceConfigProperties` — valid chained reference
      (`a→b→"hello"`) still resolves correctly
    
    Fixes #13638
    
    Co-authored-by: HARSHA B <ha...@gmail.com>
---
 .../maven/impl/model/DefaultInterpolator.java      |  4 +++
 .../maven/impl/model/DefaultInterpolatorTest.java  | 32 ++++++++++++++++++++++
 2 files changed, 36 insertions(+)

diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultInterpolator.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultInterpolator.java
index f18a38a7f1..8f23f7b2c3 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultInterpolator.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultInterpolator.java
@@ -376,6 +376,10 @@ private static String resolveVariable(
         // Try configuration properties first
         if (configProps != null) {
             substValue = configProps.get(variable);
+            if (substValue != null && substValue.contains(DELIM_START)) {
+                substValue = doSubstVars(
+                        substValue, variable, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
+            }
         }
         if (substValue == null && !variable.isEmpty() && callback != null) {
             String s1 = callback.apply(variable);
diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultInterpolatorTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultInterpolatorTest.java
index 9d332e9a51..0246fd4a07 100644
--- a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultInterpolatorTest.java
+++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultInterpolatorTest.java
@@ -81,6 +81,38 @@ void testSubstLoop() {
                 "Expected substVars() to throw an InterpolatorException, but it didn't");
     }
 
+    @Test
+    void testSelfReferencingConfigPropertyDifferentCurrentKey() {
+        assertThrows(
+                InterpolatorException.class,
+                () -> substVars("${a}", "outer", Map.of("a", "${a}")),
+                "Expected substVars() to throw an InterpolatorException for self-referencing property");
+    }
+
+    @Test
+    void testSelfReferencingConfigPropertyNullCurrentKey() {
+        assertThrows(
+                InterpolatorException.class,
+                () -> substVars("${a}", null, Map.of("a", "${a}")),
+                "Expected substVars() to throw an InterpolatorException when currentKey is null");
+    }
+
+    @Test
+    void testIndirectCycleConfigProperties() {
+        assertThrows(
+                InterpolatorException.class,
+                () -> substVars("${a}", "outer", Map.of("a", "${b}", "b", "${a}")),
+                "Expected substVars() to throw an InterpolatorException for indirect 2-variable cycle");
+    }
+
+    @Test
+    void testValidChainedReferenceConfigProperties() {
+        assertEquals(
+                "hello",
+                substVars("${a}", "outer", Map.of("a", "${b}", "b", "hello")),
+                "Expected substVars() to resolve chained reference");
+    }
+
     @Test
     void testLoopEmpty() {
         assertEquals("${a}", substVars("${a}", null, null, null, false));