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); } /**
