From 9139e94c211ec0b19b301b4dbfbb59cbcecc6aad Mon Sep 17 00:00:00 2001 From: aizu-m Date: Tue, 16 Jun 2026 15:56:49 +0530 Subject: [PATCH] fix out-of-bounds chunking in emitCommentText and emitPiText --- .../org/apache/xmlbeans/impl/store/Saver.java | 8 +-- .../checkin/SaveOptimizeForSpeedTest.java | 66 +++++++++++++++++++ 2 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 src/test/java/misc/checkin/SaveOptimizeForSpeedTest.java diff --git a/src/main/java/org/apache/xmlbeans/impl/store/Saver.java b/src/main/java/org/apache/xmlbeans/impl/store/Saver.java index 30307954e..bc842c223 100755 --- a/src/main/java/org/apache/xmlbeans/impl/store/Saver.java +++ b/src/main/java/org/apache/xmlbeans/impl/store/Saver.java @@ -2070,8 +2070,8 @@ protected void emitPiText(SaveCur c) { int off = c._offSrc; int index = 0; while (index < cch) { - int indexLimit = index + 512 > cch ? cch : 512; - CharUtil.getChars(_buf, 0, src, off + index, indexLimit); + int indexLimit = Math.min(index + 512, cch); + CharUtil.getChars(_buf, 0, src, off + index, indexLimit - index); entitizeAndWritePIText(indexLimit - index); index = indexLimit; } @@ -2085,8 +2085,8 @@ protected void emitCommentText(SaveCur c) { int off = c._offSrc; int index = 0; while (index < cch) { - int indexLimit = index + 512 > cch ? cch : 512; - CharUtil.getChars(_buf, 0, src, off + index, indexLimit); + int indexLimit = Math.min(index + 512, cch); + CharUtil.getChars(_buf, 0, src, off + index, indexLimit - index); entitizeAndWriteCommentText(indexLimit - index); index = indexLimit; } diff --git a/src/test/java/misc/checkin/SaveOptimizeForSpeedTest.java b/src/test/java/misc/checkin/SaveOptimizeForSpeedTest.java new file mode 100644 index 000000000..308221bf6 --- /dev/null +++ b/src/test/java/misc/checkin/SaveOptimizeForSpeedTest.java @@ -0,0 +1,66 @@ +/* Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package misc.checkin; + +import org.apache.xmlbeans.XmlObject; +import org.apache.xmlbeans.XmlOptions; +import org.junit.jupiter.api.Test; + +import java.io.StringWriter; +import java.time.Duration; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SaveOptimizeForSpeedTest { + + // longer than the 512 char chunk OptimizedForSpeedSaver emits comment/pi text in + private static final String LONG = repeat('a', 1500); + + private static String repeat(char c, int n) { + char[] a = new char[n]; + Arrays.fill(a, c); + return new String(a); + } + + private static String saveForSpeed(XmlObject o) throws Exception { + XmlOptions opts = new XmlOptions(); + opts.setSaveOptimizeForSpeed(true); + StringWriter sw = new StringWriter(); + o.save(sw, opts); + return sw.toString(); + } + + @Test + void testLongCommentSavesForSpeed() throws Exception { + XmlObject o = XmlObject.Factory.parse(""); + String out = saveForSpeed(o); + XmlObject.Factory.parse(out); + assertTrue(out.contains(LONG)); + } + + @Test + void testLongProcInstSavesForSpeed() throws Exception { + // pi text of 1024 chars or more spins forever before the fix + String out = assertTimeoutPreemptively(Duration.ofSeconds(30), () -> { + XmlObject o = XmlObject.Factory.parse(""); + return saveForSpeed(o); + }); + XmlObject.Factory.parse(out); + assertTrue(out.contains(LONG)); + } +}