Skip to content

fix: preserve nested generic element types during hessian2 deserialization (#16440) [3.3] - #16441

Open
waterWang wants to merge 1 commit into
apache:3.3from
waterWang:fix/nested-generic-16440
Open

fix: preserve nested generic element types during hessian2 deserialization (#16440) [3.3]#16441
waterWang wants to merge 1 commit into
apache:3.3from
waterWang:fix/nested-generic-16440

Conversation

@waterWang

Copy link
Copy Markdown

What is the purpose of the change?

Fixes #16440 — hessian2 deserialization loses Byte/Short/Float element types in nested generic collections (List<List<Byte>>, Map<String, List<Byte>>), widening them to Integer/Double on the provider side and causing ClassCastException on typed access.

Root cause

hessian2 encodes Byte/Short/Integer all as int and Float/Double as double on the wire, so narrow element types can only be restored from the declared generic type. Two problems combined:

  1. Request arguments were decoded (DecodeableRpcInvocation.drawArgs) with only the erased Class, discarding the generic parameter types entirely.
  2. Even when a generic Type was available, Hessian2ObjectInput.readObject(Class, Type) ignored it, and hessian-lite's expectedTypes mechanism only handles a single level — nested element types (e.g. the Byte inside Map<String, List<Byte>>) are still widened.

How this is fixed

  • MethodDescriptor/ReflectionMethodDescriptor: expose getGenericParameterTypes().
  • DecodeableRpcInvocation: capture the generic parameter types when looking up the method and pass them to ObjectInput.readObject(Class, Type).
  • Hessian2ObjectInput.readObject(Class, Type): when the declared type is a parameterized collection/map containing narrow wrapper types (Byte/Short/Float/Character), read the object with the erased type and recursively narrow numeric elements to the declared generic element types. Types without narrowable elements (e.g. List<String>) keep the original native path.

Verification

  • New unit test testReadObjectWithNestedGenericType in Hessian2SerializationTest covering Map<String, List<Byte>>, List<List<Byte>>, Map<String, List<Float>>, simple List<Byte> and an untouched List<String>.
  • mvn -pl dubbo-serialization/dubbo-serialization-hessian2 -am test — all 809 tests pass (incl. 798 TypeMatchTest).
  • dubbo-common and dubbo-rpc/dubbo-rpc-dubbo compile.

Checklist

  • Make sure there is a GitHub_issue field for the change.
  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
  • Write necessary unit-test to verify your logic correction.
  • Make sure gitHub actions can pass.

@codecov-commenter

codecov-commenter commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 57.57576% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.92%. Comparing base (5553cb7) to head (268542e).

Files with missing lines Patch % Lines
...common/serialize/hessian2/Hessian2ObjectInput.java 54.38% 14 Missing and 12 partials ⚠️
...a/org/apache/dubbo/rpc/model/MethodDescriptor.java 0.00% 1 Missing ⚠️
...bo/rpc/protocol/dubbo/DecodeableRpcInvocation.java 83.33% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##                3.3   #16441      +/-   ##
============================================
- Coverage     60.90%   58.92%   -1.98%     
+ Complexity    11765       15   -11750     
============================================
  Files          1953     1953              
  Lines         89271    89335      +64     
  Branches      13473    13497      +24     
============================================
- Hits          54367    52641    -1726     
- Misses        29321    30986    +1665     
- Partials       5583     5708     +125     
Flag Coverage Δ
integration-tests-java21 ?
integration-tests-java8 ?
samples-tests-java21 ?
samples-tests-java8 ?
unit-tests-java11 59.17% <57.57%> (+<0.01%) ⬆️
unit-tests-java17 58.62% <57.57%> (-0.03%) ⬇️
unit-tests-java21 58.63% <57.57%> (-0.01%) ⬇️
unit-tests-java25 58.59% <57.57%> (-0.02%) ⬇️
unit-tests-java8 59.17% <57.57%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…hessian2 deserialization (apache#16440)

hessian2 encodes Byte/Short/Integer all as int and Float/Double as double on
the wire, so the narrow element types of generic collections can only be
restored from the declared generic type. On the provider side the request
arguments were decoded with only the erased Class, so nested generics such as
Map<String, List<Byte>> or List<List<Byte>> lost the inner element type and
deserialized as Integer/Double, causing ClassCastException on typed access.

This change:
- exposes the generic parameter types on MethodDescriptor/
  ReflectionMethodDescriptor;
- passes the generic parameter type through DecodeableRpcInvocation when
  decoding request arguments;
- makes Hessian2ObjectInput.readObject(Class, Type) recursively narrow numeric
  elements (Byte/Short/Float/Character) inside nested generic collections to
  match the declared generic type.

Signed-off-by: waterWang <waterWang@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] hessian2 loses Byte/Short/Float element types in nested generic collections (List<List<Byte>>, Map<String, List<Byte>>)

2 participants