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.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
|
|
@ -0,0 +1,328 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 4068067 4101150 8366401
|
||||
* @library /java/text/testlib
|
||||
* @build HexDumpReader
|
||||
* @summary Check serialization of DecimalFormatSymbols. That is, ensure the
|
||||
* behavior for each stream version is correct during de-serialization.
|
||||
* @run junit/othervm --add-opens java.base/java.text=ALL-UNNAMED DFSSerializationTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InvalidObjectException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.Currency;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class DFSSerializationTest {
|
||||
|
||||
// Test that rely on hex dump files that were written from older JDK versions
|
||||
@Nested
|
||||
class HexDumpTests {
|
||||
|
||||
@Test // See 4068067 and CDFS which is the class in the serialized hex dump
|
||||
void JDK1_1_4Test() {
|
||||
// Reconstruct a class serialized during 1.1.4 which has a DFS holder
|
||||
var cdfs = (CheckDecimalFormatSymbols) assertDoesNotThrow(
|
||||
() -> deSer("DecimalFormatSymbols.114.txt"));
|
||||
assertDoesNotThrow(cdfs::Update); // Checks getDigit call succeeds
|
||||
}
|
||||
|
||||
@Test // See 4068067
|
||||
void JDK1_4_2Test() {
|
||||
// Reconstruct a 1.4.2 DFS
|
||||
var dfs = (DecimalFormatSymbols) assertDoesNotThrow(
|
||||
() -> deSer("DecimalFormatSymbols.142.txt"));
|
||||
// Checks curr symbol is saved, and exponent separator default set
|
||||
assertEquals("E", dfs.getExponentSeparator());
|
||||
assertEquals("*SpecialCurrencySymbol*", dfs.getCurrencySymbol());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class StreamVersionTests {
|
||||
|
||||
// Ensure correct monetarySeparator and exponential field defaults
|
||||
// Reads monetary from decimal, and sets exponential to 'E'
|
||||
@Test
|
||||
void version0Test() {
|
||||
var crafted = new DFSBuilder()
|
||||
.setVer(0)
|
||||
.set("monetarySeparator", '~')
|
||||
.set("exponential", 'Z')
|
||||
.build();
|
||||
var bytes = ser(crafted);
|
||||
var dfs = assertDoesNotThrow(() -> deSer(bytes));
|
||||
// Check exponential is set to proper default 'E', not 'Z'
|
||||
assertEquals('E', readField(dfs, "exponential"));
|
||||
// Ensure that mSep is based on dSep, and is not '~'
|
||||
assertNotEquals('~', dfs.getMonetaryDecimalSeparator());
|
||||
assertEquals(dfs.getDecimalSeparator(), dfs.getMonetaryDecimalSeparator());
|
||||
}
|
||||
|
||||
// Version 1 did not have a locale field, and it defaulted to Locale.ROOT.
|
||||
// Note that other versions did allow a locale field, which was nullable.
|
||||
// E.g. see nullableLocaleTest which does not set locale when it is `null`
|
||||
@Test
|
||||
void version1Test() {
|
||||
var crafted = new DFSBuilder()
|
||||
.setVer(1)
|
||||
.set("locale", null)
|
||||
.build();
|
||||
var bytes = ser(crafted);
|
||||
var dfs = assertDoesNotThrow(() -> deSer(bytes));
|
||||
assertEquals(Locale.ROOT, dfs.getLocale());
|
||||
}
|
||||
|
||||
// Version 2 did not have an exponential separator, and created it via exponent
|
||||
// char field.
|
||||
@Test
|
||||
void version2Test() {
|
||||
var crafted = new DFSBuilder()
|
||||
.setVer(2)
|
||||
.set("exponentialSeparator", null)
|
||||
.set("exponential", '~')
|
||||
.build();
|
||||
var bytes = ser(crafted);
|
||||
var dfs = assertDoesNotThrow(() -> deSer(bytes));
|
||||
assertEquals("~", dfs.getExponentSeparator());
|
||||
}
|
||||
|
||||
// Version 3 didn't have perMillText, percentText, and minusSignText.
|
||||
// These were created from the corresponding char equivalents.
|
||||
@Test
|
||||
void version3Test() {
|
||||
var crafted = new DFSBuilder()
|
||||
.setVer(3)
|
||||
.set("perMillText", null)
|
||||
.set("percentText", null)
|
||||
.set("minusSignText", null)
|
||||
.set("perMill", '~')
|
||||
.set("percent", '~')
|
||||
.set("minusSign", '~')
|
||||
.build();
|
||||
var bytes = ser(crafted);
|
||||
var dfs = assertDoesNotThrow(() -> deSer(bytes));
|
||||
// Need to check these String fields using reflection, since they
|
||||
// are not exposed via the public API
|
||||
assertEquals("~", readField(dfs, "perMillText"));
|
||||
assertEquals("~", readField(dfs, "percentText"));
|
||||
assertEquals("~", readField(dfs, "minusSignText"));
|
||||
}
|
||||
|
||||
// Version 4 did not have monetaryGroupingSeparator. It should be based
|
||||
// off of groupingSeparator.
|
||||
@Test
|
||||
void version4Test() {
|
||||
var crafted = new DFSBuilder()
|
||||
.setVer(4)
|
||||
.set("monetaryGroupingSeparator", 'Z')
|
||||
.set("groupingSeparator", '~')
|
||||
.build();
|
||||
var bytes = ser(crafted);
|
||||
var dfs = assertDoesNotThrow(() -> deSer(bytes));
|
||||
assertEquals(dfs.getGroupingSeparator(), dfs.getMonetaryGroupingSeparator());
|
||||
}
|
||||
}
|
||||
|
||||
// Up-to-date DFS stream versions do not expect a null locale since the
|
||||
// standard DecimalFormatSymbols API forbids it. However, this was not always
|
||||
// the case and previous stream versions can contain a null locale. Thus,
|
||||
// ensure that a null locale does not cause number data loading to fail.
|
||||
@Test
|
||||
void nullableLocaleTest() {
|
||||
var bytes = ser(new DFSBuilder()
|
||||
.set("locale", null)
|
||||
.set("minusSignText", "zFoo")
|
||||
.set("minusSign", 'z') // Set so that char/String forms agree
|
||||
.build());
|
||||
var dfs = assertDoesNotThrow(() -> deSer(bytes));
|
||||
assertNull(dfs.getLocale());
|
||||
// LMS should be based off of minusSignText when locale is null
|
||||
assertEquals("zFoo", readField(dfs, "lenientMinusSigns"));
|
||||
}
|
||||
|
||||
// readObject fails when the {@code char} and {@code String} representations
|
||||
// of percent, per mille, and/or minus sign disagree.
|
||||
@Test
|
||||
void disagreeingTextTest() {
|
||||
var expected = "'char' and 'String' representations of either percent, " +
|
||||
"per mille, and/or minus sign disagree.";
|
||||
assertEquals(expected, assertThrows(InvalidObjectException.class, () ->
|
||||
deSer(ser(new DFSBuilder()
|
||||
.set("minusSignText", "Z")
|
||||
.set("minusSign", 'X')
|
||||
.build()))).getMessage());
|
||||
assertEquals(expected, assertThrows(InvalidObjectException.class, () ->
|
||||
deSer(ser(new DFSBuilder()
|
||||
.set("perMillText", "Z")
|
||||
.set("perMill", 'X')
|
||||
.build()))).getMessage());
|
||||
assertEquals(expected, assertThrows(InvalidObjectException.class, () ->
|
||||
deSer(ser(new DFSBuilder()
|
||||
.set("percentText", "Z")
|
||||
.set("percent", 'X')
|
||||
.build()))).getMessage());
|
||||
}
|
||||
|
||||
// Ensure the serial version is updated to the current after de-serialization.
|
||||
@Test
|
||||
void updatedVersionTest() {
|
||||
var bytes = ser(new DFSBuilder().setVer(-25).build());
|
||||
var dfs = assertDoesNotThrow(() -> deSer(bytes));
|
||||
assertEquals(5, readField(dfs, "serialVersionOnStream"));
|
||||
}
|
||||
|
||||
// Should set currency from 4217 code when it is valid.
|
||||
@Test
|
||||
void validIntlCurrencyTest() {
|
||||
var bytes = ser(new DFSBuilder().set("intlCurrencySymbol", "JPY").build());
|
||||
var dfs = assertDoesNotThrow(() -> deSer(bytes));
|
||||
assertEquals(Currency.getInstance("JPY"), dfs.getCurrency());
|
||||
}
|
||||
|
||||
// Should not set currency when 4217 code is invalid, it remains null.
|
||||
@Test
|
||||
void invalidIntlCurrencyTest() {
|
||||
var bytes = ser(new DFSBuilder()
|
||||
.set("intlCurrencySymbol", ">.,")
|
||||
.set("locale", Locale.JAPAN)
|
||||
.build());
|
||||
var dfs = assertDoesNotThrow(() -> deSer(bytes));
|
||||
// Can not init off invalid 4217 code, remains null
|
||||
assertNull(dfs.getCurrency());
|
||||
}
|
||||
|
||||
// Ensure the currency symbol is read properly
|
||||
@Test
|
||||
void currencySymbolTest() {
|
||||
var crafted = new DecimalFormatSymbols();
|
||||
crafted.setCurrencySymbol("*SpecialCurrencySymbol*");
|
||||
var bytes = ser(crafted);
|
||||
var dfs = assertDoesNotThrow(() -> deSer(bytes));
|
||||
assertEquals("*SpecialCurrencySymbol*", dfs.getCurrencySymbol());
|
||||
}
|
||||
|
||||
// Ensure the exponent separator is read properly
|
||||
@Test
|
||||
void exponentSeparatorTest() {
|
||||
var crafted = new DecimalFormatSymbols();
|
||||
crafted.setExponentSeparator("*SpecialExponentSeparator*");
|
||||
var bytes = ser(crafted);
|
||||
var dfs = assertDoesNotThrow(() -> deSer(bytes));
|
||||
assertEquals("*SpecialExponentSeparator*", dfs.getExponentSeparator());
|
||||
}
|
||||
|
||||
// Utilities ----
|
||||
|
||||
// Utility to serialize
|
||||
private static byte[] ser(Object obj) {
|
||||
return assertDoesNotThrow(() -> {
|
||||
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream)) {
|
||||
oos.writeObject(obj);
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
}, "Unexpected error during serialization");
|
||||
}
|
||||
|
||||
// Utility to deserialize from byte array
|
||||
private static DecimalFormatSymbols deSer(byte[] bytes) throws IOException, ClassNotFoundException {
|
||||
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
|
||||
ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream)) {
|
||||
return (DecimalFormatSymbols) ois.readObject();
|
||||
}
|
||||
}
|
||||
|
||||
// Utility to deserialize from file in hex format
|
||||
private static Object deSer(String file) throws IOException, ClassNotFoundException {
|
||||
try (InputStream stream = HexDumpReader.getStreamFromHexDump(file);
|
||||
ObjectInputStream ois = new ObjectInputStream(stream)) {
|
||||
return ois.readObject();
|
||||
}
|
||||
}
|
||||
|
||||
// Utility to read a private field
|
||||
private static Object readField(DecimalFormatSymbols dfs, String name) {
|
||||
return assertDoesNotThrow(() -> {
|
||||
var field = DecimalFormatSymbols.class.getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
return field.get(dfs);
|
||||
}, "Unexpected error during field reading");
|
||||
}
|
||||
|
||||
// Utility class to build instances of DFS via reflection
|
||||
private static class DFSBuilder {
|
||||
|
||||
private final DecimalFormatSymbols dfs;
|
||||
|
||||
private DFSBuilder() {
|
||||
dfs = new DecimalFormatSymbols();
|
||||
}
|
||||
|
||||
private DFSBuilder setVer(Object value) {
|
||||
return set("serialVersionOnStream", value);
|
||||
}
|
||||
|
||||
private DFSBuilder set(String field, Object value) {
|
||||
return assertDoesNotThrow(() -> {
|
||||
Field f = dfs.getClass().getDeclaredField(field);
|
||||
f.setAccessible(true);
|
||||
f.set(dfs, value);
|
||||
return this;
|
||||
}, "Unexpected error during reflection setting");
|
||||
}
|
||||
|
||||
private DecimalFormatSymbols build() {
|
||||
return dfs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not nested, so that it can be cast correctly for the 1.1.4 test
|
||||
class CheckDecimalFormatSymbols implements Serializable {
|
||||
DecimalFormatSymbols _decFormatSymbols = new DecimalFormatSymbols();
|
||||
public char Update()
|
||||
{
|
||||
return _decFormatSymbols.getDigit();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue