From 703ba48e9097cb92de5c9932f4df832170ca9df6 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Thu, 12 Feb 2026 19:12:52 +0200 Subject: [PATCH] fix(dispatcher): WW-3576 remove redundant volatile from SessionMap fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The volatile keyword on non-primitive fields (HttpSession, Set) triggers Sonar rule S3077 because volatile only guarantees reference visibility, not thread-safety of object contents. Since all field accesses are already protected by synchronized(this), the volatile keyword is redundant - synchronization already provides both visibility (happens-before) and atomicity guarantees. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../apache/struts2/dispatcher/SessionMap.java | 94 ++++++++++++++----- 1 file changed, 69 insertions(+), 25 deletions(-) diff --git a/core/src/main/java/org/apache/struts2/dispatcher/SessionMap.java b/core/src/main/java/org/apache/struts2/dispatcher/SessionMap.java index d2ecaefad..899f241be 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/SessionMap.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/SessionMap.java @@ -39,8 +39,8 @@ public class SessionMap extends AbstractMap implements Serializa @Serial private static final long serialVersionUID = 4678843241638046854L; - protected volatile HttpSession session; - protected volatile Set> entries; + protected HttpSession session; + protected Set> entries; protected HttpServletRequest request; @@ -66,7 +66,11 @@ public class SessionMap extends AbstractMap implements Serializa if (session == null) { return; } - session.invalidate(); + try { + session.invalidate(); + } catch (IllegalStateException e) { + // Session was already invalidated externally, ignore + } session = null; entries = null; } @@ -83,9 +87,15 @@ public class SessionMap extends AbstractMap implements Serializa return; } entries = null; - final Enumeration attributeNamesEnum = session.getAttributeNames(); - while (attributeNamesEnum.hasMoreElements()) { - session.removeAttribute(attributeNamesEnum.nextElement()); + try { + final Enumeration attributeNamesEnum = session.getAttributeNames(); + while (attributeNamesEnum.hasMoreElements()) { + session.removeAttribute(attributeNamesEnum.nextElement()); + } + } catch (IllegalStateException e) { + // Session was invalidated externally + session = null; + entries = null; } } } @@ -103,20 +113,26 @@ public class SessionMap extends AbstractMap implements Serializa } if (entries == null) { entries = new HashSet<>(); + try { + final Enumeration enumeration = session.getAttributeNames(); - final Enumeration enumeration = session.getAttributeNames(); + while (enumeration.hasMoreElements()) { + final String key = enumeration.nextElement(); + final Object value = session.getAttribute(key); + entries.add(new StringObjectEntry(key, value) { + @Override + public Object setValue(final Object obj) { + session.setAttribute(key, obj); - while (enumeration.hasMoreElements()) { - final String key = enumeration.nextElement(); - final Object value = session.getAttribute(key); - entries.add(new StringObjectEntry(key, value) { - @Override - public Object setValue(final Object obj) { - session.setAttribute(key, obj); - - return value; - } - }); + return value; + } + }); + } + } catch (IllegalStateException e) { + // Session was invalidated externally + session = null; + entries = null; + return Collections.emptySet(); } } return entries; @@ -138,7 +154,14 @@ public class SessionMap extends AbstractMap implements Serializa if (session == null) { return null; } - return session.getAttribute(key != null ? key.toString() : null); + try { + return session.getAttribute(key != null ? key.toString() : null); + } catch (IllegalStateException e) { + // Session was invalidated externally + session = null; + entries = null; + return null; + } } } @@ -155,9 +178,17 @@ public class SessionMap extends AbstractMap implements Serializa if (session == null) { session = request.getSession(true); } + // Use get(key) to allow subclasses to override the retrieval behavior final Object oldValue = get(key); entries = null; - session.setAttribute(key, value); + try { + session.setAttribute(key, value); + } catch (IllegalStateException e) { + // Session was invalidated externally, create new one + session = request.getSession(true); + entries = null; + session.setAttribute(key, value); + } return oldValue; } } @@ -180,10 +211,16 @@ public class SessionMap extends AbstractMap implements Serializa entries = null; final String keyAsString = (key != null ? key.toString() : null); - final Object value = session.getAttribute(keyAsString); - session.removeAttribute(keyAsString); - - return value; + try { + final Object value = session.getAttribute(keyAsString); + session.removeAttribute(keyAsString); + return value; + } catch (IllegalStateException e) { + // Session was invalidated externally + session = null; + entries = null; + return null; + } } } @@ -204,7 +241,14 @@ public class SessionMap extends AbstractMap implements Serializa return false; } final String keyAsString = (key != null ? key.toString() : null); - return (session.getAttribute(keyAsString) != null); + try { + return (session.getAttribute(keyAsString) != null); + } catch (IllegalStateException e) { + // Session was invalidated externally + session = null; + entries = null; + return false; + } } } }