obabichevjb opened a new pull request, #16490:
URL: https://github.com/apache/dubbo/pull/16490

   ## What is the purpose of the change?
   
   Fixes #16489.
   
   Kotlin's `object` (and `data object`) declares a singleton: the compiler 
emits a final class with a
   **private** constructor and a single `public static final INSTANCE`, and the 
language guarantees no
   other instance exists. The default bean deserializers do not honour that — 
they allocate a fresh
   instance, bypassing the private constructor, and populate its fields. Dubbo 
therefore produces an
   instance the language says cannot exist.
   
   For a plain `object`, whose `equals` is inherited identity comparison, 
losing identity also loses
   equality. Kotlin's `when` compares with `==`, so dispatch silently changes:
   
   ```kotlin
   sealed interface Reply
   object NotFound : Reply
   
   when (service.lookup(id)) {
       NotFound -> handleMissing()   // never runs after an RPC hop
       else     -> somethingElse()   // runs instead
   }
   ```
   
   No exception and no log line — only the wrong code path.
   
   Hessian2 already solves this for the Java equivalent: `EnumDeserializer` 
writes the constant's name
   and resolves the real JVM constant, which is why an enum survives a round 
trip with `===` intact.
   This change applies the same approach to Kotlin object declarations.
   
   | Construct | before | after |
   |---|---|---|
   | `object` | `===` ✗, `==` ✗, **wrong `when` branch** | `===` ✓, `==` ✓, 
correct branch |
   | `data object` | `===` ✗, `==` ✓, branch ok | `===` ✓ |
   | `enum` | `===` ✓ | unchanged |
   | `data class` | `===` ✗, value semantics | **unchanged** |
   
   The `data class` row not moving is deliberate: it has no singleton claim and 
must keep value
   semantics. A test asserts the Java-singleton case is likewise untouched.
   
   ### How
   
   `KotlinObjectDeserializer` mirrors `EnumDeserializer`: it reads the 
serialized field data so the
   stream position stays correct, discards it, and returns the declared 
`INSTANCE`. It is wired into
   `Hessian2SerializerFactory#getDefaultDeserializer` beside the existing 
special case for Java
   records, so no change to `dubbo-hessian-lite` is required.
   
   **Discarding transmitted state is deliberate.** An enum constant's fields 
are not restored from the
   wire either, and the alternative — populating the singleton's fields — would 
mutate shared global
   state on every call. The identity guarantee is the property being preserved.
   
   **Detection is gated on the `kotlin.Metadata` annotation**, matched by 
fully-qualified name through
   `Class#getAnnotations`, so Dubbo needs no dependency on kotlin-stdlib in 
main code. Requiring the
   annotation rather than detecting the shape structurally (static final 
`INSTANCE` of the class's own
   type plus a private constructor) keeps the behaviour change scoped to 
Kotlin: a hand-written Java
   singleton of identical shape continues to deserialize to a new instance. 
Dropping the gate would
   silently change behaviour for existing Java users, so 
`testJavaSingletonBehaviourIsUnchanged` pins
   it down — removing the gate leaves 809 of 810 tests in the module green and 
fails only that one.
   
   ### Tests
   
   `KotlinObjectDeserializerTest` covers both directions:
   
   - a Kotlin object declaration deserializes to its declared `INSTANCE`
   - an ordinary Java singleton still deserializes to a new instance
   
   The Kotlin fixture is written in **Java**, annotated `@kotlin.Metadata` and 
shaped exactly as the
   Kotlin compiler emits, so no Kotlin compiler enters the build; the detection 
only inspects those
   bytecode features. `kotlin-stdlib` is added at **test scope only**, purely 
to supply that
   annotation. If you would rather avoid the test dependency, the annotation 
can be stubbed in test
   sources instead — happy to switch.
   
   ### Verification
   
   - the reproducer in the issue goes from `>>> FELL THROUGH TO else <<<` to 
`matched NotFoundObject`
   - the new test fails without the change (two distinct instances), so it is 
not vacuous
   - 810 tests in `dubbo-serialization-hessian2`, 806 in 
`dubbo-serialization-fastjson2` (as a control
     on the other serializer) and 155 in `dubbo-rpc-api` all pass
   - full clean reactor build: 114 modules, 0 failures
   - `mvn spotless:check` passes project-wide
   
   ## Checklist
   - [x] Make sure there is a 
[GitHub_issue](https://github.com/apache/dubbo/issues) field for the change. — 
#16489
   - [x] Write a pull request description that is detailed enough to understand 
what the pull request does, how, and why.
   - [x] Write necessary unit-test to verify your logic correction. If the new 
feature or significant change is committed, please remember to add sample in 
[dubbo samples](https://github.com/apache/dubbo-samples) project. — 
`KotlinObjectDeserializerTest`, covering the fixed behaviour and the unchanged 
Java-singleton behaviour.
   - [x] Make sure gitHub actions can pass. [Why the workflow is failing and 
how to fix it?](../CONTRIBUTING.md)
   


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


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to