java-topology/test/jdk/sun/nio/cs/TestEncoderReplaceUTF16.java
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

205 lines
7.7 KiB
Java

/*
* Copyright (c) 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import sun.nio.cs.ArrayEncoder;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*
* @test
* @bug 8364365
* @summary Verifies `CodingErrorAction.REPLACE` behaviour of all available
* character set encoders while encoding a UTF-16 character
* @modules java.base/jdk.internal.access
* java.base/sun.nio.cs
* @build TestEncoderReplaceLatin1
* @run junit/timeout=40 TestEncoderReplaceUTF16
* @run junit/timeout=40/othervm -XX:-CompactStrings TestEncoderReplaceUTF16
*/
class TestEncoderReplaceUTF16 {
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
/**
* Character sets known to be absent of non-Latin-1 {@linkplain CoderResult#isUnmappable() unmappable} characters.
*/
private static final Set<String> CHARSETS_WITHOUT_UNMAPPABLE = Set.of(
"CESU-8",
"EUC-JP",
"GB18030",
"ISO-2022-JP",
"ISO-2022-JP-2",
"ISO-2022-KR",
"ISO-8859-1",
"US-ASCII",
"UTF-16",
"UTF-16BE",
"UTF-16LE",
"UTF-32",
"UTF-32BE",
"UTF-32LE",
"UTF-8",
"x-euc-jp-linux",
"x-EUC-TW",
"x-eucJP-Open",
"x-IBM29626C",
"x-IBM33722",
"x-IBM964",
"x-ISCII91",
"x-ISO-2022-CN-CNS",
"x-ISO-2022-CN-GB",
"x-MS932_0213",
"x-SJIS_0213",
"x-UTF-16LE-BOM",
"X-UTF-32BE-BOM",
"X-UTF-32LE-BOM",
"x-windows-50220",
"x-windows-50221",
"x-windows-iso2022jp");
@ParameterizedTest
@MethodSource("TestEncoderReplaceLatin1#charsets")
void testEncoderReplace(Charset charset) {
// Create an encoder
CharsetEncoder encoder = createEncoder(charset);
if (encoder == null) {
return;
}
// Find an unmappable character to test the `REPLACE` action.
char[] unmappable = findUnmappableNonLatin1(encoder);
if (unmappable == null) {
return;
}
// Configure the `REPLACE` action
byte[] unmappableUTF16Bytes = utf16Bytes(unmappable);
byte[] replacement = TestEncoderReplaceLatin1.findCustomReplacement(encoder, unmappableUTF16Bytes);
if (replacement == null) {
return;
}
encoder.onUnmappableCharacter(CodingErrorAction.REPLACE).replaceWith(replacement);
// Verify the replacement
System.err.println("Verifying replacement... " + Map.of(
"unmappable", TestEncoderReplaceLatin1.prettyPrintChars(unmappable),
"unmappableUTF16Bytes", TestEncoderReplaceLatin1.prettyPrintBytes(unmappableUTF16Bytes),
"replacement", TestEncoderReplaceLatin1.prettyPrintBytes(replacement)));
TestEncoderReplaceLatin1.testCharsetEncoderReplace(encoder, unmappable, replacement);
testArrayEncoderUTF16Replace(encoder, unmappableUTF16Bytes, replacement);
}
private static CharsetEncoder createEncoder(Charset charset) {
try {
return charset.newEncoder();
} catch (UnsupportedOperationException _) {
System.err.println("Could not create the character encoder!");
}
return null;
}
/**
* Finds an {@linkplain CoderResult#isUnmappable() unmappable} non-Latin-1 {@code char[]} for the given encoder.
*/
private static char[] findUnmappableNonLatin1(CharsetEncoder encoder) {
// Fast-path for characters sets known to be absent of unmappable non-Latin-1 characters
if (CHARSETS_WITHOUT_UNMAPPABLE.contains(encoder.charset().name())) {
System.err.println("Character set is known to be absent of unmappable non-Latin-1 characters!");
return null;
}
// Try to find a single-`char` unmappable
for (int i = 0xFF; i <= 0xFFFF; i++) {
char c = (char) i;
// Skip the surrogate, as a single dangling surrogate `char` should
// trigger a "malformed" error, instead of "unmappable"
if (Character.isSurrogate(c)) {
continue;
}
boolean unmappable = !encoder.canEncode(c);
if (unmappable) {
return new char[]{c};
}
}
// Try to find a double-`char` (i.e., surrogate pair) unmappable
int[] nonBmpRange = {0x10000, 0x10FFFF};
for (int i = nonBmpRange[0]; i < nonBmpRange[1]; i++) {
char[] cs = Character.toChars(i);
if (!encoder.canEncode(new String(cs)))
return cs;
}
System.err.println("Could not find an unmappable character!");
return null;
}
private static byte[] utf16Bytes(char[] cs) {
int sl = cs.length;
byte[] sa = new byte[sl << 1];
for (int i = 0; i < sl; i++) {
JLA.uncheckedPutCharUTF16(sa, i, cs[i]);
}
return sa;
}
/**
* Verifies {@linkplain CoderResult#isUnmappable() unmappable} character
* {@linkplain CodingErrorAction#REPLACE replacement} using {@link
* ArrayEncoder#encodeFromUTF16(byte[], int, int, byte[], int)
* ArrayEncoder::encodeFromUTF16}.
*/
private static void testArrayEncoderUTF16Replace(CharsetEncoder encoder, byte[] unmappableUTF16Bytes, byte[] replacement) {
if (!(encoder instanceof ArrayEncoder arrayEncoder)) {
System.err.println("Encoder is not of type `ArrayEncoder`, skipping the `ArrayEncoder::encodeFromUTF16` test.");
return;
}
byte[] da = new byte[replacement.length];
int dp = arrayEncoder.encodeFromUTF16(unmappableUTF16Bytes, 0, unmappableUTF16Bytes.length >>> 1, da, 0);
assertTrue(dp == replacement.length && Arrays.equals(da, replacement), () -> {
Object context = Map.of(
"dp", dp,
"da", TestEncoderReplaceLatin1.prettyPrintBytes(da),
"unmappableUTF16Bytes", TestEncoderReplaceLatin1.prettyPrintBytes(unmappableUTF16Bytes),
"replacement", TestEncoderReplaceLatin1.prettyPrintBytes(replacement));
return "Unexpected `ArrayEncoder::encodeFromUTF16` output! " + context;
});
}
}