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]