diff --git a/src/java.xml/share/classes/com/sun/org/apache/xml/internal/serializer/ToStream.java b/src/java.xml/share/classes/com/sun/org/apache/xml/internal/serializer/ToStream.java
index fc01f006e97..7bff17a455f 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xml/internal/serializer/ToStream.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xml/internal/serializer/ToStream.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2006, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
@@ -55,7 +55,7 @@ import org.xml.sax.SAXException;
* serializers (xml, html, text ...) that write output to a stream.
*
* @xsl.usage internal
- * @LastModified: Mar 2022
+ * @LastModified: Feb 2025
*/
abstract public class ToStream extends SerializerBase {
@@ -955,7 +955,7 @@ abstract public class ToStream extends SerializerBase {
throws IOException, SAXException
{
int status = -1;
- if (i + 1 >= end)
+ if (i + 1 >= end && m_highSurrogate == 0)
{
m_highSurrogate = c;
return status;
diff --git a/test/jaxp/javax/xml/jaxp/unittest/transform/JDK8207760.java b/test/jaxp/javax/xml/jaxp/unittest/transform/JDK8207760.java
index 00c1b8a8e25..6055fa878bc 100644
--- a/test/jaxp/javax/xml/jaxp/unittest/transform/JDK8207760.java
+++ b/test/jaxp/javax/xml/jaxp/unittest/transform/JDK8207760.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -41,10 +41,10 @@ import org.testng.annotations.DataProvider;
/*
* @test
+ * @bug 8207760 8349699
+ * @summary Verifies that a surrogate pair at the edge of a buffer is properly handled
* @library /javax/xml/jaxp/libs /javax/xml/jaxp/unittest
* @run testng/othervm transform.JDK8207760
- * @summary Verifies that a surrogate pair at the edge of a buffer is properly handled
- * @bug 8207760
*/
public class JDK8207760 {
final String xsl8207760 =
@@ -93,6 +93,14 @@ public class JDK8207760 {
"\n" +
"";
+ final String xsl8349699 = """
+
+
+
+
+
+ """;
+
@DataProvider(name = "xsls")
public Object[][] getDataBug8207760_cdata() {
return new Object[][]{
@@ -101,6 +109,117 @@ public class JDK8207760 {
};
}
+ /*
+ * Data for verifying the patch for JDK8349699
+ * @see testBug8349699
+ */
+ @DataProvider(name = "surrogatePair")
+ public Object[][] getDataFor8349699() {
+ return new Object[][]{
+ // a surrogate pair in an XML element placed anywhere in a string
+ {getXML(1024, 1024, "\uD835\uDF00"), getString(1024, 1024, "\uD835\uDF00")},
+ {getXML(1023, 1023, "\uD835\uDF00"), getString(1023, 1023, "\uD835\uDF00")},
+ {getXML(1023,0, "\uD835\uDF00"), getString(1023,0, "\uD835\uDF00")},
+ {getXML(1023,120, "\uD835\uDF00"), getString(1023,120, "\uD835\uDF00")},
+ // this is the original test as demonstrated in the bug report
+ {getXML(1017,1017, "\uD835\uDF03\uD835\uDF00\uD835\uDF00\uD835\uDF00\uD835\uDF00"),
+ getString(1017,1017, "\uD835\uDF03\uD835\uDF00\uD835\uDF00\uD835\uDF00\uD835\uDF00")},
+ {getXML(1017,0, "\uD835\uDF03\uD835\uDF00\uD835\uDF00\uD835\uDF00\uD835\uDF00"),
+ getString(1017,0, "\uD835\uDF03\uD835\uDF00\uD835\uDF00\uD835\uDF00\uD835\uDF00")},
+ {getXML(1017,120, "\uD835\uDF03\uD835\uDF00\uD835\uDF00\uD835\uDF00\uD835\uDF00"),
+ getString(1017,120, "\uD835\uDF03\uD835\uDF00\uD835\uDF00\uD835\uDF00\uD835\uDF00")},
+ };
+ }
+
+ /*
+ * Data for verifying the patch for JDK8349699
+ * @see testBug8349699N
+ */
+ @DataProvider(name = "invalidSurrogatePair")
+ public Object[][] getDataFor8349699N() {
+ return new Object[][]{
+ // invalid pair: high/high
+ {getXML(1024, 1024, "\uD835\uD835")},
+ {getXML(1023, 1023, "\uD835\uD835")},
+ {getXML(1023,0, "\uD835\uD835")},
+ {getXML(1023,120, "\uD835\uD835")},
+ // invalid pair: low/low
+ {getXML(1024, 1024, "\uDF00\uDF00")},
+ {getXML(1023, 1023, "\uDF00\uDF00")},
+ {getXML(1023,0, "\uDF00\uDF00")},
+ {getXML(1023,120, "\uDF00\uDF00")},
+ // invalid pair in the original test string
+ {getXML(1017,1017, "\uD835\uDF03\uD835\uDF00\uD835\uDF00\uD835\uD835\uD835\uDF00")},
+ {getXML(1017,0, "\uD835\uDF03\uD835\uDF00\uD835\uDF00\uD835\uD835\uD835\uDF00")},
+ {getXML(1017,120, "\uD835\uDF03\uD835\uDF00\uD835\uDF00\uD835\uD835\uD835\uDF00")},
+ {getXML(1017,1017, "\uD835\uDF03\uD835\uDF00\uD835\uDF00\uDF00\uDF00\uD835\uDF00")},
+ {getXML(1017,0, "\uD835\uDF03\uD835\uDF00\uD835\uDF00\uDF00\uDF00\uD835\uDF00")},
+ {getXML(1017,120, "\uD835\uDF03\uD835\uDF00\uD835\uDF00\uDF00\uDF00\uD835\uDF00")},
+ };
+ }
+
+ /*
+ * @bug 8349699
+ * Verifies that a surrogate pair at the edge of a buffer is properly handled
+ * when serializing into a Character section.
+ */
+ @Test(dataProvider = "surrogatePair")
+ public final void testBug8349699(String xml, String expected) throws Exception {
+ Transformer t = createTransformerFromInputstream(
+ new ByteArrayInputStream(xsl8349699.getBytes(StandardCharsets.UTF_8)));
+ StringWriter sw = new StringWriter();
+ t.transform(new StreamSource(new StringReader(xml)), new StreamResult(sw));
+ Assert.assertEquals(sw.toString(), expected);
+ }
+
+ /*
+ * @bug 8349699
+ * Verifies that invalid surrogate pairs are caught.
+ */
+ @Test(dataProvider = "invalidSurrogatePair")
+ public final void testBug8349699N(String xml) throws Exception {
+ Assert.assertThrows(TransformerException.class, () -> {
+ Transformer t = createTransformerFromInputstream(
+ new ByteArrayInputStream(xsl8349699.getBytes(StandardCharsets.UTF_8)));
+ StringWriter sw = new StringWriter();
+ t.transform(new StreamSource(new StringReader(xml)), new StreamResult(sw));
+ });
+ }
+
+ /**
+ * Returns an XML with the input string inserted in a text of length 'len' at
+ * the position 'pos'.
+ * @param len the length of the text to be placed in the XML
+ * @param pos the position at which the input string will be inserted into the text
+ * @param input the input string
+ * @return an XML
+ */
+ private String getXML(int len, int pos, String input) {
+ StringBuilder sb = new StringBuilder("");
+ sb.append(getString(len, pos, input));
+ sb.append("");
+ return sb.toString();
+ }
+
+ /**
+ * Returns a text string with the input string inserted at the specified position.
+ * @param len the length of the text to be returned
+ * @param pos the position at which the input string will be inserted into the text
+ * @param input the input string
+ * @return a text string
+ */
+ private String getString(int len, int pos, String input) {
+ StringBuilder sb = new StringBuilder();
+ if (pos == 0) {
+ sb.append(input).append("x".repeat(len));
+ } else if (pos == len) {
+ sb.append("x".repeat(len)).append(input);
+ } else {
+ sb.append("x".repeat(pos)).append(input).append("x".repeat(len - pos));
+ }
+ return sb.toString();
+ }
+
/*
* @bug 8207760
* Verifies that a surrogate pair at the edge of a buffer is properly handled