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 7cefcffaac TODO-238 — BUG × juneau-rest-server fixes: BUG-09 
request-derived leaks (CachingHttpServletRequest/UrlPath) + mutable-state 
exposure (RestMatcherList, Mixin, RestContext), BUG-15 auth null-guards; 
RestOpContext matcher immutable views + compareTo declaring-class tie-breaker. 
Public matcher getters X[]→List (10.0.0).
7cefcffaac is described below

commit 7cefcffaac14bd32a4374d32a94ecf7ebd464dac
Author: James Bognar <[email protected]>
AuthorDate: Fri Jul 17 15:57:45 2026 -0400

    TODO-238 — BUG × juneau-rest-server fixes: BUG-09 request-derived leaks 
(CachingHttpServletRequest/UrlPath) + mutable-state exposure (RestMatcherList, 
Mixin, RestContext), BUG-15 auth null-guards; RestOpContext matcher immutable 
views + compareTo declaring-class tie-breaker. Public matcher getters X[]→List 
(10.0.0).
    
    Co-authored-by: Cursor <[email protected]>
---
 .../apache/juneau/rest/server/MixinAnnotation.java | 28 +++++++--------
 .../org/apache/juneau/rest/server/RestContext.java |  4 +--
 .../apache/juneau/rest/server/RestOpContext.java   | 42 +++++++++++++++-------
 .../juneau/rest/server/auth/AuthFilterChain.java   | 11 ++++++
 .../server/auth/AuthenticatedRequestWrapper.java   | 12 ++++++-
 .../rest/server/matcher/RestMatcherList.java       |  4 +--
 .../server/util/CachingHttpServletRequest.java     |  4 +--
 .../apache/juneau/rest/server/util/UrlPath.java    |  4 +--
 8 files changed, 74 insertions(+), 35 deletions(-)

diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/MixinAnnotation.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/MixinAnnotation.java
index 60c559adaf..80257a565d 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/MixinAnnotation.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/MixinAnnotation.java
@@ -379,32 +379,32 @@ public class MixinAnnotation {
                }
 
                @Override /* Overridden from Mixin */ public Class<?> type() { 
return type; }
-               @Override /* Overridden from Mixin */ public Class<? extends 
RestGuard>[] guards() { return guards; }
+               @Override /* Overridden from Mixin */ public Class<? extends 
RestGuard>[] guards() { return copyOf(guards); }
                @Override /* Overridden from Mixin */ public String roleGuard() 
{ return roleGuard; }
                @Override /* Overridden from Mixin */ public String 
rolesDeclared() { return rolesDeclared; }
-               @Override /* Overridden from Mixin */ public Class<? extends 
RestConverter>[] converters() { return converters; }
-               @Override /* Overridden from Mixin */ public Class<? extends 
Encoder>[] encoders() { return encoders; }
-               @Override /* Overridden from Mixin */ public Class<? extends 
Serializer>[] serializers() { return serializers; }
-               @Override /* Overridden from Mixin */ public Class<?>[] 
parsers() { return parsers; }
-               @Override /* Overridden from Mixin */ public Class<? extends 
ResponseProcessor>[] responseProcessors() { return responseProcessors; }
-               @Override /* Overridden from Mixin */ public Class<? extends 
RestOpArg>[] restOpArgs() { return restOpArgs; }
+               @Override /* Overridden from Mixin */ public Class<? extends 
RestConverter>[] converters() { return copyOf(converters); }
+               @Override /* Overridden from Mixin */ public Class<? extends 
Encoder>[] encoders() { return copyOf(encoders); }
+               @Override /* Overridden from Mixin */ public Class<? extends 
Serializer>[] serializers() { return copyOf(serializers); }
+               @Override /* Overridden from Mixin */ public Class<?>[] 
parsers() { return copyOf(parsers); }
+               @Override /* Overridden from Mixin */ public Class<? extends 
ResponseProcessor>[] responseProcessors() { return copyOf(responseProcessors); }
+               @Override /* Overridden from Mixin */ public Class<? extends 
RestOpArg>[] restOpArgs() { return copyOf(restOpArgs); }
                @Override /* Overridden from Mixin */ public Class<? extends 
CallLogger> callLogger() { return callLogger; }
                @Override /* Overridden from Mixin */ public Class<? extends 
HttpPartSerializer> partSerializer() { return partSerializer; }
                @Override /* Overridden from Mixin */ public Class<? extends 
HttpPartParser> partParser() { return partParser; }
                @Override /* Overridden from Mixin */ public Debug debug() { 
return debug; }
                @Override /* Overridden from Mixin */ public String messages() 
{ return messages; }
-               @Override /* Overridden from Mixin */ public String[] 
defaultRequestHeaders() { return defaultRequestHeaders; }
-               @Override /* Overridden from Mixin */ public String[] 
defaultResponseHeaders() { return defaultResponseHeaders; }
-               @Override /* Overridden from Mixin */ public String[] 
defaultRequestAttributes() { return defaultRequestAttributes; }
-               @Override /* Overridden from Mixin */ public String[] 
produces() { return produces; }
-               @Override /* Overridden from Mixin */ public String[] 
consumes() { return consumes; }
+               @Override /* Overridden from Mixin */ public String[] 
defaultRequestHeaders() { return copyOf(defaultRequestHeaders); }
+               @Override /* Overridden from Mixin */ public String[] 
defaultResponseHeaders() { return copyOf(defaultResponseHeaders); }
+               @Override /* Overridden from Mixin */ public String[] 
defaultRequestAttributes() { return copyOf(defaultRequestAttributes); }
+               @Override /* Overridden from Mixin */ public String[] 
produces() { return copyOf(produces); }
+               @Override /* Overridden from Mixin */ public String[] 
consumes() { return copyOf(consumes); }
                @Override /* Overridden from Mixin */ public String 
defaultAccept() { return defaultAccept; }
                @Override /* Overridden from Mixin */ public String 
defaultContentType() { return defaultContentType; }
                @Override /* Overridden from Mixin */ public String 
defaultCharset() { return defaultCharset; }
                @Override /* Overridden from Mixin */ public String maxInput() 
{ return maxInput; }
                @Override /* Overridden from Mixin */ public String path() { 
return path; }
-               @Override /* Overridden from Mixin */ public String[] paths() { 
return paths; }
-               @Override /* Overridden from Mixin */ public String[] 
noInherit() { return noInherit; }
+               @Override /* Overridden from Mixin */ public String[] paths() { 
return copyOf(paths); }
+               @Override /* Overridden from Mixin */ public String[] 
noInherit() { return copyOf(noInherit); }
        }
 
        /**
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestContext.java
index ed42323d40..d1eb9ce4ec 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestContext.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestContext.java
@@ -4003,7 +4003,7 @@ public class RestContext extends Context {
         * @return The resolved mount paths (never {@code null}; possibly 
empty).
         * @since 10.0.0
         */
-       public String[] getPaths() { return paths; }
+       public String[] getPaths() { return copyOf(paths); }
 
        /**
         * Returns the path matcher for this context.
@@ -4081,7 +4081,7 @@ public class RestContext extends Context {
         *      The REST-op-arg classes for this resource.
         *      <br>Never <jk>null</jk>.
         */
-       public Class<? extends RestOpArg>[] getRestOpArgs() { return 
restOpArgs.get(); }
+       public Class<? extends RestOpArg>[] getRestOpArgs() { return 
copyOf(restOpArgs.get()); }
 
        /**
         * Returns the child resources associated with this servlet.
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestOpContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestOpContext.java
index c75e665faa..f9cc333e21 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestOpContext.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestOpContext.java
@@ -969,6 +969,13 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
        /** The required matchers extracted from {@link #matchersList}. */
        private final Memoizer<RestMatcher[]> requiredMatchers = memoizer(() -> 
matchersList.get().getRequiredEntries());
 
+       // Immutable, cached views over the routing arrays returned by the 
public getters.  Built once (memoized) so
+       // there is zero per-call allocation on the routing hot path and no 
live-array exposure to callers.  Internal
+       // hot-path code reads the private array memoizers directly; these 
views exist only for the public API surface.
+       private final Memoizer<List<UrlPathMatcher>> pathMatchersView = 
memoizer(() -> List.of(pathMatchers.get()));
+       private final Memoizer<List<RestMatcher>> optionalMatchersView = 
memoizer(() -> List.of(optionalMatchers.get()));
+       private final Memoizer<List<RestMatcher>> requiredMatchersView = 
memoizer(() -> List.of(requiredMatchers.get()));
+
        /** The computed response metadata for this operation method. */
        private final Memoizer<ResponseBeanMeta> responseMeta = memoizer(() -> 
ResponseBeanMeta.create(methodInfo(), appliedAnnotations()));
 
@@ -1368,7 +1375,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                // Pre-warm httpMethod so it is in the memoizer cache for 
immediate use by compareTo/match.
                httpMethod.get();
 
-                       var pm = getPathMatchers();
+                       var pm = pathMatchers.get();
                        bs.add(UrlPathMatcher[].class, pm);
                        bs.addBean(UrlPathMatcher.class, pm.length > 0 ? pm[0] 
: null);
 
@@ -1385,8 +1392,8 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
        public int compareTo(RestOpContext o) {
                int c;
 
-               var pm = getPathMatchers();
-               var opm = o.getPathMatchers();
+               var pm = pathMatchers.get();
+               var opm = o.pathMatchers.get();
                for (int i = 0; i < Math.min(pm.length, opm.length); i++) {
                        c = pm[i].compareTo(opm[i]);
                        if (c != 0)
@@ -1397,11 +1404,11 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                if (c != 0)
                        return c;
 
-               c = cmp(o.getRequiredMatchers().length, 
getRequiredMatchers().length);
+               c = cmp(o.requiredMatchers.get().length, 
requiredMatchers.get().length);
                if (c != 0)
                        return c;
 
-               c = cmp(o.getOptionalMatchers().length, 
getOptionalMatchers().length);
+               c = cmp(o.optionalMatchers.get().length, 
optionalMatchers.get().length);
                if (c != 0)
                        return c;
 
@@ -1428,6 +1435,14 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                if (c != 0)
                        return c;
 
+               // Declaring-class tie-breaker: keeps compareTo consistent with 
equals()/hashCode() (which key off method
+               // identity) for two ops whose methods share the same signature 
but differ by declaring class.  Only affects
+               // ordering that was previously a zero-tie, so it makes 
tie-break stability deterministic rather than changing
+               // any non-tied dispatch precedence.
+               c = cmp(method.getDeclaringClass().getName(), 
o.method.getDeclaringClass().getName());
+               if (c != 0)
+                       return c;
+
                return 0;
        }
 
@@ -1671,31 +1686,34 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         *
         * @return The path pattern.
         */
-       public String getPathPattern() { return 
getPathMatchers()[0].toString(); }
+       public String getPathPattern() { return 
pathMatchers.get()[0].toString(); }
 
        /**
         * Returns the URL path matchers for this operation.
         *
         * @return The URL path matchers for this operation.
         *      <br>Never <jk>null</jk>.
+        *      <br>The returned list is an immutable, cached view.
         */
-       public UrlPathMatcher[] getPathMatchers() { return pathMatchers.get(); }
+       public List<UrlPathMatcher> getPathMatchers() { return 
pathMatchersView.get(); }
 
        /**
         * Returns the optional matchers for this operation.
         *
         * @return The optional matchers for this operation.
         *      <br>Never <jk>null</jk>.
+        *      <br>The returned list is an immutable, cached view.
         */
-       public RestMatcher[] getOptionalMatchers() { return 
optionalMatchers.get(); }
+       public List<RestMatcher> getOptionalMatchers() { return 
optionalMatchersView.get(); }
 
        /**
         * Returns the required matchers for this operation.
         *
         * @return The required matchers for this operation.
         *      <br>Never <jk>null</jk>.
+        *      <br>The returned list is an immutable, cached view.
         */
-       public RestMatcher[] getRequiredMatchers() { return 
requiredMatchers.get(); }
+       public List<RestMatcher> getRequiredMatchers() { return 
requiredMatchersView.get(); }
 
        /**
         * Returns the call logger for this operation.
@@ -1803,7 +1821,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
 
        private UrlPathMatch matchPattern(RestSession call) {
                UrlPathMatch pm = null;
-               for (var pp : getPathMatchers())
+               for (var pp : pathMatchers.get())
                        if (pm == null)
                                pm = pp.match(call.getUrlPath());
                return pm;
@@ -1835,8 +1853,8 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                if (pm == null)
                        return 0;
 
-               var rm = getRequiredMatchers();
-               var om = getOptionalMatchers();
+               var rm = requiredMatchers.get();
+               var om = optionalMatchers.get();
                if (rm.length == 0 && om.length == 0) {
                        session.urlPathMatch(pm);  // Cache so we don't have to 
recalculate.
                        return 2;
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/auth/AuthFilterChain.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/auth/AuthFilterChain.java
index 8a1a0de9f5..179220f08e 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/auth/AuthFilterChain.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/auth/AuthFilterChain.java
@@ -16,6 +16,7 @@
  */
 package org.apache.juneau.rest.server.auth;
 
+import static org.apache.juneau.commons.utils.AssertionUtils.*;
 import static org.apache.juneau.commons.utils.CollectionUtils.*;
 import static org.apache.juneau.commons.utils.Shorts.*;
 import static org.apache.juneau.commons.utils.StringUtils.isEmpty;
@@ -88,8 +89,15 @@ import jakarta.servlet.http.*;
  *
  * @since 10.0.0
  */
+@SuppressWarnings({
+       "java:S115" // Constants use UPPER_snakeCase convention
+})
 public class AuthFilterChain implements Filter, Authenticator {
 
+       // Argument name constants for assertArgNotNull
+       private static final String ARG_value = "value";
+       private static final String ARG_pattern = "pattern";
+
        /**
         * A single entry in the chain: an {@link AuthFilter} paired with an 
optional path pattern.
         */
@@ -136,6 +144,7 @@ public class AuthFilterChain implements Filter, 
Authenticator {
                 * @return This object.
                 */
                public Builder append(AuthFilter value) {
+                       assertArgNotNull(ARG_value, value);
                        entries.add(new Entry(value, null));
                        return this;
                }
@@ -152,6 +161,8 @@ public class AuthFilterChain implements Filter, 
Authenticator {
                 * @return This object.
                 */
                public Builder append(AuthFilter value, String pattern) {
+                       assertArgNotNull(ARG_value, value);
+                       assertArgNotNullOrBlank(ARG_pattern, pattern);
                        entries.add(new Entry(value, 
UrlPathMatcher.of(pattern)));
                        return this;
                }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/auth/AuthenticatedRequestWrapper.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/auth/AuthenticatedRequestWrapper.java
index 334e08dfa9..bec91da91f 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/auth/AuthenticatedRequestWrapper.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/auth/AuthenticatedRequestWrapper.java
@@ -16,6 +16,8 @@
  */
 package org.apache.juneau.rest.server.auth;
 
+import static org.apache.juneau.commons.utils.AssertionUtils.*;
+
 import java.security.*;
 import java.util.*;
 
@@ -48,8 +50,15 @@ import jakarta.servlet.http.*;
  *
  * @since 10.0.0
  */
+@SuppressWarnings({
+       "java:S115" // Constants use UPPER_snakeCase convention
+})
 public class AuthenticatedRequestWrapper extends HttpServletRequestWrapper {
 
+       // Argument name constants for assertArgNotNull
+       private static final String ARG_req = "req";
+       private static final String ARG_result = "result";
+
        private final Principal principal;
        private final Set<String> roles;
 
@@ -60,7 +69,8 @@ public class AuthenticatedRequestWrapper extends 
HttpServletRequestWrapper {
         * @param result The authentication result. Must not be <jk>null</jk>.
         */
        public AuthenticatedRequestWrapper(HttpServletRequest req, AuthResult 
result) {
-               super(req);
+               super(assertArgNotNull(ARG_req, req));
+               assertArgNotNull(ARG_result, result);
                this.principal = result.getPrincipal();
                this.roles = new HashSet<>(result.getRoles());
        }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/matcher/RestMatcherList.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/matcher/RestMatcherList.java
index 2187d2a5b0..88567e9eb0 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/matcher/RestMatcherList.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/matcher/RestMatcherList.java
@@ -124,12 +124,12 @@ public class RestMatcherList {
         *
         * @return An unmodifiable list of entries in this list that are 
specified as optional.
         */
-       public RestMatcher[] getOptionalEntries() { return optionalEntries; }
+       public RestMatcher[] getOptionalEntries() { return 
copyOf(optionalEntries); }
 
        /**
         * Returns the entries in this list that are specified as required.
         *
         * @return An unmodifiable list of entries in this list that are 
specified as required.
         */
-       public RestMatcher[] getRequiredEntries() { return requiredEntries; }
+       public RestMatcher[] getRequiredEntries() { return 
copyOf(requiredEntries); }
 }
\ No newline at end of file
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/util/CachingHttpServletRequest.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/util/CachingHttpServletRequest.java
index 0c766e8de7..b088eac267 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/util/CachingHttpServletRequest.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/util/CachingHttpServletRequest.java
@@ -61,9 +61,9 @@ public class CachingHttpServletRequest extends 
HttpServletRequestWrapper {
        /**
         * Returns the content of the servlet request without consuming the 
stream.
         *
-        * @return The content of the request.
+        * @return The content of the request.  This is a defensive copy; 
modifying it does not affect the cached request body.
         */
-       public byte[] getContent() { return content; }
+       public byte[] getContent() { return content.clone(); }
 
        @Override
        public ServletInputStream getInputStream() { return new 
BoundedServletInputStream(content); }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/util/UrlPath.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/util/UrlPath.java
index 3c12a05443..6957904189 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/util/UrlPath.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/util/UrlPath.java
@@ -86,9 +86,9 @@ public class UrlPath {
        /**
         * Returns the path parts.
         *
-        * @return The path parts.
+        * @return The path parts.  This is a defensive copy; modifying it does 
not affect this object's routing state.
         */
-       public String[] getParts() { return parts; }
+       public String[] getParts() { return parts.clone(); }
 
        /**
         * Returns the raw path passed into this object.

Reply via email to