[ 
https://issues.apache.org/jira/browse/WW-5700?focusedWorklogId=1038233&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1038233
 ]

ASF GitHub Bot logged work on WW-5700:
--------------------------------------

                Author: ASF GitHub Bot
            Created on: 27/Aug/26 08:32
            Start Date: 27/Aug/26 08:32
    Worklog Time Spent: 10m 
      Work Description: lukaszlenart opened a new pull request, #1873:
URL: https://github.com/apache/struts/pull/1873

   Fixes [WW-5700](https://issues.apache.org/jira/browse/WW-5700)
   
   ### Problem
   
   `XWorkConverter.convertValue()` signals a failed conversion by returning
   `TypeConverter.NO_CONVERSION_POSSIBLE` — which is itself a plain `String`,
   `"ognl.NoConversionPossible"`. `XWorkMapPropertyAccessor` and
   `XWorkListPropertyAccessor` stored that return value into the target 
collection
   without checking for it.
   
   Generics are erased at that point, so the `put` succeeds silently and the
   `ClassCastException` only surfaces later, when application code reads the 
entry
   back — with a stack trace pointing away from the framework, which makes this
   very hard to recognise from a bug report.
   
   Reported on user@ as *"Struts setting a String object instead of Integer in 
the
   form"*: an unchecked `<s:checkbox submitUnchecked="true"/>` causes
   `CheckboxInterceptor` to submit its `uncheckedValue`, `"false"`, which cannot
   become an `Integer`.
   
   ### Fix
   
   Guard both accessors and skip the assignment. The conversion error has 
already
   been registered by `convertValue()`, so nothing is lost — validation-driven
   actions see no change at all.
   
   Details worth a reviewer's attention:
   
   - The map accessor guards the **key** as well as the value. An unconvertible 
key
     poisons iteration over the whole map rather than a single entry, and is
     reachable because `ACCEPTED_PATTERNS` is asymmetric: the bare-bracket 
branch
     accepts digits only, `(\[\d+])`, but the quoted-key branch accepts word
     characters, `(\['(\w-?|[一-龥]-?)+'])`. So `capDeferral['abc']` is an 
accepted
     parameter name even where the declared key type is numeric.
   - **Identity comparison**, not `equals()`, matching OGNL's own guard in
     `OgnlRuntime`. The constant is a String literal and the converter returns 
that
     exact reference, so `equals()` would silently discard a form legitimately
     submitting the text `ognl.NoConversionPossible` into a String-valued 
collection.
   - The guard **skips rather than throws**: on the 
`XWorkMethodAccessor.callMethod`
     path an exception here would be swallowed and only logged.
   - In the list accessor the guard sits **before** the auto-grow block, so an
     unconvertible value does not grow the list.
   
   ### Not included
   
   `XWorkCollectionPropertyAccessor` carries the same unguarded pattern, but its
   scalar `setProperty` path is not reachable through the value stack — 
`ids[0]` on
   a `Set` is rejected by OGNL before it gets there. No failing test could be
   written, so it is deliberately left untouched rather than changed blind.
   
   ### Precedent
   
   [WW-3762](https://issues.apache.org/jira/browse/WW-3762) fixed this same bug 
class
   in `XWorkBasicConverter.doConvertToCollection` in 2.3.3, and 
`CollectionConverter`
   still carries that guard today, three times. The property accessors were 
simply
   never given the equivalent check.
   
   ### Testing
   
   Written test-first; each test was watched failing for the right reason 
before the
   fix, with a valid entry binding first so none can pass vacuously. For the
   end-to-end test the production change was stashed to confirm it fails 
without it.
   
   - `XWorkMapPropertyAccessorTest` — unconvertible value, unconvertible key
   - `XWorkListPropertyAccessorTest` — unconvertible element
   - `ParametersInterceptorTest` — the reported checkbox scenario, end to end
   
   Full core suite: **3201 tests, 0 failures**.
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)




Issue Time Tracking
-------------------

            Worklog Id:     (was: 1038233)
    Remaining Estimate: 0h
            Time Spent: 10m

> Failed type conversion stores the NO_CONVERSION_POSSIBLE marker string into 
> typed Maps, Lists and Collections
> -------------------------------------------------------------------------------------------------------------
>
>                 Key: WW-5700
>                 URL: https://issues.apache.org/jira/browse/WW-5700
>             Project: Struts 2
>          Issue Type: Bug
>            Reporter: Lukasz Lenart
>            Assignee: Lukasz Lenart
>            Priority: Major
>          Time Spent: 10m
>  Remaining Estimate: 0h
>
> h2. Summary
> When conversion of a request parameter into a typed collection fails, Struts 
> stores its internal
> "conversion failed" marker into the collection instead of skipping the 
> assignment. The marker is
> itself a {{java.lang.String}}, so it lands in a collection declared to hold 
> some other type. Because
> generics are erased at that point the store succeeds silently, and the 
> {{ClassCastException}} is
> deferred until application code reads the entry back.
> The resulting stack trace points at _application_ code rather than at Struts, 
> which makes this very
> hard to recognise from a bug report.
> Reported on the user list:
> "Struts setting a String object instead of Integer in the form" (2026-08-14), 
> against 7.2.1 and 7.3.0.
> h2. Root cause
> {{TypeConverter.NO\_CONVERSION\_POSSIBLE}} is not a sentinel object. It is a 
> plain String:
> {code:java}
> // core/src/main/java/org/apache/struts2/conversion/TypeConverter.java:49
> Object NO\_CONVERSION\_POSSIBLE = "ognl.NoConversionPossible";
> {code}
> {{XWorkConverter.convertValue}} returns it on failure (lines 339, 351, 361), 
> _after_ correctly
> registering the conversion error via {{handleConversionException}}. Three 
> property accessors then
> store that return value with no guard:
> * {{XWorkMapPropertyAccessor.setProperty}} (~line 127) - both {{getKey()}} 
> and {{getValue()}} unguarded
> * {{XWorkListPropertyAccessor.getRealValue}} (line 188)
> * {{XWorkCollectionPropertyAccessor.getRealValue}} (line 263)
> By contrast OGNL itself guards this correctly at {{OgnlRuntime}} line 1323, 
> which is why a plain
> (non-collection) property is left untouched on a failed conversion.
> h2. Reproduced
> On {{main}} at 05ad78a06, end-to-end through {{ParametersInterceptor}} with
> {{struts.parameters.requireAnnotations=true}}. Both halves reproduce.
> _Value half_ - the reporter's case. An unchecked {{s:checkbox}} with 
> {{submitUnchecked="true"}}
> causes {{CheckboxInterceptor}} to submit the parameter with its 
> {{uncheckedValue}}, default
> {{"false"}}. Bound into a HashMap with Long keys and Integer values:
> {noformat}
> key=100 value=[1] (java.lang.Integer)
> key=200 value=[ognl.NoConversionPossible] (java.lang.String)
> conversionErrors={capDeferral[200]=ConversionData@...}
> {noformat}
> _Key half_ - the marker is stored as a map _key_, which breaks iteration over 
> the entire map rather
> than a single entry:
> {noformat}
> acceptable=[capDeferral['abc'], capDeferral[7]]
> key=[ognl.NoConversionPossible] (java.lang.String) value=[1]
> key=[7] (java.lang.Long) value=[2]
> conversionErrors={capDeferral['abc']=ConversionData@...}
> {noformat}
> The key half is reachable because {{ACCEPTED\_PATTERNS}} in
> {{DefaultAcceptedPatternsChecker}} is asymmetric: the bare-bracket branch 
> accepts digits only,
> {{(\[\d+])}}, but the quoted-key branch accepts word characters,
> {{(\['(\w-?|[一-龥]-?)+'])}}. So {{capDeferral['abc']}} is an accepted 
> parameter name even
> where the declared map key type is numeric, and nothing downstream re-checks 
> the key against that
> type. This is worth stating explicitly because the natural "map indices are 
> numeric" intuition does
> not hold.
> Note the conversion error _is_ reported in both halves. This is therefore not 
> a validation bypass:
> it only bites an action that reads the collection without acting on 
> conversion errors.
> h2. Proposed fix
> Guard for the marker and skip the store; the error has already been 
> registered, so nothing is lost.
> {code:java}
> Object key = getKey(context, name);
> if (key == TypeConverter.NO\_CONVERSION\_POSSIBLE) {
>     return;
> }
> Object convertedValue = getValue(context, value);
> if (convertedValue == TypeConverter.NO\_CONVERSION\_POSSIBLE) {
>     return;
> }
> map.put(key, convertedValue);
> {code}
> Same guard in the List and Collection accessors, placed immediately after 
> {{getRealValue}} and, in
> the List case, _before_ the auto-grow block so an unconvertible value does 
> not grow the list.
> Use identity comparison rather than {{equals}}, matching OGNL's own guard: 
> the constant is a String
> literal and the converter returns that exact reference, so with {{equals}} a 
> form legitimately
> submitting the text "ognl.NoConversionPossible" into a String-valued map 
> would be silently
> discarded.
> h2. Precedent
> WW-3762 fixed exactly this class of bug in 
> {{XWorkBasicConverter.doConvertToCollection}} back in
> 2.3.3. {{CollectionConverter}} still carries that guard today, three times. 
> The property accessors
> were simply never given the equivalent check.
> h2. Backward compatibility
> Narrow. Previously an unconvertible entry was stored as the marker String; 
> now nothing is stored.
> Nothing can reasonably depend on the old behaviour, and the conversion error 
> is reported either way,
> so validation-driven actions see no change at all.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to