uuuyuqi opened a new pull request, #110:
URL: https://github.com/apache/dubbo-hessian-lite/pull/110

   ## Problem
   
   Reading an untyped map with an expected interface type fails when 
`SerializerFactory` selects `ObjectDeserializer` for that interface. This 
affects interfaces such as `Serializable` and `Cloneable`, not just generic 
fields.
   
   For example:
   
   ```java
   class ResultDTO<T extends Serializable> implements Serializable {
       private T module;
   }
   ```
   
   When `module` contains a `HashMap<Long, String>`, its erased field type is 
`Serializable`. The default HashMap serializer writes an untyped map (`H` in 
Hessian 2). Deserializing the field then fails with:
   
   ```text
   ObjectDeserializer: unexpected object java.lang.Long
   ```
   
   `Long` is the type of the first map key, not the underlying cause. Empty 
maps also fail, with a different error. Generics are not required to reproduce 
the issue: reading the same map directly as `Cloneable` fails as well.
   
   The problem depends on the wire representation and the selected 
deserializer; it does not mean every object or every Map implementation fails 
when read through an interface.
   
   ## Cause
   
   `ObjectDeserializer` does not override `readMap()`. When `Hessian2Input` 
encounters an untyped map, it calls the selected deserializer's map reader, 
which falls back to the error implementation in `AbstractDeserializer` instead 
of reading the map.
   
   ## Changes
   
   Implement both `readMap` overloads in `ObjectDeserializer`:
   
   - Obtain the deserializer registered for `Map.class` from the current 
input's serializer factory.
   - Delegate map reading, preserving the expected key/value type hints and 
existing map reference handling.
   - Check that a non-null result is assignable to the expected interface, 
otherwise throw a `HessianProtocolException` identifying both types.
   - Allow a registered Map deserializer to return null without causing an NPE.
   - Reject direct self-delegation to avoid infinite recursion for that 
configuration.
   
   This uses the existing Map deserialization implementation and respects 
custom Map deserializers instead of unconditionally constructing a HashMap.
   
   ## Scope
   
   This change adds **Map reading only**. It does not change serializer 
selection, wire encoding, dependencies, or versions. Existing 
Object/Map/HashMap, ordinary object, ArrayList, and null decoding paths are 
unchanged.
   
   `ObjectDeserializer.readList`, `readLengthList`, and `readObject(in, 
fields)` retain their existing unsupported behavior. The fix does not make 
arbitrary objects readable as arbitrary interfaces: non-null map results must 
implement the expected interface.
   
   ## Tests
   
   Add 16 regression tests covering:
   
   - Non-empty and empty maps in a generic field bounded by `Serializable`.
   - A `Cloneable` field.
   - Expected key/value types, including `Short` values.
   - Registered Map deserializers, a compatible custom interface, and null 
results.
   - Rejection of incompatible interfaces and direct self-delegation.
   - Hessian 2 `H` maps and `M` maps with an empty type.
   - Map self-references and shared references read through different 
interfaces.
   - Unchanged ordinary object, ArrayList, Object/Map/HashMap, and null 
behavior.
   
   Before the production fix, the new tests run against master produced 4 
passes, 2 assertion failures, and 10 errors. After the fix, all 16 pass.
   
   Full local test results (Maven 3.9.6):
   
   | JDK | Tests (including skipped) | Failures | Errors | Skipped | Result |
   | --- | ---: | ---: | ---: | ---: | --- |
   | 8 | 213 | 0 | 0 | 2 | Pass |
   | 11 | 218 | 0 | 0 | 2 | Pass |
   | 17 | 241 | 0 | 0 | 38 | Pass |
   | 21 | 241 | 0 | 0 | 38 | Pass |
   
   Skips are due to existing JDK conditions; no tests were excluded to make the 
build pass. All 16 new tests ran and passed on each listed JDK. They also pass 
on JDK 8 with `-Dcom.caucho.hessian.unsafe=false`. JDK 22 was not available 
locally.
   
   Commands, with `JAVA_HOME` set for each JDK:
   
   ```bash
   # Targeted regression tests on JDK 8
   mvn -B -o -pl java-8-test -am \
     -Dtest=ObjectDeserializerTest -Dsurefire.failIfNoSpecifiedTests=false test
   
   # Full tests on JDK 8
   mvn -B -o test
   
   # Full tests on JDK 11, 17, and 21
   mvn -B -o -Pbase test
   
   # Targeted regression tests without Unsafe on JDK 8
   mvn -B -o -pl java-8-test -am \
     -Dtest=ObjectDeserializerTest -Dsurefire.failIfNoSpecifiedTests=false \
     -Dcom.caucho.hessian.unsafe=false test
   ```
   
   The explicit `base` profile on newer JDKs includes the production module in 
the same reactor, ensuring the tests use the modified code rather than an 
existing cached artifact. These are library-level tests, not Dubbo RPC 
end-to-end tests. No artifacts were installed or deployed during local 
verification.
   


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