sbglasius opened a new issue, #16295:
URL: https://github.com/apache/grails-core/issues/16295
### Expected Behavior
`render(someObject as JSON)` should serialize an ordinary `Serializable`
Java bean. Practically every
Java bean declares `private static final long serialVersionUID`, so this
covers a very large share of
all objects users pass to `as JSON`.
### Actual Behaviour
The request fails with HTTP 500 as soon as the object graph reaches a
`Serializable` Java bean — here
Spring Security's `SimpleGrantedAuthority`:
```
org.grails.web.converters.exceptions.ConverterException:
Error converting Bean with class
org.springframework.security.core.authority.SimpleGrantedAuthority
at
org.grails.web.converters.marshaller.json.GenericJavaBeanMarshaller.marshalObject(GenericJavaBeanMarshaller.java:87)
at
org.grails.web.converters.marshaller.json.CollectionMarshaller.marshalObject(CollectionMarshaller.java:43)
...
Caused by: java.lang.IllegalArgumentException: non-null object for
private static final long
org.springframework.security.core.authority.SimpleGrantedAuthority.serialVersionUID
at
org.grails.web.converters.marshaller.json.GenericJavaBeanMarshaller.marshalObject(GenericJavaBeanMarshaller.java:74)
```
**Root cause.** `GenericJavaBeanMarshaller.java:74` has its `&&` operands in
the wrong order:
```java
for (Field field : o.getClass().getDeclaredFields()) {
int modifiers = field.getModifiers();
if (field.canAccess(o) && Modifier.isPublic(modifiers) &&
!(Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers))) {
```
Per its javadoc, `AccessibleObject.canAccess(obj)` throws
`IllegalArgumentException` when the member
is `static` and `obj` is non-null. Because `canAccess(o)` is evaluated
**first**, the
`Modifier.isStatic(...)` guard never gets the chance to short-circuit it,
and the loop blows up on
the first static field it meets — typically `serialVersionUID`.
### Suggested fix
Swap the operands so the cheap modifier checks run first:
```java
if (Modifier.isPublic(modifiers) && !(Modifier.isStatic(modifiers) ||
Modifier.isTransient(modifiers)) && field.canAccess(o)) {
```
Two call sites (line numbers on `8.0.x`):
-
`grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/GenericJavaBeanMarshaller.java:74`
-
`grails-converters/src/main/groovy/org/grails/web/converters/marshaller/xml/GenericJavaBeanMarshaller.java:57`
Verified by shadowing the class in the reproducer's `src/main/groovy` with a
patched copy.
### Regression
This is a regression introduced in the 8.x line, in commit
[`9e60b8a4de`](https://github.com/apache/grails-core/commit/9e60b8a4de10822ecb22d404a38e21840f5ab821)
("refactor: replace usages of deprecated external APIs"). `7.2.x` and
earlier read:
```java
if (field.isAccessible() && Modifier.isPublic(modifiers) &&
!(Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers))) {
```
`isAccessible()` takes no argument and never throws, so the operand order
was harmless there. The
mechanical `isAccessible()` → `canAccess(o)` swap made the first operand
throwing, which is what
turns this into a hard failure.
Worth noting while fixing: on `7.2.x` this branch was effectively dead code,
because
`field.isAccessible()` returns `false` for any field that was never
`setAccessible(true)`-ed. So
`canAccess(o)` is the right *intent* — it makes public fields on public
classes actually serialize —
it just has to be evaluated last.
Present on `8.0.x`, `8.1.x` and `9.0.x`; the commit is contained in tags
`v8.0.0-M2` through
`v8.0.0-M6`. Not present on `7.x`.
### Steps To Reproduce
```bash
git clone https://github.com/sbglasius/asjson-bug
cd asjson-bug
./gradlew integrationTest
```
`DemoControllerTest > simple GET on show` fails.
Note this bug is **masked** by the companion issue: out of the box the
request dies earlier, in
`GroovyBeanMarshaller`, on the anonymous `UserDetails` class. Patch that
first (or hand `as JSON` any
`Serializable` bean with a public class) and you land on this one. The
reproducer's README documents
both and shows how to shadow the marshallers to step through them one at a
time.
### Environment Information
- Operating System: Linux (kernel 6.17)
- JDK Version: 25.0.4 (BellSoft Liberica)
- Groovy 5.0.8, Spring Boot 4.1.0, Spring Framework 7.0.8, Spring Security
7.1.0
- Gradle 9.6
### Example Application
https://github.com/sbglasius/asjson-bug
### Version
8.0.0-M5
### Companion issue
Masked by https://github.com/apache/grails-core/issues/16294 — fix that one
first to reach this bug.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]