sbglasius opened a new issue, #16294:
URL: https://github.com/apache/grails-core/issues/16294

   ### Expected Behavior
   
   `render(someObject as JSON)` should serialize an object whose class is not 
`public` — an anonymous
   class, a local class, or a package-private class — as long as the property 
getters themselves are
   `public`. This is a very common shape in Spring Security code, where you 
hand out an anonymous
   implementation of a public interface such as `UserDetails`.
   
   ### Actual Behaviour
   
   The request fails with HTTP 500:
   
   ```
   org.grails.web.converters.exceptions.ConverterException:
       Error converting Bean with class com.example.DemoController$1
        at 
org.grails.web.converters.marshaller.json.GroovyBeanMarshaller.marshalObject(GroovyBeanMarshaller.java:89)
        at grails.converters.JSON.value(JSON.java:209)
        at 
org.grails.web.converters.marshaller.json.MapMarshaller.marshalObject(MapMarshaller.java:47)
        ...
   Caused by: java.lang.IllegalAccessException:
       class org.grails.web.converters.marshaller.json.GroovyBeanMarshaller
       cannot access a member of class com.example.DemoController$1 with 
modifiers "public"
        at 
java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:400)
        at 
java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:670)
        at java.base/java.lang.reflect.Method.invoke(Method.java:556)
        at 
org.grails.web.converters.marshaller.json.GroovyBeanMarshaller.marshalObject(GroovyBeanMarshaller.java:69)
   ```
   
   **Root cause.** `GroovyBeanMarshaller.java:69` invokes the read method with 
no accessibility
   handling at all:
   
   ```java
   Object value = readMethod.invoke(o, (Object[]) null);
   ```
   
   `BeanUtils.getPropertyDescriptors(clazz)` returns, for the *overridden* 
properties, read methods
   whose declaring class is the anonymous class itself. An anonymous class is 
effectively
   package-private, so a caller in another package cannot invoke those methods 
even though the methods
   carry the `public` modifier. `setAccessible`/`makeAccessible` is never 
called — grepping
   `grails-converters` for `makeAccessible|setAccessible` returns nothing.
   
   Note the asymmetry this produces. Reproduced standalone against the 
reproducer app's own classpath,
   the `UserDetails` **default** methods keep the interface as their declaring 
class and succeed, while
   the three overridden ones fail:
   
   ```
   clazz=com.example.probe.Factory$1 public=false
     accountNonExpired:      declared on ...UserDetails  -> OK (true)
     accountNonLocked:       declared on ...UserDetails  -> OK (true)
     authorities:            declared on ...Factory$1    -> 
IllegalAccessException
     credentialsNonExpired:  declared on ...UserDetails  -> OK (true)
     enabled:                declared on ...UserDetails  -> OK (true)
     password:               declared on ...Factory$1    -> 
IllegalAccessException
     username:               declared on ...Factory$1    -> 
IllegalAccessException
         getInterfaceMethodIfPossible -> UserDetails => OK (user)
         makeAccessible               -> OK (user)
   ```
   
   ### Suggested fix
   
   Resolve the read method to a publicly accessible interface method, and make 
it accessible:
   
   ```java
   Method invokable = ClassUtils.getInterfaceMethodIfPossible(readMethod, 
clazz);
   ReflectionUtils.makeAccessible(invokable);
   Object value = invokable.invoke(o, (Object[]) null);
   ```
   
   All four marshallers call `readMethod.invoke` bare and need the same 
treatment (line numbers on
   `8.0.x`):
   
   - 
`grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/GroovyBeanMarshaller.java:69`
   - 
`grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/GenericJavaBeanMarshaller.java:67`
   - 
`grails-converters/src/main/groovy/org/grails/web/converters/marshaller/xml/GroovyBeanMarshaller.java:69`
   - 
`grails-converters/src/main/groovy/org/grails/web/converters/marshaller/xml/GenericJavaBeanMarshaller.java:49`
   
   I verified the fix by shadowing the marshaller classes in the reproducer's 
`src/main/groovy` with
   patched copies: the request then returns HTTP 200 and the integration test 
passes.
   
   Fixing this bug alone is **not** sufficient to make the reproducer pass — it 
then hits a second,
   independent bug in `GenericJavaBeanMarshaller`, filed separately as the 
companion issue.
   
   ### Affected versions
   
   This is not a regression. The bare `readMethod.invoke(...)` call is present 
on every branch that
   carries the `grails-converters` module — `7.0.x`, `7.1.x`, `7.2.x`, `8.0.x`, 
`8.1.x` and `9.0.x`.
   I reproduced it on 8.0.0-M5 only; the other branches are identified by code 
inspection.
   
   ### Steps To Reproduce
   
   ```bash
   git clone https://github.com/sbglasius/asjson-bug
   cd asjson-bug
   ./gradlew integrationTest
   ```
   
   `DemoControllerTest > simple GET on show` fails. The controller action is 
simply:
   
   ```groovy
   def show() {
       def user = new UserDetails() {
           @Override Collection<? extends GrantedAuthority> getAuthorities() { 
[new SimpleGrantedAuthority('ROLE_ADMIN')] }
           @Override String getPassword() { null }
           @Override String getUsername() { "user" }
       }
       render([user: user] as JSON)
   }
   ```
   
   `DemoControllerTest > simple GET on index`, which renders a plain `Map`, 
passes — so the failure is
   specific to marshalling the non-public class.
   
   ### 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
   


-- 
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]

Reply via email to