[Bug]: NullPointerException in A2ACommonFieldMapper when a struct field has an explicit null value
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 78/100
Hướng nghiên cứu
Bắt đầu trong A2ACommonFieldMapper.structToMap và valueToObject, sau đó lần theo đường dẫn TaskMapperImpl/MessageMapperImpl được Client.getTask(...) gọi tới qua JSON-RPC. Hoàn tất khi một phản hồi chứa các trường null tường minh lồng nhau được chuyển đổi mà không xảy ra NullPointerException và các trường null vẫn được biểu diễn trong dữ liệu kết quả.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
What happened?
A2ACommonFieldMapper.structToMap throws a NullPointerException when converting a protobuf Struct that contains a field whose value is an explicit JSON null (protobuf Value.KindCase.NULL_VALUE). This reproduces on 1.3.0.Final when deserializing a Task returned from GetTask, specifically when a message part's structured data contains a null field anywhere in its (possibly nested) object.
This looks like an incomplete fix of #618: that issue fixed the empty-struct case (valueToObject now short-circuits STRUCT_VALUE with 0 fields to Collections.emptyMap() — see lines 218-224 in the current source), but the explicit-null-field case was not addressed and still NPEs.
Root Cause
valueToObject correctly returns Java null for a protobuf NULL_VALUE field:
case NULL_VALUE:
default:
return null;
But structToMap collects the struct's fields with Collectors.toMap:
default Map<String, Object> structToMap(Struct struct) {
if (struct == null || struct.getFieldsCount() == 0) {
return null;
}
return struct.getFieldsMap().entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> valueToObject(e.getValue())));
}
Collectors.toMap's default accumulator calls Map.merge, which calls Objects.requireNonNull(value) — it cannot accept a null mapped value, regardless of how deeply nested the offending field is. Any JSON object with a null field anywhere in a message's structured data part therefore breaks task/message deserialization entirely.
Steps to Reproduce
- Have a server return (via GetTask, or any response going through TaskMapperImpl/MessageMapperImpl) a message part whose structured JSON data contains an explicit null field, e.g.:
{
"data": {
"data": {
"some_optional_field": null,
"other_field": "value"
}
}
}
- Have the client call
Client.getTask(...)(JSON-RPC transport) to fetch that task. - A
NullPointerExceptionis thrown while converting the response, and the client never gets to see the actual task/message content.
Relevant log output
java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:233)
at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:180)
at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.base/java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet.lambda$entryConsumer$0(Collections.java:1778)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939)
at java.base/java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntrySetSpliterator.forEachRemaining(Collections.java:1803)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
at org.a2aproject.sdk.grpc.mapper.A2ACommonFieldMapper.structToMap(A2ACommonFieldMapper.java:170)
at org.a2aproject.sdk.grpc.mapper.A2ACommonFieldMapper.valueToObject(A2ACommonFieldMapper.java:225)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
at org.a2aproject.sdk.grpc.mapper.A2ACommonFieldMapper.valueToObject(A2ACommonFieldMapper.java:229)
at org.a2aproject.sdk.grpc.mapper.A2ACommonFieldMapper.lambda$structToMap$1(A2ACommonFieldMapper.java:170)
at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:180)
at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.base/java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet.lambda$entryConsumer$0(Collections.java:1778)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939)
at java.base/java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntrySetSpliterator.forEachRemaining(Collections.java:1803)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
at org.a2aproject.sdk.grpc.mapper.A2ACommonFieldMapper.structToMap(A2ACommonFieldMapper.java:170)
at org.a2aproject.sdk.grpc.mapper.A2ACommonFieldMapper.valueToObject(A2ACommonFieldMapper.java:225)
at org.a2aproject.sdk.grpc.mapper.A2ACommonFieldMapper.lambda$structToMap$1(A2ACommonFieldMapper.java:170)
at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:180)
at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.base/java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet.lambda$entryConsumer$0(Collections.java:1778)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939)
at java.base/java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntrySetSpliterator.forEachRemaining(Collections.java:1803)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
at org.a2aproject.sdk.grpc.mapper.A2ACommonFieldMapper.structToMap(A2ACommonFieldMapper.java:170)
at org.a2aproject.sdk.grpc.mapper.A2ACommonFieldMapper.valueToObject(A2ACommonFieldMapper.java:225)
at org.a2aproject.sdk.grpc.mapper.A2ACommonFieldMapper.lambda$structToMap$1(A2ACommonFieldMapper.java:170)
at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:180)
at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.base/java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet.lambda$entryConsumer$0(Collections.java:1778)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939)
at java.base/java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntrySetSpliterator.forEachRemaining(Collections.java:1803)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
at org.a2aproject.sdk.grpc.mapper.A2ACommonFieldMapper.structToMap(A2ACommonFieldMapper.java:170)
at org.a2aproject.sdk.grpc.mapper.A2ACommonFieldMapper.valueToObject(A2ACommonFieldMapper.java:225)
at org.a2aproject.sdk.grpc.mapper.PartMapper.fromProto(PartMapper.java:110)
at org.a2aproject.sdk.grpc.mapper.MessageMapperImpl.partListToPartArray(MessageMapperImpl.java:88)
at org.a2aproject.sdk.grpc.mapper.MessageMapperImpl.fromProto(MessageMapperImpl.java:72)
at org.a2aproject.sdk.grpc.mapper.TaskMapperImpl.messageListToMessageArray(TaskMapperImpl.java:95)
at org.a2aproject.sdk.grpc.mapper.TaskMapperImpl.fromProto(TaskMapperImpl.java:69)
at org.a2aproject.sdk.grpc.utils.ProtoUtils$FromProto.lambda$task$11(ProtoUtils.java:313)
at org.a2aproject.sdk.grpc.utils.ProtoUtils$FromProto.convert(ProtoUtils.java:198)
at org.a2aproject.sdk.grpc.utils.ProtoUtils$FromProto.task(ProtoUtils.java:313)
at org.a2aproject.sdk.grpc.utils.JSONRPCUtils.parseResponseBody(JSONRPCUtils.java:315)
at org.a2aproject.sdk.client.transport.jsonrpc.JSONRPCTransport.unmarshalResponse(JSONRPCTransport.java:369)
at org.a2aproject.sdk.client.transport.jsonrpc.JSONRPCTransport.getTask(JSONRPCTransport.java:148)
at org.a2aproject.sdk.client.Client.getTask(Client.java:371)
(application code)
Code of Conduct
- I agree to follow this project's Code of Conduct
- Ngôn ngữ chính
- Java
- Star
- 495
- Fork
- 174
- Merge trung bình
- 1 ngày 18 giờ
- Pull request đã merge (30 ngày)
- 48
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của a2aproject/a2a-java
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
a2aproject/a2a-java#1158 · 2 bình luận ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
a2aproject/a2a-java#1142 · 1 người được giao ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
a2aproject/a2a-java#1012 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
a2aproject/a2a-java#464 · 1 bình luận ·
-
sample
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
a2aproject/a2a-java#375 · 5 bình luận · 1 người được giao ·
Tất cả issue của a2aproject/a2a-java
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
infinispan/infinispan#18150 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
-
untriaged
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
opensearch-project/k-NN#3597 ·
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100