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

commit 080022b0bd39157e6fd494c21314b2cdfe485b79
Author: James Bognar <[email protected]>
AuthorDate: Fri Jul 17 08:44:45 2026 -0400

    TODO-254: Harden HoconValue.getElements() against mutable-state exposure
    
    Follow-up to BUG-09 from the TODO-238 juneau-marshall quality-registry 
pilot.
    HoconArray.getElements() previously exposed the backing mutable element 
list;
    this hardens it to prevent external mutation of internal parser state.
    
    Co-authored-by: Cursor <[email protected]>
---
 .../juneau/marshall/hocon/HoconParserSession.java  | 14 ++++++-------
 .../juneau/marshall/hocon/HoconResolver.java       | 22 +++++++++-----------
 .../apache/juneau/marshall/hocon/HoconValue.java   | 24 ++++++++++++++++++++--
 3 files changed, 39 insertions(+), 21 deletions(-)

diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/hocon/HoconParserSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/hocon/HoconParserSession.java
index e410d50cd8..f0db6b8fd0 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/hocon/HoconParserSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/hocon/HoconParserSession.java
@@ -147,12 +147,12 @@ public class HoconParserSession extends 
ReaderParserSession implements RecordRea
                                var value = parseValueOrConcat(t);
                                var existing = root.getPath(path);
                                if (existing instanceof HoconValue.HoconArray 
arr)
-                                       arr.getElements().add(value);
+                                       arr.add(value);
                                else {
                                        var newArr = new 
HoconValue.HoconArray();
                                        if (existing != null)
-                                               
newArr.getElements().add(existing);
-                                       newArr.getElements().add(value);
+                                               newArr.add(existing);
+                                       newArr.add(value);
                                        root.setPath(path, newArr);
                                }
                        } else if (next.type() == 
HoconTokenizer.TokenType.EQUALS || next.type() == 
HoconTokenizer.TokenType.COLON) {
@@ -347,12 +347,12 @@ public class HoconParserSession extends 
ReaderParserSession implements RecordRea
                                var value = parseValueOrConcat(t);
                                var existing = obj.getPath(path);
                                if (existing instanceof HoconValue.HoconArray 
arr)
-                                       arr.getElements().add(value);
+                                       arr.add(value);
                                else {
                                        var newArr = new 
HoconValue.HoconArray();
                                        if (existing != null)
-                                               
newArr.getElements().add(existing);
-                                       newArr.getElements().add(value);
+                                               newArr.add(existing);
+                                       newArr.add(value);
                                        obj.setPath(path, newArr);
                                }
                        } else if (next.type() == 
HoconTokenizer.TokenType.EQUALS || next.type() == 
HoconTokenizer.TokenType.COLON) {
@@ -389,7 +389,7 @@ public class HoconParserSession extends ReaderParserSession 
implements RecordRea
                        // arrays appear without a separator; the result is 
already a flattened HoconArray at
                        // that point.  Here we must add the element as-is so 
nested arrays like
                        // `[[1,2,3], [4,5,6]]` (with separators) stay nested 
rather than flattening.
-                       arr.getElements().add(parseValueOrConcat(t));
+                       arr.add(parseValueOrConcat(t));
                        // parseValueOrConcat's internal concat loop calls 
peekNoSkip(), which eagerly consumes the
                        // closing-bracket char from the underlying reader and 
stashes it as peeked=RBRACKET.
                        // skipWhitespaceAndComments is a no-op while a token 
is cached (see HoconTokenizer), so it
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/hocon/HoconResolver.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/hocon/HoconResolver.java
index 3c7b26a0e6..2a210903d2 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/hocon/HoconResolver.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/hocon/HoconResolver.java
@@ -81,17 +81,13 @@ public class HoconResolver {
                        }
                        case ARRAY -> {
                                var arr = (HoconValue.HoconArray) val;
-                               var elements = arr.getElements();
-                               var toRemove = new ArrayList<Integer>();
-                               for (var i = 0; i < elements.size(); i++) {
-                                       var resolved = 
resolveValue(elements.get(i));
-                                       if (resolved == null)
-                                               toRemove.add(i);
-                                       else
-                                               elements.set(i, resolved);
+                               var resolved = new ArrayList<HoconValue>();
+                               for (var el : arr.getElements()) {
+                                       var r = resolveValue(el);
+                                       if (r != null)
+                                               resolved.add(r);
                                }
-                               for (var i = toRemove.size() - 1; i >= 0; i--)
-                                       
elements.remove(toRemove.get(i).intValue());
+                               arr.setElements(resolved);
                                yield val;
                        }
                        case SUBSTITUTION -> 
resolveSubstitution((HoconValue.HoconSubstitution) val, false);
@@ -187,8 +183,10 @@ public class HoconResolver {
                        }
                        case ARRAY -> {
                                var arr = (HoconValue.HoconArray) val;
-                               for (var i = 0; i < arr.getElements().size(); 
i++)
-                                       arr.getElements().set(i, 
resolveValueWithLookup(arr.getElements().get(i), lookup));
+                               var resolved = new ArrayList<HoconValue>();
+                               for (var el : arr.getElements())
+                                       resolved.add(resolveValueWithLookup(el, 
lookup));
+                               arr.setElements(resolved);
                                yield val;
                        }
                        case SUBSTITUTION -> 
resolveSubstitutionWithLookup((HoconValue.HoconSubstitution) val, false, 
lookup);
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/hocon/HoconValue.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/hocon/HoconValue.java
index a728cb12fd..dad911a9f1 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/hocon/HoconValue.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/hocon/HoconValue.java
@@ -162,10 +162,30 @@ public abstract sealed class HoconValue permits 
HoconValue.HoconObject, HoconVal
                /**
                 * Returns the elements.
                 *
-                * @return The elements list.
+                * @return An unmodifiable copy of the elements list.
                 */
                public List<HoconValue> getElements() {
-                       return elements;
+                       return u(copyOf(elements));
+               }
+
+               /**
+                * Adds an element to this array.
+                *
+                * @param element The element to add.
+                */
+               public void add(HoconValue element) {
+                       elements.add(element);
+               }
+
+               /**
+                * Replaces the contents of this array with the specified 
elements.
+                *
+                * @param value The new elements. Can be <jk>null</jk> to clear 
the array.
+                */
+               public void setElements(List<HoconValue> value) {
+                       elements.clear();
+                       if (value != null)
+                               elements.addAll(value);
                }
 
                /**

Reply via email to