问题描述
JSON.parseObject(String) 解析含超长字符串字段的 JSON 时,JSONReaderUTF16.readString() 中实例级重用缓冲区 strBuf 未做容量检查,导致 System.arraycopy 越界抛出 ArrayIndexOutOfBoundsException。
环境信息
- OS信息: macOS 14.2 (M1 pro)
- JDK信息: Openjdk 21
- 版本信息: Fastjson2 2.0.61
重现步骤
- 构造如下 JSON,其中包含两个关键字段:
attachment:值为含 \" 转义的短字符串(在 readString() 中首次分配 strBuf = char[512])
noticeDetails:值为超长富文本(>2000字符)且中间含 \" 转义
- 调用
JSON.parseObject(jsonString) 解析
- 解析到
noticeDetails 字段时,stroff=2328 > strBuf.length=512,System.arraycopy 越界
// 测试代码
public class Test {
public static void main(String[] args) throws Exception {
String jsonString = new String(Files.readAllBytes(Paths.get("/path/to/json内容.json")));
JSONObject jsonObject = JSON.parseObject(jsonString);
System.out.println(jsonObject);
}
}
测试 JSON 结构:
{
"attachment" : "{\"files\":[{\"code\":\"200\",\"contentType\":\"application/msword\",\"id\":\"***ENCRYPTED_ID***\",\"name\":\"3岁以下婴幼儿健康养育照护指南(试行).doc\",\"url\":\"v1/file-store/download/***ENCRYPTED***\",\"size\":\"***ENCRYPTED***\"}],\"notes\":\"\",\"attachmentItemMapping\":\"\"}",
"noticeDetails" : "<p>***脱敏-机构名称***资产管***有限公司(以下简称“招租方”)对:***脱敏-城市***市城投江南新城2#楼(***脱敏-城市***市城投)【 10层 ***脱敏-面积***㎡ ***脱敏-城市***市城投(延时的除外)在阿里资产平台进行公开进行,现就有关事项公告如下:<br>注:2025年4月7日10时起至2025年4月8日10时止(延时的除外)期间,此竞价入口为唯一指定入口!通过其他竞价平台报名或参与竞价均视为无效操作。<br>一、招租房产:***脱敏-城市***市江南新城华创产业园2#楼(人才公寓)【 10层 ***脱敏-面积***㎡ 】 、【 11层***脱敏-面积***㎡ 】房屋租赁。<br>起租价:***脱敏-金额***元/年,保证金:***脱敏-金额***元,加价幅度:***脱敏-金额***元或整倍数,租赁期限:***脱敏-年限***年 ,租金支付方式:一年一付,履约保证金:***脱敏-金额***元,房产用途:以《租赁合同》为准,是否可以联合承租:否。<br>二、房产概况:***脱敏-城市***市江南新城华创产业园2#楼(人才公寓)【 10层 ***脱敏-面积***㎡ 】 、【 11层***脱敏-面积***㎡ 】房屋。房屋建筑面积:***脱敏-面积***㎡,权利性质:国有,用途:其他,房屋结构:混合,租赁情况:闲置,抵押情况:未知。<br>三、特别告知:<br>1.房屋按现状出租,出租前的水、电、物业、包烧等一切费用由招租方当场结清,承租期间的水、电、包烧等一切费用由承租人承担并按实结算。target=\"_blank\">"
}
期待的正确结果
正常解析完成,不抛出异常。
相关日志输出
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: arraycopy: last destination index 2328 out of bounds for char[512]
at java.base/java.lang.System.arraycopy(Native Method)
at com.alibaba.fastjson2.JSONReaderUTF16.readString(JSONReaderUTF16.java:3259)
at com.alibaba.fastjson2.JSONReader.read(JSONReader.java:3544)
at com.alibaba.fastjson2.JSON.parseObject(JSON.java:476)
at com.bosssoft.asset.Test.main(Test.java:18)
根因分析
JSONReaderUTF16.readString() 第 3254-3259 行:
char[] strBuf = this.strBuf;
if (strBuf == null) {
strBuf = new char[stroff + 512];
this.strBuf = strBuf;
}
System.arraycopy(chars, start, strBuf, 0, stroff); // 缺少容量校验
strBuf 是实例级重用缓冲区。当处理 attachment 这样含 \ 的短字段时,stroff 很小(如 0),分配 char[512]。后续处理 noticeDetails 这样含 \ 的超长字段时,快速扫描已累积 stroff=2328,但 strBuf 非 null 不会重新分配,arraycopy 直接将 2328 个字符写入 char[512] 导致越界。
修复方案:在 System.arraycopy 前增加 strBuf 容量检查:
char[] strBuf = this.strBuf;
if (strBuf == null || stroff > strBuf.length) {
strBuf = new char[Math.max(stroff + 512, strBuf.length * 2)];
this.strBuf = strBuf;
}
System.arraycopy(chars, start, strBuf, 0, stroff);
附加信息
- 使用
JSON.parseObject(byte[])(如 Files.readAllBytes)可绕过此问题,因 byte 路径使用 JSONReaderUTF8,不受 JSONReaderUTF16.strBuf 影响
问题描述
JSON.parseObject(String)解析含超长字符串字段的 JSON 时,JSONReaderUTF16.readString()中实例级重用缓冲区strBuf未做容量检查,导致System.arraycopy越界抛出ArrayIndexOutOfBoundsException。环境信息
重现步骤
attachment:值为含\"转义的短字符串(在readString()中首次分配strBuf = char[512])noticeDetails:值为超长富文本(>2000字符)且中间含\"转义JSON.parseObject(jsonString)解析noticeDetails字段时,stroff=2328 > strBuf.length=512,System.arraycopy越界测试 JSON 结构:
{ "attachment" : "{\"files\":[{\"code\":\"200\",\"contentType\":\"application/msword\",\"id\":\"***ENCRYPTED_ID***\",\"name\":\"3岁以下婴幼儿健康养育照护指南(试行).doc\",\"url\":\"v1/file-store/download/***ENCRYPTED***\",\"size\":\"***ENCRYPTED***\"}],\"notes\":\"\",\"attachmentItemMapping\":\"\"}", "noticeDetails" : "<p>***脱敏-机构名称***资产管***有限公司(以下简称“招租方”)对:***脱敏-城市***市城投江南新城2#楼(***脱敏-城市***市城投)【 10层 ***脱敏-面积***㎡ ***脱敏-城市***市城投(延时的除外)在阿里资产平台进行公开进行,现就有关事项公告如下:<br>注:2025年4月7日10时起至2025年4月8日10时止(延时的除外)期间,此竞价入口为唯一指定入口!通过其他竞价平台报名或参与竞价均视为无效操作。<br>一、招租房产:***脱敏-城市***市江南新城华创产业园2#楼(人才公寓)【 10层 ***脱敏-面积***㎡ 】 、【 11层***脱敏-面积***㎡ 】房屋租赁。<br>起租价:***脱敏-金额***元/年,保证金:***脱敏-金额***元,加价幅度:***脱敏-金额***元或整倍数,租赁期限:***脱敏-年限***年 ,租金支付方式:一年一付,履约保证金:***脱敏-金额***元,房产用途:以《租赁合同》为准,是否可以联合承租:否。<br>二、房产概况:***脱敏-城市***市江南新城华创产业园2#楼(人才公寓)【 10层 ***脱敏-面积***㎡ 】 、【 11层***脱敏-面积***㎡ 】房屋。房屋建筑面积:***脱敏-面积***㎡,权利性质:国有,用途:其他,房屋结构:混合,租赁情况:闲置,抵押情况:未知。<br>三、特别告知:<br>1.房屋按现状出租,出租前的水、电、物业、包烧等一切费用由招租方当场结清,承租期间的水、电、包烧等一切费用由承租人承担并按实结算。target=\"_blank\">" }期待的正确结果
正常解析完成,不抛出异常。
相关日志输出
根因分析
JSONReaderUTF16.readString()第 3254-3259 行:strBuf是实例级重用缓冲区。当处理attachment这样含\的短字段时,stroff很小(如 0),分配char[512]。后续处理noticeDetails这样含\的超长字段时,快速扫描已累积stroff=2328,但strBuf非 null 不会重新分配,arraycopy直接将 2328 个字符写入char[512]导致越界。修复方案:在
System.arraycopy前增加strBuf容量检查:附加信息
JSON.parseObject(byte[])(如Files.readAllBytes)可绕过此问题,因byte路径使用JSONReaderUTF8,不受JSONReaderUTF16.strBuf影响