From a8f6b4c59d660c4d7b8f3b17d1539e3866c1905f Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Mon, 3 Aug 2026 12:58:54 +0200 Subject: [PATCH] WW-5674 perf(ognl): resolve package names via cached Class.getPackageName getPackage() performs a classloader package-map lookup on every call; the name returned by getPackageName() is computed once and cached on the Class. The isArray()/isPrimitive() guard covers exactly the inputs for which getPackage() returns null, so results are unchanged for every class shape. --- .../org/apache/struts2/ognl/SecurityMemberAccess.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 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 d25bbe377..0e22e74ce 100644 --- a/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java +++ b/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java @@ -372,10 +372,16 @@ public class SecurityMemberAccess implements MemberAccess { } public static String toPackageName(Class clazz) { - if (clazz.getPackage() == null) { + // Class.getPackage() resolves through the defining classloader's package map on every + // call, whereas getPackageName() is computed once and cached on the Class. getPackage() + // returns null for exactly arrays, primitives and void, so the guard reproduces the + // previous result for every input. Note that void.class.isPrimitive() is true. + // Arrays deliberately keep the empty package here: getPackageName() would resolve them + // to the element type's package, which would loosen the allowlist. See WW-5674. + if (clazz.isArray() || clazz.isPrimitive()) { return ""; } - return clazz.getPackage().getName(); + return clazz.getPackageName(); } protected boolean isExcludedPackageNamePatterns(Class clazz) {