Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] CSVWriter 写入CSV数据超过 65536 个字节时报错 #2848

Closed
CodePlayer opened this issue Aug 1, 2024 · 9 comments
Closed

[BUG] CSVWriter 写入CSV数据超过 65536 个字节时报错 #2848

CodePlayer opened this issue Aug 1, 2024 · 9 comments
Labels
bug Something isn't working fixed
Milestone

Comments

@CodePlayer
Copy link
Contributor

问题描述

我们使用 Fastjson2 用于生成 CSV 格式的数据文件。
写入少量数据时,一切表现正常。
然而,当写入超过 65536 个字节的数据时,就会触发如下数组越界异常 。

java.lang.ArrayIndexOutOfBoundsException: Index 65536 out of bounds for length 65536
	at com.alibaba.fastjson2.support.csv.CSVWriterUTF8.writeComma(CSVWriterUTF8.java:52)
	at com.alibaba.fastjson2.support.csv.CSVWriter.writeLine(CSVWriter.java:149)

通过异常堆栈信息得知报错代码位置如下:

public void writeComma() {
if (off + 1 == bytes.length) {
flush();
}
bytes[off++] = ',';
}

如果之前 CSVWriterUTF8.off 已经是 65536,本次再调用 writeComma()方法时,由于 bytes.length 固定是 65536,并不满足 if( off + 1 == bytes.length )if 条件,因此触发越界异常。

此外,我们恰好在该数据量边界写入的是字符串,也得到了一个类似的异常:

java.lang.StringIndexOutOfBoundsException: offset 65532, count 5, length 65536
	at java.base/java.lang.String.checkBoundsOffCount(String.java:4591)
	at java.base/java.lang.String.getBytes(String.java:1734)

环境信息

请填写以下信息:

  • OS信息: CentOS 8.2
  • JDK信息: Openjdk 17
  • 版本信息:Fastjson2 2.0.52

期待的正确结果

正常写入,不再报错。

@CodePlayer CodePlayer added the bug Something isn't working label Aug 1, 2024
@CodePlayer CodePlayer changed the title [BUG] CSVWriter 写入CSV数据超过 65535 个字节时报错 [BUG] CSVWriter 写入CSV数据超过 65536 个字节时报错 Aug 1, 2024
@wenshao
Copy link
Member

wenshao commented Aug 2, 2024

堆栈信息可以发一下么?

@CodePlayer
Copy link
Contributor Author

CodePlayer commented Aug 7, 2024

堆栈信息可以发一下么?

简单的堆栈信息就是我上面发的,线上环境的堆栈信息在记录日志时做了精简,只会显示最近2行 + 本应用包名开头的,所以暂时没有完整的堆栈信息。

我们目前是临时通过 每 write 100 行,就手动调用一次 writer.flush() 的方式来规避该 bug。

@CodePlayer
Copy link
Contributor Author

CodePlayer commented Aug 7, 2024

通过上述简单关键堆栈信息里的 报错文件名称 和 行数,再结合报错异常分析,也能定位到具体的问题。

在这里,把 if (off + 1 == bytes.length) 改为 if (off >= bytes.length) 应该可以解决该问题。

@wenshao
Copy link
Member

wenshao commented Aug 7, 2024

如果这里有问题,应该是很多个地方都会有问题,应该是某个地方的offset计算错了,你本地方便做调试么?看调用这个之前是什么操作,最后一列是什么类型?

@CodePlayer
Copy link
Contributor Author

CodePlayer commented Aug 13, 2024

try (CSVWriter writer = CSVWriter.of()) {
	writer.writeValue("1".repeat(65531));
	writer.writeComma();
	writer.writeValue(new BigDecimal("1.00"));
	writer.writeComma(); // java.lang.ArrayIndexOutOfBoundsException: Index 65536 out of bounds for length 65536
}

前几天比较忙,今天调试了一下,发现 write BigDecimal 刚好达到 65536 时,内部并没有 flush(),再次调用其他方法就会报错 。
以上是复现该问题的简单模拟代码,CSVWriterUTF8CSVWriterUTF16 类似的其他方法也可以一并检查一下,如果某个 write 方法里面没有 flush() 调用,就可能存在该风险。

@wenshao
Copy link
Member

wenshao commented Aug 13, 2024

https://oss.sonatype.org/content/repositories/snapshots/com/alibaba/fastjson2/fastjson2/2.0.53-SNAPSHOT/
问题已修复,帮忙用2.0.53-SNAPSHOT帮忙验证,2.0.53版本预计在月底发布

@wenshao
Copy link
Member

wenshao commented Sep 16, 2024

@wenshao wenshao closed this as completed Sep 16, 2024
@CodePlayer
Copy link
Contributor Author

@wenshao 用新版本测试了下,writeBigDecimal 的问题已经解决了,不过又发现了一个类似的 bug。

OutputStreamWriter out = new OutputStreamWriter(new ByteArrayOutputStream(), StandardCharsets.UTF_8);
try (CSVWriter writer = CSVWriter.of(out)) {
	writer.writeValue("1".repeat(65534));
	writer.writeComma();
	writer.writeString("123"); // java.lang.StringIndexOutOfBoundsException: offset 65535, count 3, length 65536
}

if (escapeCount == 0) {
str.getChars(0, str.length(), chars, off);
off += str.length();
return;
}
if (off + 2 + str.length() + escapeCount >= chars.length) {
flush();
}

定位了下,如上所示,是 CSVWriterUTF16142 行代码处没有 预先检测数组容量 所致。

错误堆栈信息如下:

java.lang.StringIndexOutOfBoundsException: offset 65535, count 3, length 65536

	at java.base/java.lang.String.checkBoundsOffCount(String.java:4593)
	at java.base/java.lang.String.getChars(String.java:1681)
	at com.alibaba.fastjson2.support.csv.CSVWriterUTF16.writeString(CSVWriterUTF16.java:142)
	at test.FastjsonTest.test(FastjsonTest.java:56)

发现这些 bug 有些随机,必须要恰好构造出符合特定条件的数据才能出现。

@CodePlayer
Copy link
Contributor Author

@wenshao
举一反三,我分析了一下,出 bug 的地方一般可能存在两种情况:

  1. 存量部分 + 增量部分 刚好超过底层数组容量 65536,且没有预检测
  2. 增量部分 自己就超过了 65536。

我先构造一个 65535 容量的 Writer buffer,然后依次调用每一个 write*() 方法,发现还有如下方法存在类似的问题(此处以 CSVWriterUTF16 为例,CSVWriterUTF8 同理】):

OutputStreamWriter out = new OutputStreamWriter(new ByteArrayOutputStream(), StandardCharsets.UTF_8);
try (CSVWriter writer = CSVWriter.of(out)) {
	writer.writeValue("1".repeat(65534));
	writer.writeComma();
	writer.writeLocalDateTime(LocalDateTime.now()); // java.lang.ArrayIndexOutOfBoundsException: Index 65539 out of bounds for length 65536
// 【writeInstant(Instant instant) 也受此影响】
}

还有

OutputStreamWriter out = new OutputStreamWriter(new ByteArrayOutputStream(), StandardCharsets.UTF_8);
try (CSVWriter writer = CSVWriter.of(out)) {
	writer.writeValue("1".repeat(65534));
	writer.writeComma();
	writer.writeString("2".repeat(65537)); // java.lang.StringIndexOutOfBoundsException: offset 65535, count 65537, length 65536
// 【CSVWriterUTF16.writeString(byte[] utf8) 也受此影响】
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working fixed
Projects
None yet
Development

No branches or pull requests

2 participants