Expected Behavior
Subscript and dot access on a GrailsParameterMap always address a request parameter:
def params = new GrailsParameterMap(request)
params['identifier'] = 'id1'
assert params['identifier'] == 'id1'
getIdentifier() and getRequest() remain reachable as explicit method calls, with params.getIdentifier() returning the id entry of the map — unchanged.
Actual Behaviour
On Groovy 5 (reproduced on 5.0.6 and 5.1.0) the assignment throws:
groovy.lang.ReadOnlyPropertyException: Cannot set read-only property: identifier for class: grails.web.servlet.mvc.GrailsParameterMap
at groovy.lang.MetaClassImpl.setProperty(MetaClassImpl.java:2837)
at org.codehaus.groovy.runtime.InvokerHelper.setProperty(InvokerHelper.java:187)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.putAt(DefaultGroovyMethods.java:12191)
Groovy 5 changed runtime method selection for classes implementing Map: for a String key, DefaultGroovyMethods.putAt(Object, String, Object) (→ setProperty) is now preferred over putAt(Map, K, V) (→ put), and getAt(Object, String) (→ getProperty) over getAt(Map, Object). Because GrailsParameterMap declares getIdentifier() and getRequest() with no setters, those names became read-only properties rather than ordinary map keys.
| Expression |
Groovy 4.0.30 |
Groovy 5.x |
params['identifier'] = 'x' |
map put |
throws ReadOnlyPropertyException |
params['request'] = 'x' |
map put |
throws ReadOnlyPropertyException |
params['identifier'] |
map value |
getIdentifier() → params.id |
params['request'] |
map value |
the HttpServletRequest |
params.identifier / params.request |
map value |
getter result |
A request parameter named identifier or request is therefore silently unreachable, and writing one throws. GroovyPageAttributes is affected the same way through the shared base grails.util.AbstractTypeConvertingMap — attrs['gspTagSyntaxCall'] = x writes the field instead of the map.
Steps To Reproduce
new GrailsParameterMap(new MockHttpServletRequest())
map['identifier'] = 'id1'
ReadOnlyPropertyException is thrown.
Added as grails.web.servlet.mvc.GrailsParameterMapTests#testAddingIdentifierParam.
Environment Information
- Grails: 8.0.0-SNAPSHOT (
8.0.x)
- Groovy: 5.1.0 (also reproduced on 5.0.6; works on 4.0.30)
- JDK: 21+
Only 8.0.x is affected — 7.0.x is on Groovy 4.x.
Expected Behavior
Subscript and dot access on a
GrailsParameterMapalways address a request parameter:getIdentifier()andgetRequest()remain reachable as explicit method calls, withparams.getIdentifier()returning theidentry of the map — unchanged.Actual Behaviour
On Groovy 5 (reproduced on 5.0.6 and 5.1.0) the assignment throws:
Groovy 5 changed runtime method selection for classes implementing
Map: for aStringkey,DefaultGroovyMethods.putAt(Object, String, Object)(→setProperty) is now preferred overputAt(Map, K, V)(→put), andgetAt(Object, String)(→getProperty) overgetAt(Map, Object). BecauseGrailsParameterMapdeclaresgetIdentifier()andgetRequest()with no setters, those names became read-only properties rather than ordinary map keys.params['identifier'] = 'x'ReadOnlyPropertyExceptionparams['request'] = 'x'ReadOnlyPropertyExceptionparams['identifier']getIdentifier()→params.idparams['request']HttpServletRequestparams.identifier/params.requestA request parameter named
identifierorrequestis therefore silently unreachable, and writing one throws.GroovyPageAttributesis affected the same way through the shared basegrails.util.AbstractTypeConvertingMap—attrs['gspTagSyntaxCall'] = xwrites the field instead of the map.Steps To Reproduce
new GrailsParameterMap(new MockHttpServletRequest())map['identifier'] = 'id1'ReadOnlyPropertyExceptionis thrown.Added as
grails.web.servlet.mvc.GrailsParameterMapTests#testAddingIdentifierParam.Environment Information
8.0.x)Only
8.0.xis affected —7.0.xis on Groovy 4.x.