From 34e5cf8b2a5c3add24211b013761a892fef238f4 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Mon, 3 Aug 2026 13:12:27 +0200 Subject: [PATCH] WW-5674 perf(ognl): match both allowlist package sets in one walk isClassAllowlisted walked the class's package name twice, once for ALLOWLIST_REQUIRED_PACKAGES and once for the configured allowlist. A two-set overload probes both sets at each prefix, halving the work on a path that runs for every OGNL member access. Asserted equivalent to OR-ing the two single-set calls across a matrix of class shapes and candidate sets. Co-Authored-By: Claude Opus 5 --- .../struts2/ognl/SecurityMemberAccess.java | 19 ++++++++++++++++--- ...curityMemberAccessPackageMatchingTest.java | 14 ++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java b/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java index e41004a2b..b18e568fc 100644 --- a/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java +++ b/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java @@ -254,8 +254,7 @@ public class SecurityMemberAccess implements MemberAccess { || ALLOWLIST_REQUIRED_CLASSES.contains(clazz) || (providerAllowlist != null && providerAllowlist.getProviderAllowlist().contains(clazz)) || (threadAllowlist != null && threadAllowlist.getAllowlist().contains(clazz)) - || isClassBelongsToPackages(clazz, ALLOWLIST_REQUIRED_PACKAGES) - || isClassBelongsToPackages(clazz, allowlistPackageNames); + || isClassBelongsToPackages(clazz, ALLOWLIST_REQUIRED_PACKAGES, allowlistPackageNames); } /** @@ -391,7 +390,21 @@ public class SecurityMemberAccess implements MemberAccess { } public static boolean isClassBelongsToPackages(Class clazz, Set matchingPackages) { - return isPackageBelongsToPackages(toPackageName(clazz), matchingPackages, emptySet()); + return isClassBelongsToPackages(clazz, matchingPackages, emptySet()); + } + + /** + * Tests the class's package against two sets in a single walk. Equivalent to calling + * {@link #isClassBelongsToPackages(Class, Set)} once per set and OR-ing the results, but + * walks the package name only once. + * + * @param clazz the class whose package is tested + * @param first the first set of package names to match against + * @param second the second set of package names to match against + * @return {@code true} if the class's package or any parent package is in either set + */ + public static boolean isClassBelongsToPackages(Class clazz, Set first, Set second) { + return isPackageBelongsToPackages(toPackageName(clazz), first, second); } /** diff --git a/core/src/test/java/org/apache/struts2/ognl/SecurityMemberAccessPackageMatchingTest.java b/core/src/test/java/org/apache/struts2/ognl/SecurityMemberAccessPackageMatchingTest.java index 04e2da96c..361a1c43b 100644 --- a/core/src/test/java/org/apache/struts2/ognl/SecurityMemberAccessPackageMatchingTest.java +++ b/core/src/test/java/org/apache/struts2/ognl/SecurityMemberAccessPackageMatchingTest.java @@ -195,4 +195,18 @@ public class SecurityMemberAccessPackageMatchingTest { .isFalse(); } } + + @Test + public void twoSetOverloadEqualsDisjunctionOfSingleSetCalls() throws Exception { + for (Class clazz : classShapes()) { + for (Set first : CANDIDATE_SETS) { + for (Set second : CANDIDATE_SETS) { + assertThat(isClassBelongsToPackages(clazz, first, second)) + .as("clazz=[%s] first=%s second=%s", clazz.getName(), first, second) + .isEqualTo(isClassBelongsToPackages(clazz, first) + || isClassBelongsToPackages(clazz, second)); + } + } + } + } }