This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git
The following commit(s) were added to refs/heads/master by this push:
new 0924f91d5 FastDateParser.readObject(ObjectInputStream) now validates
the same (#1692)
0924f91d5 is described below
commit 0924f91d57d8932084c44fa7a957318a57b7aa40
Author: Gary Gregory <[email protected]>
AuthorDate: Sat Jun 6 09:53:58 2026 -0400
FastDateParser.readObject(ObjectInputStream) now validates the same (#1692)
constructor invariants
Add org.apache.commons.lang3.SerializationUtils.requireNonNull(T,
String)
---
src/main/java/org/apache/commons/lang3/Range.java | 12 +-
.../apache/commons/lang3/SerializationUtils.java | 140 +++++++++++---------
.../apache/commons/lang3/time/FastDateParser.java | 18 +--
.../lang3/time/FastDateParserReadObjectTest.java | 143 +++++++++++++++++++++
4 files changed, 230 insertions(+), 83 deletions(-)
diff --git a/src/main/java/org/apache/commons/lang3/Range.java
b/src/main/java/org/apache/commons/lang3/Range.java
index 0d2165de8..0d6bb2680 100644
--- a/src/main/java/org/apache/commons/lang3/Range.java
+++ b/src/main/java/org/apache/commons/lang3/Range.java
@@ -550,15 +550,9 @@ private void readObject(final ObjectInputStream in) throws
IOException, ClassNot
if (hashCode != hash(minimum, maximum)) {
throw new InvalidObjectException("Range hashCode does not match
minimum/maximum.");
}
- if (maximum == null) {
- throw new InvalidObjectException("maximum null");
- }
- if (minimum == null) {
- throw new InvalidObjectException("minimum null");
- }
- if (comparator == null) {
- throw new InvalidObjectException("comparator null");
- }
+ SerializationUtils.requireNonNull(maximum, "maximum null");
+ SerializationUtils.requireNonNull(minimum, "minimum null");
+ SerializationUtils.requireNonNull(comparator, "comparator null");
if (comparator.compare(minimum, maximum) > 0) {
throw new InvalidObjectException("Range minimum is greater than
maximum under the comparator.");
}
diff --git a/src/main/java/org/apache/commons/lang3/SerializationUtils.java
b/src/main/java/org/apache/commons/lang3/SerializationUtils.java
index 124fcb0fc..4b1fea491 100644
--- a/src/main/java/org/apache/commons/lang3/SerializationUtils.java
+++ b/src/main/java/org/apache/commons/lang3/SerializationUtils.java
@@ -14,12 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.apache.commons.lang3;
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.ObjectStreamClass;
@@ -48,31 +50,29 @@
* </p>
*
* @see org.apache.commons.io.serialization.ValidatingObjectInputStream
+ * @see <a
href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java
Object Serialization Specification</a>
* @since 1.0
*/
public class SerializationUtils {
/**
- * Custom specialization of the standard JDK {@link ObjectInputStream}
- * that uses a custom {@link ClassLoader} to resolve a class.
- * If the specified {@link ClassLoader} is not able to resolve the class,
- * the context classloader of the current thread will be used.
- * This way, the standard deserialization work also in web-application
- * containers and application servers, no matter in which of the
- * {@link ClassLoader} the particular class that encapsulates
+ * Custom specialization of the standard JDK {@link ObjectInputStream}
that uses a custom {@link ClassLoader} to resolve a class. If the specified
+ * {@link ClassLoader} is not able to resolve the class, the context
classloader of the current thread will be used. This way, the standard
deserialization
+ * work also in web-application containers and application servers, no
matter in which of the {@link ClassLoader} the particular class that
encapsulates
* serialization/deserialization lives.
*
- * <p>For more in-depth information about the problem for which this
- * class here is a workaround, see the JIRA issue LANG-626.</p>
+ * <p>
+ * For more in-depth information about the problem for which this class
here is a workaround, see the JIRA issue LANG-626.
+ * </p>
*/
- static final class ClassLoaderAwareObjectInputStream extends
ObjectInputStream {
+ static final class ClassLoaderAwareObjectInputStream extends
ObjectInputStream {
private final ClassLoader classLoader;
/**
* Constructs a new instance.
*
- * @param in The {@link InputStream}.
+ * @param in The {@link InputStream}.
* @param classLoader classloader to use
* @throws IOException if an I/O error occurs while reading stream
header.
* @see java.io.ObjectInputStream
@@ -83,12 +83,11 @@ static final class ClassLoaderAwareObjectInputStream
extends ObjectInputStream {
}
/**
- * Overridden version that uses the parameterized {@link ClassLoader}
or the {@link ClassLoader}
- * of the current {@link Thread} to resolve the class.
+ * Overridden version that uses the parameterized {@link ClassLoader}
or the {@link ClassLoader} of the current {@link Thread} to resolve the class.
*
* @param desc An instance of class {@link ObjectStreamClass}.
* @return A {@link Class} object corresponding to {@code desc}.
- * @throws IOException Any of the usual Input/Output exceptions.
+ * @throws IOException Any of the usual Input/Output
exceptions.
* @throws ClassNotFoundException If class of a serialized object
cannot be found.
*/
@Override
@@ -108,22 +107,21 @@ protected Class<?> resolveClass(final ObjectStreamClass
desc) throws IOException
}
}
}
-
}
/**
* Deep clones an {@link Object} using serialization.
*
- * <p>This is many times slower than writing clone methods by hand
- * on all objects in your object graph. However, for complex object
- * graphs, or for those that don't support deep cloning this can
- * be a simple alternative implementation. Of course all the objects
- * must be {@link Serializable}.</p>
+ * <p>
+ * This is many times slower than writing clone methods by hand on all
objects in your object graph. However, for complex object graphs, or for those
that
+ * don't support deep cloning this can be a simple alternative
implementation. Of course all the objects must be {@link Serializable}.
+ * </p>
*
- * @param <T> the type of the object involved.
- * @param object the {@link Serializable} object to clone.
+ * @param <T> the type of the object involved.
+ * @param object the {@link Serializable} object to clone.
* @return the cloned object.
* @throws SerializationException (runtime) if the serialization fails.
+ * @see <a
href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java
Object Serialization Specification</a>
*/
public static <T extends Serializable> T clone(final T object) {
if (object == null) {
@@ -135,7 +133,6 @@ public static <T extends Serializable> T clone(final T
object) {
// When we serialize and deserialize an object, it is reasonable
to assume the deserialized object is of the
// same type as the original serialized object
return (T) in.readObject();
-
} catch (final ClassNotFoundException | IOException ex) {
throw new SerializationException(String.format("%s while reading
cloned object data", ex.getClass().getSimpleName()), ex);
}
@@ -145,22 +142,22 @@ public static <T extends Serializable> T clone(final T
object) {
* Deserializes a single {@link Object} from an array of bytes.
*
* <p>
- * If the call site incorrectly types the return value, a {@link
ClassCastException} is thrown from the call site.
- * Without Generics in this declaration, the call site must type cast and
can cause the same ClassCastException.
- * Note that in both cases, the ClassCastException is in the call site,
not in this method.
+ * If the call site incorrectly types the return value, a {@link
ClassCastException} is thrown from the call site. Without Generics in this
declaration, the
+ * call site must type cast and can cause the same ClassCastException.
Note that in both cases, the ClassCastException is in the call site, not in this
+ * method.
* </p>
* <p>
* If you want to secure deserialization with a whitelist or blacklist,
please use Apache Commons IO's
* {@link org.apache.commons.io.serialization.ValidatingObjectInputStream
ValidatingObjectInputStream}.
* </p>
*
- * @param <T> the object type to be deserialized.
- * @param objectData
- * the serialized object, must not be null.
+ * @param <T> the object type to be deserialized.
+ * @param objectData the serialized object, must not be null.
* @return the deserialized object.
- * @throws NullPointerException if {@code objectData} is {@code null}.
+ * @throws NullPointerException if {@code objectData} is {@code null}.
* @throws SerializationException (runtime) if the serialization fails.
* @see org.apache.commons.io.serialization.ValidatingObjectInputStream
+ * @see <a
href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java
Object Serialization Specification</a>
*/
public static <T> T deserialize(final byte[] objectData) {
Objects.requireNonNull(objectData, "objectData");
@@ -171,19 +168,18 @@ public static <T> T deserialize(final byte[] objectData) {
* Deserializes an {@link Object} from the specified stream.
*
* <p>
- * The stream will be closed once the object is written. This avoids the
need for a finally clause, and maybe also
- * exception handling, in the application code.
+ * The stream will be closed once the object is written. This avoids the
need for a finally clause, and maybe also exception handling, in the application
+ * code.
* </p>
*
* <p>
- * The stream passed in is not buffered internally within this method.
This is the responsibility of your
- * application if desired.
+ * The stream passed in is not buffered internally within this method.
This is the responsibility of your application if desired.
* </p>
*
* <p>
- * If the call site incorrectly types the return value, a {@link
ClassCastException} is thrown from the call site.
- * Without Generics in this declaration, the call site must type cast and
can cause the same ClassCastException.
- * Note that in both cases, the ClassCastException is in the call site,
not in this method.
+ * If the call site incorrectly types the return value, a {@link
ClassCastException} is thrown from the call site. Without Generics in this
declaration, the
+ * call site must type cast and can cause the same ClassCastException.
Note that in both cases, the ClassCastException is in the call site, not in this
+ * method.
* </p>
*
* <p>
@@ -191,12 +187,13 @@ public static <T> T deserialize(final byte[] objectData) {
* {@link org.apache.commons.io.serialization.ValidatingObjectInputStream
ValidatingObjectInputStream}.
* </p>
*
- * @param <T> the object type to be deserialized.
+ * @param <T> the object type to be deserialized.
* @param inputStream the serialized object input stream, must not be null.
* @return the deserialized object.
- * @throws NullPointerException if {@code inputStream} is {@code null}.
+ * @throws NullPointerException if {@code inputStream} is {@code null}.
* @throws SerializationException (runtime) if the serialization fails.
* @see org.apache.commons.io.serialization.ValidatingObjectInputStream
+ * @see <a
href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java
Object Serialization Specification</a>
*/
@SuppressWarnings("resource") // inputStream is managed by the caller
public static <T> T deserialize(final InputStream inputStream) {
@@ -211,14 +208,32 @@ public static <T> T deserialize(final InputStream
inputStream) {
}
/**
- * Performs a serialization roundtrip. Serializes and deserializes the
given object, great for testing objects that
- * implement {@link Serializable}.
+ * Checks that the specified object reference is not {@code null} and
throws a customized {@link InvalidObjectException} if it is. This method is
designed
+ * primarily for doing state validation in {@link Serializable} class's
{@code readObject(ObjectInputStream)} methods.
+ *
+ * @param obj the object reference to check for nullity.
+ * @param message detail message to be used in the event that a {@link
InvalidObjectException} is thrown.
+ * @param <T> the type of the reference.
+ * @return {@code obj} if not {@code null}.
+ * @throws InvalidObjectException if {@code obj} is {@code null}.
+ * @see Serializable
+ * @see <a
href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java
Object Serialization Specification</a>
+ * @since 3.21.0
+ */
+ public static <T> T requireNonNull(final T obj, final String message)
throws InvalidObjectException {
+ if (obj == null) {
+ throw new InvalidObjectException(message);
+ }
+ return obj;
+ }
+
+ /**
+ * Performs a serialization roundtrip. Serializes and deserializes the
given object, great for testing objects that implement {@link Serializable}.
*
- * @param <T>
- * the type of the object involved.
- * @param obj
- * the object to roundtrip.
+ * @param <T> the type of the object involved.
+ * @param obj the object to roundtrip.
* @return the serialized and deserialized object.
+ * @see <a
href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java
Object Serialization Specification</a>
* @since 3.3
*/
@SuppressWarnings("unchecked") // OK, because we serialized a type `T`
@@ -227,12 +242,12 @@ public static <T extends Serializable> T roundtrip(final
T obj) {
}
/**
- * Serializes an {@link Object} to a byte array for
- * storage/serialization.
+ * Serializes an {@link Object} to a byte array for storage/serialization.
*
- * @param obj the object to serialize to bytes.
+ * @param obj the object to serialize to bytes.
* @return a byte[] with the converted Serializable.
* @throws SerializationException (runtime) if the serialization fails.
+ * @see <a
href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java
Object Serialization Specification</a>
*/
public static byte[] serialize(final Serializable obj) {
final ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
@@ -243,17 +258,20 @@ public static byte[] serialize(final Serializable obj) {
/**
* Serializes an {@link Object} to the specified stream.
*
- * <p>The stream will be closed once the object is written.
- * This avoids the need for a finally clause, and maybe also exception
- * handling, in the application code.</p>
+ * <p>
+ * The stream will be closed once the object is written. This avoids the
need for a finally clause, and maybe also exception handling, in the application
+ * code.
+ * </p>
*
- * <p>The stream passed in is not buffered internally within this method.
- * This is the responsibility of your application if desired.</p>
+ * <p>
+ * The stream passed in is not buffered internally within this method.
This is the responsibility of your application if desired.
+ * </p>
*
- * @param obj the object to serialize to bytes, may be null.
- * @param outputStream the stream to write to, must not be null.
- * @throws NullPointerException if {@code outputStream} is {@code null}.
+ * @param obj the object to serialize to bytes, may be null.
+ * @param outputStream the stream to write to, must not be null.
+ * @throws NullPointerException if {@code outputStream} is {@code null}.
* @throws SerializationException (runtime) if the serialization fails.
+ * @see <a
href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java
Object Serialization Specification</a>
*/
@SuppressWarnings("resource") // outputStream is managed by the caller
public static void serialize(final Serializable obj, final OutputStream
outputStream) {
@@ -266,11 +284,12 @@ public static void serialize(final Serializable obj,
final OutputStream outputSt
}
/**
- * SerializationUtils instances should NOT be constructed in standard
programming.
- * Instead, the class should be used as {@code
SerializationUtils.clone(object)}.
+ * SerializationUtils instances should NOT be constructed in standard
programming. Instead, the class should be used as
+ * {@code SerializationUtils.clone(object)}.
*
- * <p>This constructor is public to permit tools that require a JavaBean
instance
- * to operate.</p>
+ * <p>
+ * This constructor is public to permit tools that require a JavaBean
instance to operate.
+ * </p>
*
* @since 2.0
* @deprecated TODO Make private in 4.0.
@@ -279,5 +298,4 @@ public static void serialize(final Serializable obj, final
OutputStream outputSt
public SerializationUtils() {
// empty
}
-
}
diff --git a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
index 02da1a4a0..21454f375 100644
--- a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
+++ b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
@@ -46,6 +46,7 @@
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.LocaleUtils;
+import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.StringUtils;
/**
@@ -105,7 +106,6 @@ private static final class CaseInsensitiveTextStrategy
extends PatternStrategy {
CaseInsensitiveTextStrategy(final int field, final Calendar
definingCalendar, final Locale locale) {
this.field = field;
this.locale = LocaleUtils.toLocale(locale);
-
final StringBuilder regex = new StringBuilder();
regex.append("((?iu)");
lKeyValues = appendDisplayNames(definingCalendar, locale, field,
regex);
@@ -219,7 +219,7 @@ static Strategy getStrategy(final int tokenLen) {
case 3:
return ISO_8601_3_STRATEGY;
default:
- throw new IllegalArgumentException("invalid number of X");
+ throw new IllegalArgumentException("Invalid number of X");
}
}
@@ -280,7 +280,6 @@ int modify(final FastDateParser parser, final int iValue) {
boolean parse(final FastDateParser parser, final Calendar calendar,
final String source, final ParsePosition pos, final int maxWidth) {
int idx = pos.getIndex();
int last = source.length();
-
if (maxWidth == 0) {
// if no maxWidth, strip leading white space
for (; idx < last; ++idx) {
@@ -296,22 +295,18 @@ boolean parse(final FastDateParser parser, final Calendar
calendar, final String
last = end;
}
}
-
for (; idx < last; ++idx) {
final char c = source.charAt(idx);
if (!Character.isDigit(c)) {
break;
}
}
-
if (pos.getIndex() == idx) {
pos.setErrorIndex(idx);
return false;
}
-
final int value =
Integer.parseInt(source.substring(pos.getIndex(), idx));
pos.setIndex(idx);
-
calendar.set(field, modify(parser, value));
return true;
}
@@ -458,7 +453,6 @@ private StrategyAndWidth letterPattern(final char c) {
private StrategyAndWidth literal() {
boolean activeQuote = false;
-
final StringBuilder sb = new StringBuilder();
while (currentIdx < pattern.length()) {
final char c = pattern.charAt(currentIdx);
@@ -540,12 +534,9 @@ static boolean skipTimeZone(final String tzId) {
*/
TimeZoneStrategy(final Locale locale) {
this.locale = LocaleUtils.toLocale(locale);
-
final StringBuilder sb = new StringBuilder();
sb.append("((?iu)" + RFC_822_TIME_ZONE + "|" + GMT_OPTION);
-
final Set<String> sorted = new TreeSet<>(LONGER_FIRST_LOWERCASE);
-
// Order is undefined.
// TODO Use of getZoneStrings() is discouraged per its Javadoc.
final String[][] zones =
DateFormatSymbols.getInstance(locale).getZoneStrings();
@@ -1107,8 +1098,9 @@ public Object parseObject(final String source, final
ParsePosition pos) {
*/
private void readObject(final ObjectInputStream in) throws IOException,
ClassNotFoundException {
in.defaultReadObject();
- final Calendar definingCalendar = Calendar.getInstance(timeZone,
locale);
- init(definingCalendar);
+ SerializationUtils.requireNonNull(pattern, "pattern null");
+ SerializationUtils.requireNonNull(timeZone, "timeZone null");
+ init(Calendar.getInstance(timeZone, locale));
}
/**
diff --git
a/src/test/java/org/apache/commons/lang3/time/FastDateParserReadObjectTest.java
b/src/test/java/org/apache/commons/lang3/time/FastDateParserReadObjectTest.java
new file mode 100644
index 000000000..70510f421
--- /dev/null
+++
b/src/test/java/org/apache/commons/lang3/time/FastDateParserReadObjectTest.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * https://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 org.apache.commons.lang3.time;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.Serializable;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests that a deserialized {@link FastDateParser} rejects null {@code
pattern} and null {@code timeZone} fields.
+ *
+ * <p>
+ * The two null-checks were introduced in {@link
FastDateParser#readObject(ObjectInputStream)}:
+ * </p>
+ * <ul>
+ * <li>{@code if (pattern == null) throw new InvalidObjectException("pattern
null");}</li>
+ * <li>{@code if (timeZone == null) throw new InvalidObjectException("timeZone
null");}</li>
+ * </ul>
+ *
+ * <p>
+ * Because neither null value can reach {@code readObject} through the normal
public API, the tests forge a malicious serialization stream. A
+ * {@link FastDateParserForge} helper carries the same non-transient field set
as {@link FastDateParser} (same names, same types, same {@code
serialVersionUID})
+ * but allows null values. A custom {@link ObjectOutputStream} sub-class
rewrites the class-descriptor name to {@code FastDateParser} so the stream is
accepted
+ * by {@link ObjectInputStream} as a {@link FastDateParser} payload; {@code
defaultReadObject} then assigns the forged values to the actual
+ * {@link FastDateParser} fields, triggering the null checks.
+ * </p>
+ */
+class FastDateParserReadObjectTest {
+
+ /**
+ * Forge carrier: same non-transient fields as {@link FastDateParser}, in
the same alphabetical order used by Java default serialization ({@code century},
+ * {@code locale}, {@code pattern}, {@code startYear}, {@code timeZone}),
with the same {@code serialVersionUID}. Allows null for {@code pattern} and
+ * {@code timeZone}.
+ */
+ private static final class FastDateParserForge implements Serializable {
+
+ /** Must match {@link FastDateParser#serialVersionUID}. */
+ private static final long serialVersionUID = 3L;
+ // Fields must match FastDateParser's non-transient fields by name and
type.
+ private final int century;
+ private final Locale locale;
+ private final String pattern;
+ private final int startYear;
+ private final TimeZone timeZone;
+
+ FastDateParserForge(final String pattern, final TimeZone timeZone,
final Locale locale, final int century, final int startYear) {
+ this.pattern = pattern;
+ this.timeZone = timeZone;
+ this.locale = locale;
+ this.century = century;
+ this.startYear = startYear;
+ }
+ }
+
+ /**
+ * Deserializes {@code bytes} and returns the resulting object.
+ *
+ * @param bytes serialized form
+ * @return the deserialized object
+ * @throws IOException if an I/O error occurs
+ * @throws ClassNotFoundException if the class of the serialized object
cannot be found
+ */
+ private static Object deserialize(final byte[] bytes) throws IOException,
ClassNotFoundException {
+ try (ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(bytes))) {
+ return ois.readObject();
+ }
+ }
+
+ /**
+ * Serializes a {@link FastDateParserForge} but rewrites the class
descriptor so that the resulting stream is treated as a {@link FastDateParser}
during
+ * deserialization.
+ *
+ * @param forge the forge instance to serialize
+ * @return a byte array whose class descriptor names {@link FastDateParser}
+ * @throws IOException if an I/O error occurs
+ */
+ private static byte[] forgeStream(final FastDateParserForge forge) throws
IOException {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(baos) {
+
+ @Override
+ protected void writeClassDescriptor(final ObjectStreamClass desc)
throws IOException {
+ if
(desc.getName().equals(FastDateParserForge.class.getName())) {
+ // Spoof the class descriptor so the stream deserializes
as FastDateParser.
+
super.writeClassDescriptor(ObjectStreamClass.lookup(FastDateParser.class));
+ } else {
+ super.writeClassDescriptor(desc);
+ }
+ }
+ }) {
+ oos.writeObject(forge);
+ }
+ return baos.toByteArray();
+ }
+
+ /**
+ * Tests that a forged stream whose {@code pattern} field is {@code null}
is rejected with {@link InvalidObjectException}.
+ */
+ @Test
+ void testNullPatternRejected() throws IOException {
+ final FastDateParserForge forge = new FastDateParserForge(null, //
pattern = null (the evil value under test)
+ TimeZone.getTimeZone("GMT"), Locale.US, 1900, 0);
+ final byte[] forgedBytes = forgeStream(forge);
+ assertThrows(InvalidObjectException.class, () ->
deserialize(forgedBytes), "A null pattern must be rejected with
InvalidObjectException");
+ }
+
+ /**
+ * Tests that a forged stream whose {@code timeZone} field is {@code null}
is rejected with {@link InvalidObjectException}.
+ */
+ @Test
+ void testNullTimeZoneRejected() throws IOException {
+ final FastDateParserForge forge = new
FastDateParserForge("yyyy-MM-dd", null, // timeZone = null (the evil value
under test)
+ Locale.US, 1900, 0);
+ final byte[] forgedBytes = forgeStream(forge);
+ assertThrows(InvalidObjectException.class, () ->
deserialize(forgedBytes), "A null timeZone must be rejected with
InvalidObjectException");
+ }
+}