alibaba / alibaba/fastjson2

[BUG]JSON.toJSON() 在多态字段场景复用 ObjectWriter 缓存未校验类型,导致 invoke getter method error

Open
#7,638 1 comment 0 reactions 0 assignees View on GitHub
bug
Dominant language
Java
Stars
4.4k
Forks
613
Avg merge
1d 22h
Merged PRs (30d)
6

Description

### 问题描述
在处理包含多态字段的对象时,JSON.toJSON() 存在缓存复用导致异常。
如果在同一 JVM 进程内,先对某子类实例调用了 JSON.toJSONString(),随后再对声明类型相同但实际类型不同的另一个子类实例调用 JSON.toJSON(),底层的 toJSONObject 会错误复用前者的 ObjectWriter 缓存,从而调用了不存在于当前实例的 getter 方法,抛出异常。

### 环境信息
- OS信息:Windows 11 amd64
- JDK信息:OpenJDK 21.0.10 (Temurin)
- 版本信息:Fastjson2 2.0.60

### 重现步骤
1. 构建包含多态声明字段的对象。
2. 先调用 `JSON.toJSONString(element1)` 触发底层缓存机制。
3. 再调用 `JSON.toJSON(element2)`。
4. 抛出类型不匹配异常:`invoke getter method error, attrText`。

```java
package com.example;

import com.alibaba.fastjson2.JSON;

public class BugReproduce {
public static class PropsValue {
private String commonField;
public String getCommonField() { return commonField; }
public void setCommonField(String commonField) { this.commonField = commonField; }
}

public static class TextPropsValue extends PropsValue {
private String attrText;
public String getAttrText() { return attrText; }
public void setAttrText(String attrText) { this.attrText = attrText; }
}

public static class ButtonPropsValue extends PropsValue {
private String attrButton;
public String getAttrButton() { return attrButton; }
public void setAttrButton(String attrButton) { this.attrButton = attrButton; }
}

public static class Element {
private PropsValue propsValue;
public PropsValue getPropsValue() { return propsValue; }
public void setPropsValue(PropsValue propsValue) { this.propsValue = propsValue; }
}

public static void main(String[] args) {
// 1. 构造第一个子类实例并触发缓存
TextPropsValue textValue = new TextPropsValue();
textValue.setCommonField("common");
textValue.setAttrText("hello");
Element element1 = new Element();
element1.setPropsValue(textValue);

// 走 write 路径,写入 TextPropsValue 的缓存
String json1 = JSON.toJSONString(element1);
// 备注:调用 JSON.toJSON(element1, JSONWriter.Feature.ReferenceDetection) 同样会触发后续污染

// 2. 构造第二个子类实例
ButtonPropsValue buttonValue = new ButtonPropsValue();
buttonValue.setCommonField("common");
buttonValue.setAttrButton("click");
Element element2 = new Element();
element2.setPropsValue(buttonValue);

// 3. 再次调用,触发异常
Object json2 = JSON.toJSON(element2);
}
}
```

### 期待的正确结果
预期第二次调用不抛异常。对于多态字段,应基于当前字段值的运行时真实类型选择正确的 ObjectWriter,在发现缓存绑定的类型与实际类型不符时,应当跳过该缓存并重新动态获取 ObjectWriter。

### 相关日志输出
```
Exception in thread "main" com.alibaba.fastjson2.JSONException: invoke getter method error, attrText
at com.alibaba.fastjson2.writer.FieldWriterStringMethod.getFieldValue(FieldWriterStringMethod.java:29)
at com.alibaba.fastjson2.writer.ObjectWriterAdapter.toJSONObject(ObjectWriterAdapter.java:618)
at com.alibaba.fastjson2.writer.ObjectWriterAdapter.toJSONObject(ObjectWriterAdapter.java:610)
at com.alibaba.fastjson2.writer.ObjectWriterAdapter.toJSONObject(ObjectWriterAdapter.java:691)
at com.alibaba.fastjson2.JSON.toJSON(JSON.java:4027)
at com.alibaba.fastjson2.JSON.toJSON(JSON.java:3999)
at com.example.BugReproduce.main(BugReproduce.java:55)
Caused by: java.lang.IllegalArgumentException: object is not an instance of declaring class
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.checkReceiver(DirectMethodHandleAccessor.java:197)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:99)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at com.alibaba.fastjson2.writer.FieldWriterStringMethod.getFieldValue(FieldWriterStringMethod.java:27)
... 6 more
```

#### 附加信息
*如果你还有其他需要提供的信息,可以在这里填写(可以提供截图、视频等)。*

Contributor guide

Open the contributing guide

Research direction

Start with the JSON.toJSON path in JSON.java and the cache reuse logic in ObjectWriterAdapter.java, using the supplied BugReproduce example to reproduce the failure. Inspect FieldWriterStringMethod.java and verify that a cached writer is not used for a different runtime subtype; done means the second JSON.toJSON(element2) completes without the attrText getter error.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
65/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.