alibaba / alibaba/fastjson2

[BUG]2.0.64 + JDK8(aarch64):合法 {"flag":false} 循环 parseObject 报 syntax error : f;toJSONString 截断字符串

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

Description

### 问题描述

从 Fastjson2 2.0.52 升级到 2.0.64 后,在 Oracle JDK 8 + aarch64 上出现两类问题:

1. `JSON.parseObject("{\"flag\":false}", FlagBean.class)` 第一次成功,循环热起来(C2)后间歇抛 `JSONException: syntax error : f`(`JSONReaderUTF16.readBoolValue`)。2.0.64 读 `false` 走 `IOUtils.isALSE`(`UNSAFE.getLong`);加 `-Xint` 或退回 2.0.52 则 20 万次全部成功。
2. `JSON.toJSONString` 在 Bean + 嵌套 JSONObject + 中文 + 较长 `getId()` 下会丢掉字符串开头字符(例如 `PREFIX_LONG_CONSTANT_SUFFIX` 写成 `EFIX_LONG_CONSTANT_SUFFIX`)。这与 boolean 报错是同一平台上的另一条路径,下面分开给出例子。

与 https://github.com/alibaba/fastjson2/issues/7828 同类:aarch64 + JDK8 对 `char[]` 做 unaligned `Unsafe.getLong` / `putLong`。#7828 是 `writeInt3` 数字路径;这里是 boolean 读取(`isALSE`)和字符串写入(`JSONWriterUTF16JDK8UF`)。

### 环境信息

- OS信息: macOS 12.6.5 (21G531) arm64 Apple M1 8Core 16 GB
- JDK信息: Oracle JDK 1.8.0_381(Java(TM) SE Runtime Environment 1.8.0_381-b09,HotSpot 64-Bit Server VM 25.381-b09 mixed mode,os.arch=aarch64)
- 版本信息: Fastjson2 2.0.64(对照:2.0.52 正常)

### 重现步骤

**问题 1:`syntax error : f`**

1. 使用 `JSON.parseObject(String, Class)` 方法
2. 输入字面量 `{"flag":false}`(合法 JSON,不是残缺的 `f`),循环调用 200000 次
3. 第一次解析成功;C2 编译后出现 `JSONException: syntax error : f`

```java
import com.alibaba.fastjson2.JSON;

public class ReproSyntaxF {
public static class FlagBean {
public Boolean flag;
}

public static void main(String[] args) {
System.out.println("java=" + System.getProperty("java.version"));
System.out.println("os.arch=" + System.getProperty("os.arch"));
System.out.println("fastjson2=" + JSON.VERSION);

final String json = "{\"flag\":false}";
FlagBean first = JSON.parseObject(json, FlagBean.class);
System.out.println("firstParse flag=" + first.flag);

int syntaxF = 0;
int firstFailAt = -1;
for (int i = 0; i < 200000; i++) {
try {
JSON.parseObject(json, FlagBean.class);
} catch (Exception e) {
if (String.valueOf(e.getMessage()).contains("syntax error : f")) {
syntaxF++;
if (firstFailAt < 0) {
firstFailAt = i;
e.printStackTrace(System.out);
}
}
}
}
System.out.println("syntaxErrorF=" + syntaxF + "/200000");
System.out.println("firstFailAt=" + firstFailAt);
}
}
```

实测(同一台机器、循环 200000 次):

| 组合 | 结果 |
|------|------|
| 2.0.64 + C2(默认) | syntaxErrorF=759/200000,第一次失败在 i=747 |
| 2.0.64 + `-Xint` | 0/200000 |
| 2.0.52 + C2 | 0/200000 |

**问题 2:`toJSONString` 截断字符串(本例子没有 boolean,不会报 `syntax error : f`)**

1. 使用 `JSON.toJSONString` 方法
2. 输入带嵌套 `JSONObject`、中文字段、较长 `getId()` 的 Bean,循环 5000 次
3. 输出里 `id` 丢掉首字符,例如 `PREFIX_LONG_CONSTANT_SUFFIX` → `EFIX_LONG_CONSTANT_SUFFIX`

```java
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;

public class Repro {
public static class Event {
public static final String EVENT_ID = "PREFIX_LONG_CONSTANT_SUFFIX";
private String eventId = "evt-1";
private String eventType = "demo.changed";
private String source = "demo";
private String traceId = "trace-1";
private JSONObject body;
private long timestamp = 1788162120689L;

public String getId() { return EVENT_ID; }
public String getEventId() { return eventId; }
public String getEventType() { return eventType; }
public String getSource() { return source; }
public String getTraceId() { return traceId; }
public JSONObject getBody() { return body; }
public long getTimestamp() { return timestamp; }
public void setBody(JSONObject body) { this.body = body; }
}

public static void main(String[] args) {
int corrupt = 0;
String last = null;
for (int i = 0; i < 5000; i++) {
Event event = new Event();
JSONObject body = new JSONObject();
body.put("createBy", "u1");
body.put("dutyUser", "u2");
body.put("name", "任务甲");
body.put("planCode", "P-01");
body.put("planEndDate", "2026-12-31 00:00:00");
body.put("planName", "计划名");
body.put("projectCode", "PRJ-01");
body.put("sceneUser", 0);
body.put("sceneAffiliation", "部门A");
body.put("title", "项目A");
event.setBody(body);

String json = JSON.toJSONString(event);
if (!json.contains("\"sceneAffiliation\"")
|| json.contains("\u0000")
|| !json.contains(Event.EVENT_ID)) {
corrupt++;
last = json;
}
}
System.out.println("java=" + System.getProperty("java.version"));
System.out.println("fastjson2=" + JSON.VERSION);
System.out.println("corrupt=" + corrupt + "/5000");
System.out.println(last);
}
}
```

实测:`corrupt=4357/5000`。单次、字段很少时经常正确;反复 `toJSONString`(命中 `JSONWriterUTF16` 线程本地 `char[]` 缓存)才稳定损坏。

### 期待的正确结果

- 对字面量 `{"flag":false}` 循环 `parseObject` 必须稳定成功,C2 编译后也不能间歇报 `syntax error : f`
- `toJSONString` 不得截断字段名和字符串;对自身输出再 `parseObject` 必须成功
- aarch64 + JDK8 上对 `char[]` 的 unaligned Unsafe 读写应回退到对齐访问或普通数组赋值

### 相关日志输出

问题 1:

```
java=1.8.0_381
os.arch=aarch64
fastjson2=2.0.64
firstParse flag=false
syntaxErrorF=759/200000
firstFailAt=747
com.alibaba.fastjson2.JSONException: syntax error : f
at com.alibaba.fastjson2.JSONReaderUTF16.readBoolValue(JSONReaderUTF16.java:5534)
at com.alibaba.fastjson2.JSONReader.readBool(JSONReader.java:3864)
at com.alibaba.fastjson2.reader.FieldReaderBool.readFieldValue(FieldReaderBool.java:43)
at com.alibaba.fastjson2.reader.ObjectReaderAdapter.readFieldValue(ObjectReaderAdapter.java:520)
at com.alibaba.fastjson2.JSON.parseObject(JSON.java:992)
```

问题 2:

```
java=1.8.0_381
fastjson2=2.0.64
corrupt=4357/5000
{"body":{"createBy":"u1","dutyUser":"u2","name":"任务甲","planCode":"P-01","planEndDate":"2026-12-31 00:00:00","planName":"计划名","projectCode":"PRJ-01","sceneUser":0,"sceneAffiliation":"部门A","title":"项目A"},"eventId":"evt-1","eventType":"demo.changed","id":"EFIX_LONG_CONSTANT_SUFFIX","source":"demo","timestamp":1788162120689,"traceId":"trace-1"}
```

#### 附加信息

相关代码:

- `JSONReaderUTF16.readBoolValue`(2.0.64 约 5469 / 5534 行):`ch == 'f'` 后调用 `IOUtils.isALSE`
- `IOUtils.isALSE(char[])` → `getLongUnaligned` → JDK8 上就是 `UNSAFE.getLong`
- JDK8 默认写路径 `JSONWriter.of()` → `JSONWriterUTF16JDK8UF.writeString` → `JSONWriterUTF16.writeString(char[])` 的 `getLongLE` / `putLongLE`
- `IOUtils.putLongUnaligned(char[])` 在 JDK8 上就是 `UNSAFE.putLong`(JDK9 才有真正的 unaligned API)

业务侧还见过字段名被截成 `ion`、字符串里出现 `\u0000`、`illegal fieldName` / `not allow unquoted fieldName`。那些是 writer 损坏的下游解析报错;问题 1 的最小复现输入始终是完整的 `{"flag":false}`。

Contributor guide

Open the contributing guide

Research direction

Start by running the two supplied reproductions on Oracle JDK 8 aarch64, then inspect JSONReaderUTF16.readBoolValue, IOUtils.isALSE, JSONWriterUTF16JDK8UF.writeString, and the related getLongLE/putLongLE paths. Compare behavior with 2.0.52 and verify that repeated parsing stays error-free and repeated serialization preserves the full strings and produces JSON that can be parsed successfully.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.