Skip to content
New issue

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

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

Already on GitHub? # 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

https://github.com/alibaba/fastjson2/releases/tag/2.0.53
问题已修复,请用新版本

@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) 也受此影响】
}

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

No branches or pull requests

2 participants