sbglasius commented on code in PR #16296:
URL: https://github.com/apache/grails-core/pull/16296#discussion_r3916991229


##########
grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/GenericJavaBeanMarshaller.java:
##########
@@ -64,14 +66,17 @@ public void marshalObject(Object o, JSON json) throws 
ConverterException {
                     if (Modifier.isStatic(readMethod.getModifiers())) continue;
                     if (readMethod.getAnnotation(PersistenceMethod.class) != 
null) continue;
                     if (readMethod.getAnnotation(ControllerMethod.class) != 
null) continue;
-                    Object value = readMethod.invoke(o, (Object[]) null);
+                    Method invokableMethod = 
ClassUtils.getInterfaceMethodIfPossible(readMethod, clazz);

Review Comment:
   Yes, it does, but only for non-interface classes. The full answer goes like 
this:
   ```
   == beans.PkgWithInterface  (class public: false)
     raw read method canAccess from this package: false
     getInterfaceMethodIfPossible -> beans.Public (same object as raw: false)
     resolved canAccess BEFORE makeAccessible: true      <- already accessible
     resolved canAccess AFTER  makeAccessible: true
     the raw class-declared method still canAccess: false <- untouched
   
   == beans.PkgNoInterface  (class public: false)
     getInterfaceMethodIfPossible -> beans.PkgNoInterface (same object as raw: 
true)
     resolved canAccess BEFORE makeAccessible: false
     resolved canAccess AFTER  makeAccessible: true       <- flag flipped
     a later BeanUtils+resolve sees it accessible: true   (same object: true)
     a FRESH getDeclaredMethod copy canAccess: false      <- not globally opened
   ```
   
   * Interface case (the reported bug — anonymous `UserDetails`): 
`getInterfaceMethodIfPossible` hands back a different Method, the interface's, 
which is public-on-public. `ReflectionUtils.makeAccessible` short-circuits and 
*nothing is mutated.* The class's own read method stays inaccessible.
   * No-interface case (package-private class, public getter, nothing to 
resolve to): yes, `setAccessible(true)` fires and it persists. Two bounds on 
how far:
     * The flag lives on the Method instance, not on class metadata. 
`BeanUtils.getPropertyDescriptors` is backed by the static 
`CachedIntrospectionResults` cache and returns the same instance every call 
(confirmed above), so anything else in the JVM that asks Spring for that 
class's descriptors gets an already-invokable method, for the lifetime of that 
cache.
     * A fresh `getDeclaredMethod`/`getMethod` copy is still false. The member 
is not globally opened, and this grants nothing a caller couldn't get itself — 
any classpath code in the unnamed module can `setAccessible` a public method of 
a non-public class. It's also the pattern Spring itself uses on cached members 
(`AutowiredAnnotationBeanPostProcessor`, `AbstractNestablePropertyAccessor`).
   
   If you'd rather not mutate shared cached state at all, the clean alternative 
is to work on our own copy — `clazz.getDeclaredMethod(name)` returns a new 
instance per call (also confirmed above), so `setAccessible` on that leaks 
nowhere. Cost is one extra reflective lookup per property per marshal unless we 
cache it ourselves.



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