Skip to content

Commit db3d6d7

Browse files
author
Roger Riggs
committedDec 21, 2021
8278087: Deserialization filter and filter factory property error reporting under specified
Backport-of: f90425a
1 parent 467f654 commit db3d6d7

File tree

4 files changed

+204
-83
lines changed

4 files changed

+204
-83
lines changed
 

‎src/java.base/share/classes/java/io/ObjectInputFilter.java

+66-21
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727

2828
import jdk.internal.access.SharedSecrets;
2929
import jdk.internal.util.StaticProperty;
30-
import sun.security.action.GetBooleanAction;
3130

3231
import java.lang.reflect.InvocationTargetException;
3332
import java.security.AccessController;
@@ -523,10 +522,15 @@ enum Status {
523522
* {@systemProperty jdk.serialFilter}, its value is used to configure the filter.
524523
* If the system property is not defined, and the {@link java.security.Security} property
525524
* {@code jdk.serialFilter} is defined then it is used to configure the filter.
526-
* The filter is created as if {@link #createFilter(String) createFilter} is called;
527-
* if the filter string is invalid, an {@link ExceptionInInitializerError} is thrown.
528-
* Otherwise, the filter is not configured during initialization and
529-
* can be set with {@link #setSerialFilter(ObjectInputFilter) Config.setSerialFilter}.
525+
* The filter is created as if {@link #createFilter(String) createFilter} is called,
526+
* if the filter string is invalid the initialization fails and subsequent attempts to
527+
* {@linkplain Config#getSerialFilter() get the filter}, {@linkplain Config#setSerialFilter set a filter},
528+
* or create an {@linkplain ObjectInputStream#ObjectInputStream(InputStream) ObjectInputStream}
529+
* throw {@link IllegalStateException}. Deserialization is not possible with an
530+
* invalid serial filter.
531+
* If the system property {@code jdk.serialFilter} or the {@link java.security.Security}
532+
* property {@code jdk.serialFilter} is not set the filter can be set with
533+
* {@link #setSerialFilter(ObjectInputFilter) Config.setSerialFilter}.
530534
* Setting the {@code jdk.serialFilter} with {@link System#setProperty(String, String)
531535
* System.setProperty} <em>does not set the filter</em>.
532536
* The syntax for the property value is the same as for the
@@ -545,9 +549,12 @@ enum Status {
545549
* <p>The class must be public, must have a public zero-argument constructor, implement the
546550
* {@link BinaryOperator {@literal BinaryOperator<ObjectInputFilter>}} interface, provide its implementation and
547551
* be accessible via the {@linkplain ClassLoader#getSystemClassLoader() application class loader}.
548-
* If the filter factory constructor is not invoked successfully, an {@link ExceptionInInitializerError}
549-
* is thrown and subsequent use of the filter factory for deserialization fails with
550-
* {@link IllegalStateException}.
552+
* If the filter factory constructor is not invoked successfully subsequent attempts to
553+
* {@linkplain Config#getSerialFilterFactory() get the factory},
554+
* {@linkplain Config#setSerialFilterFactory(BinaryOperator) set the factory}, or create an
555+
* {@link ObjectInputStream#ObjectInputStream(InputStream) ObjectInputStream}
556+
* throw {@link IllegalStateException}. Deserialization is not possible with an
557+
* invalid serial filter factory.
551558
* The filter factory configured using the system or security property during initialization
552559
* can NOT be replaced with {@link #setSerialFilterFactory(BinaryOperator) Config.setSerialFilterFactory}.
553560
* This ensures that a filter factory set on the command line is not overridden accidentally
@@ -582,12 +589,22 @@ final class Config {
582589
*/
583590
private static volatile ObjectInputFilter serialFilter;
584591

592+
/**
593+
* Saved message if the jdk.serialFilter property is invalid.
594+
*/
595+
private static final String invalidFilterMessage;
596+
585597
/**
586598
* Current serial filter factory.
587599
* @see Config#setSerialFilterFactory(BinaryOperator)
588600
*/
589601
private static volatile BinaryOperator<ObjectInputFilter> serialFilterFactory;
590602

603+
/**
604+
* Saved message if the jdk.serialFilterFactory property is invalid.
605+
*/
606+
private static final String invalidFactoryMessage;
607+
591608
/**
592609
* Boolean to indicate that the filter factory can not be set or replaced.
593610
* - an ObjectInputStream has already been created using the current filter factory
@@ -630,23 +647,24 @@ final class Config {
630647
Security.getProperty(SERIAL_FILTER_PROPNAME));
631648

632649
// Initialize the static filter if the jdk.serialFilter is present
633-
ObjectInputFilter filter = null;
650+
String filterMessage = null;
634651
if (filterString != null) {
635652
configLog.log(DEBUG,
636653
"Creating deserialization filter from {0}", filterString);
637654
try {
638-
filter = createFilter(filterString);
655+
serialFilter = createFilter(filterString);
639656
} catch (RuntimeException re) {
640657
configLog.log(ERROR,
641658
"Error configuring filter: {0}", (Object) re);
642-
// Do not continue if configuration not initialized
643-
throw re;
659+
// serialFilter remains null
660+
filterMessage = "Invalid jdk.serialFilter: " + re.getMessage();
644661
}
645662
}
646-
serialFilter = filter;
663+
invalidFilterMessage = filterMessage;
647664

648665
// Initialize the filter factory if the jdk.serialFilterFactory is defined
649666
// otherwise use the builtin filter factory.
667+
String factoryMessage = null;
650668
if (factoryClassName == null) {
651669
serialFilterFactory = new BuiltinFilterFactory();
652670
} else {
@@ -671,10 +689,13 @@ final class Config {
671689
Throwable th = (ex instanceof InvocationTargetException ite) ? ite.getCause() : ex;
672690
configLog.log(ERROR,
673691
"Error configuring filter factory: {0}", (Object)th);
674-
// Do not continue if configuration not initialized
675-
throw new ExceptionInInitializerError(th);
692+
// Configuration not initialized
693+
// serialFilterFactory remains null and filterFactoryNoReplace == true;
694+
factoryMessage = "invalid jdk.serialFilterFactory: " +
695+
factoryClassName + ": " + th.getClass().getName() + ": " + th.getMessage();
676696
}
677697
}
698+
invalidFactoryMessage = factoryMessage;
678699
// Setup shared secrets for RegistryImpl to use.
679700
SharedSecrets.setJavaObjectInputFilterAccess(Config::createFilter2);
680701
}
@@ -696,8 +717,14 @@ private static void traceFilter(String msg, Object... args) {
696717
* Returns the static JVM-wide deserialization filter or {@code null} if not configured.
697718
*
698719
* @return the static JVM-wide deserialization filter or {@code null} if not configured
720+
* @throws IllegalStateException if the initialization of the filter from the
721+
* system property {@code jdk.serialFilter} or
722+
* the security property {@code jdk.serialFilter} fails.
699723
*/
700724
public static ObjectInputFilter getSerialFilter() {
725+
if (invalidFilterMessage != null) {
726+
throw new IllegalStateException(invalidFilterMessage);
727+
}
701728
return serialFilter;
702729
}
703730

@@ -707,7 +734,9 @@ public static ObjectInputFilter getSerialFilter() {
707734
* @param filter the deserialization filter to set as the JVM-wide filter; not null
708735
* @throws SecurityException if there is security manager and the
709736
* {@code SerializablePermission("serialFilter")} is not granted
710-
* @throws IllegalStateException if the filter has already been set
737+
* @throws IllegalStateException if the filter has already been set or the initialization
738+
* of the filter from the system property {@code jdk.serialFilter} or
739+
* the security property {@code jdk.serialFilter} fails.
711740
*/
712741
public static void setSerialFilter(ObjectInputFilter filter) {
713742
Objects.requireNonNull(filter, "filter");
@@ -716,6 +745,9 @@ public static void setSerialFilter(ObjectInputFilter filter) {
716745
if (sm != null) {
717746
sm.checkPermission(ObjectStreamConstants.SERIAL_FILTER_PERMISSION);
718747
}
748+
if (invalidFilterMessage != null) {
749+
throw new IllegalStateException(invalidFilterMessage);
750+
}
719751
synchronized (serialFilterLock) {
720752
if (serialFilter != null) {
721753
throw new IllegalStateException("Serial filter can only be set once");
@@ -749,8 +781,10 @@ public static void setSerialFilter(ObjectInputFilter filter) {
749781
* @since 17
750782
*/
751783
public static BinaryOperator<ObjectInputFilter> getSerialFilterFactory() {
752-
if (serialFilterFactory == null)
753-
throw new IllegalStateException("Serial filter factory initialization incomplete");
784+
if (serialFilterFactory == null) {
785+
// If initializing the factory failed or not yet complete, throw with the message
786+
throw new IllegalStateException(invalidFilterFactoryMessage());
787+
}
754788
return serialFilterFactory;
755789
}
756790

@@ -812,15 +846,26 @@ public static void setSerialFilterFactory(BinaryOperator<ObjectInputFilter> filt
812846
}
813847
if (filterFactoryNoReplace.getAndSet(true)) {
814848
final String msg = serialFilterFactory != null
815-
? serialFilterFactory.getClass().getName()
816-
: "initialization incomplete";
817-
throw new IllegalStateException("Cannot replace filter factory: " + msg);
849+
? "Cannot replace filter factory: " + serialFilterFactory.getClass().getName()
850+
: invalidFilterFactoryMessage();
851+
throw new IllegalStateException(msg);
818852
}
819853
configLog.log(DEBUG,
820854
"Setting deserialization filter factory to {0}", filterFactory.getClass().getName());
821855
serialFilterFactory = filterFactory;
822856
}
823857

858+
/*
859+
* Return message for an invalid filter factory configuration saved from the static init.
860+
* It can be called before the static initializer is complete and has set the message/null.
861+
*/
862+
private static String invalidFilterFactoryMessage() {
863+
assert serialFilterFactory == null; // undefined if a filter factory has been set
864+
return (invalidFactoryMessage != null)
865+
? invalidFactoryMessage
866+
: "Serial filter factory initialization incomplete";
867+
}
868+
824869
/**
825870
* Returns an ObjectInputFilter from a string of patterns.
826871
* <p>

‎src/java.base/share/classes/java/io/ObjectInputStream.java

+8
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,8 @@ private static class Logging {
384384
* <p>The constructor initializes the deserialization filter to the filter returned
385385
* by invoking the {@link Config#getSerialFilterFactory()} with {@code null} for the current filter
386386
* and the {@linkplain Config#getSerialFilter() static JVM-wide filter} for the requested filter.
387+
* If the serial filter or serial filter factory properties are invalid
388+
* an {@link IllegalStateException} is thrown.
387389
*
388390
* <p>If a security manager is installed, this constructor will check for
389391
* the "enableSubclassImplementation" SerializablePermission when invoked
@@ -396,6 +398,8 @@ private static class Logging {
396398
* @throws IOException if an I/O error occurs while reading stream header
397399
* @throws SecurityException if untrusted subclass illegally overrides
398400
* security-sensitive methods
401+
* @throws IllegalStateException if the initialization of {@link ObjectInputFilter.Config}
402+
* fails due to invalid serial filter or serial filter factory properties.
399403
* @throws NullPointerException if {@code in} is {@code null}
400404
* @see ObjectInputStream#ObjectInputStream()
401405
* @see ObjectInputStream#readFields()
@@ -421,6 +425,8 @@ public ObjectInputStream(InputStream in) throws IOException {
421425
* <p>The constructor initializes the deserialization filter to the filter returned
422426
* by invoking the {@link Config#getSerialFilterFactory()} with {@code null} for the current filter
423427
* and the {@linkplain Config#getSerialFilter() static JVM-wide filter} for the requested filter.
428+
* If the serial filter or serial filter factory properties are invalid
429+
* an {@link IllegalStateException} is thrown.
424430
*
425431
* <p>If there is a security manager installed, this method first calls the
426432
* security manager's {@code checkPermission} method with the
@@ -431,6 +437,8 @@ public ObjectInputStream(InputStream in) throws IOException {
431437
* {@code checkPermission} method denies enabling
432438
* subclassing.
433439
* @throws IOException if an I/O error occurs while creating this stream
440+
* @throws IllegalStateException if the initialization of {@link ObjectInputFilter.Config}
441+
* fails due to invalid serial filter or serial filter factory properties.
434442
* @see SecurityManager#checkPermission
435443
* @see java.io.SerializablePermission
436444
*/

‎test/jdk/java/io/Serializable/serialFilter/InvalidGlobalFilterTest.java

+78-45
Original file line numberDiff line numberDiff line change
@@ -21,67 +21,100 @@
2121
* questions.
2222
*/
2323

24-
import jdk.test.lib.process.OutputAnalyzer;
25-
import jdk.test.lib.process.ProcessTools;
24+
import org.testng.Assert;
25+
import org.testng.annotations.DataProvider;
26+
import org.testng.annotations.Test;
2627

27-
import java.io.File;
28+
import java.io.ByteArrayInputStream;
29+
import java.io.IOException;
2830
import java.io.ObjectInputFilter;
31+
import java.io.ObjectInputStream;
32+
import java.util.Map;
2933

3034
/*
3135
* @test
32-
* @bug 8269336
36+
* @bug 8278087
3337
* @summary Test that an invalid pattern value for the jdk.serialFilter system property causes an
34-
* exception to be thrown in the class initialization of java.io.ObjectInputFilter.Config class
35-
* @library /test/lib
36-
* @run driver InvalidGlobalFilterTest
38+
* exception to be thrown when an attempt is made to use the filter or deserialize.
39+
* A subset of invalid filter patterns is tested.
40+
* @run testng/othervm -Djdk.serialFilter=.* InvalidGlobalFilterTest
41+
* @run testng/othervm -Djdk.serialFilter=! InvalidGlobalFilterTest
42+
* @run testng/othervm -Djdk.serialFilter=/ InvalidGlobalFilterTest
43+
*
3744
*/
45+
@Test
3846
public class InvalidGlobalFilterTest {
3947
private static final String serialPropName = "jdk.serialFilter";
48+
private static final String serialFilter = System.getProperty(serialPropName);
49+
50+
static {
51+
// Enable logging
52+
System.setProperty("java.util.logging.config.file",
53+
System.getProperty("test.src", ".") + "/logging.properties");
54+
}
4055

4156
/**
42-
* Launches multiple instances of a Java program by passing each instance an invalid value
43-
* for the {@code jdk.serialFilter} system property. The launched program then triggers the
44-
* class initialization of {@code ObjectInputFilter.Config} class to have it parse the (invalid)
45-
* value of the system property. The launched program is expected to propagate the exception
46-
* raised by the {@code ObjectInputFilter.Config} initialization and the test asserts that the
47-
* launched program did indeed fail with this expected exception.
57+
* Map of invalid patterns to the expected exception message.
4858
*/
49-
public static void main(final String[] args) throws Exception {
50-
final String[] invalidPatterns = {".*", ".**", "!", "/java.util.Hashtable", "java.base/", "/"};
51-
for (final String invalidPattern : invalidPatterns) {
52-
final ProcessBuilder processBuilder = ProcessTools.createJavaProcessBuilder(
53-
"-D" + serialPropName + "=" + invalidPattern,
54-
"-Djava.util.logging.config.file=" + System.getProperty("test.src")
55-
+ File.separator + "logging.properties",
56-
ObjectInputFilterConfigLoader.class.getName());
57-
// launch a process by passing it an invalid value for -Djdk.serialFilter
58-
final OutputAnalyzer outputAnalyzer = ProcessTools.executeProcess(processBuilder);
59-
try {
60-
// we expect the JVM launch to fail
61-
outputAnalyzer.shouldNotHaveExitValue(0);
62-
// do an additional check to be sure it failed for the right reason
63-
outputAnalyzer.stderrShouldContain("java.lang.ExceptionInInitializerError");
64-
} finally {
65-
// fail or pass, we print out the generated output from the launched program
66-
// for any debugging
67-
System.err.println("Diagnostics from process " + outputAnalyzer.pid() + ":");
68-
// print out any stdout/err that was generated in the launched program
69-
outputAnalyzer.reportDiagnosticSummary();
70-
}
59+
private static final Map<String, String> invalidMessages =
60+
Map.of(".*", "Invalid jdk.serialFilter: package missing in: \".*\"",
61+
".**", "Invalid jdk.serialFilter: package missing in: \".**\"",
62+
"!", "Invalid jdk.serialFilter: class or package missing in: \"!\"",
63+
"/java.util.Hashtable", "Invalid jdk.serialFilter: module name is missing in: \"/java.util.Hashtable\"",
64+
"java.base/", "Invalid jdk.serialFilter: class or package missing in: \"java.base/\"",
65+
"/", "Invalid jdk.serialFilter: module name is missing in: \"/\"");
66+
67+
@DataProvider(name = "MethodsToCall")
68+
private Object[][] cases() {
69+
return new Object[][] {
70+
{serialFilter, "getSerialFilter", (Assert.ThrowingRunnable) () -> ObjectInputFilter.Config.getSerialFilter()},
71+
{serialFilter, "setSerialFilter", (Assert.ThrowingRunnable) () -> ObjectInputFilter.Config.setSerialFilter(new NoopFilter())},
72+
{serialFilter, "new ObjectInputStream(is)", (Assert.ThrowingRunnable) () -> new ObjectInputStream(new ByteArrayInputStream(new byte[0]))},
73+
{serialFilter, "new OISSubclass()", (Assert.ThrowingRunnable) () -> new OISSubclass()},
74+
};
75+
}
76+
77+
/**
78+
* Test each method that should throw IllegalStateException based on
79+
* the invalid arguments it was launched with.
80+
*/
81+
@Test(dataProvider = "MethodsToCall")
82+
public void initFaultTest(String pattern, String method, Assert.ThrowingRunnable runnable) {
83+
84+
IllegalStateException ex = Assert.expectThrows(IllegalStateException.class,
85+
runnable);
86+
87+
String expected = invalidMessages.get(serialFilter);
88+
if (expected == null) {
89+
Assert.fail("No expected message for filter: " + serialFilter);
7190
}
91+
System.out.println(ex.getMessage());
92+
Assert.assertEquals(ex.getMessage(), expected, "wrong message");
7293
}
7394

74-
// A main() class which just triggers the class initialization of ObjectInputFilter.Config
75-
private static final class ObjectInputFilterConfigLoader {
95+
private static class NoopFilter implements ObjectInputFilter {
96+
/**
97+
* Returns UNDECIDED.
98+
*
99+
* @param filter the FilterInfo
100+
* @return Status.UNDECIDED
101+
*/
102+
public ObjectInputFilter.Status checkInput(FilterInfo filter) {
103+
return ObjectInputFilter.Status.UNDECIDED;
104+
}
76105

77-
public static void main(final String[] args) throws Exception {
78-
System.out.println("JVM was launched with " + serialPropName
79-
+ " system property set to " + System.getProperty(serialPropName));
80-
// this call is expected to fail and we aren't interested in the result.
81-
// we just let the exception propagate out of this call and fail the
82-
// launched program. The test which launched this main, then asserts
83-
// that the exception was indeed thrown.
84-
ObjectInputFilter.Config.getSerialFilter();
106+
public String toString() {
107+
return "NoopFilter";
85108
}
86109
}
110+
111+
/**
112+
* Subclass of ObjectInputStream to test subclassing constructor.
113+
*/
114+
private static class OISSubclass extends ObjectInputStream {
115+
116+
protected OISSubclass() throws IOException {
117+
}
118+
}
119+
87120
}

‎test/jdk/java/io/Serializable/serialFilter/SerialFactoryFaults.java

+52-17
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,14 @@
2222
*/
2323

2424
import org.testng.Assert;
25+
import org.testng.annotations.DataProvider;
2526
import org.testng.annotations.Test;
2627

28+
import java.io.ByteArrayInputStream;
29+
import java.io.IOException;
2730
import java.io.ObjectInputFilter;
2831
import java.io.ObjectInputFilter.Config;
32+
import java.io.ObjectInputStream;
2933
import java.util.function.BinaryOperator;
3034

3135
/* @test
@@ -39,33 +43,47 @@
3943
@Test
4044
public class SerialFactoryFaults {
4145

46+
// Sample the serial factory class name
47+
private static final String factoryName = System.getProperty("jdk.serialFilterFactory");
48+
4249
static {
4350
// Enable logging
4451
System.setProperty("java.util.logging.config.file",
4552
System.getProperty("test.src", ".") + "/logging.properties");
4653
}
4754

48-
public void initFaultTest() {
49-
String factoryName = System.getProperty("jdk.serialFilterFactory");
50-
ExceptionInInitializerError ex = Assert.expectThrows(ExceptionInInitializerError.class,
51-
() -> Config.getSerialFilterFactory());
52-
Throwable cause = ex.getCause();
55+
@DataProvider(name = "MethodsToCall")
56+
private Object[][] cases() {
57+
return new Object[][] {
58+
{"getSerialFilterFactory", (Assert.ThrowingRunnable) () -> Config.getSerialFilterFactory()},
59+
{"setSerialFilterFactory", (Assert.ThrowingRunnable) () -> Config.setSerialFilterFactory(new NoopFactory())},
60+
{"new ObjectInputStream(is)", (Assert.ThrowingRunnable) () -> new ObjectInputStream(new ByteArrayInputStream(new byte[0]))},
61+
{"new OISSubclass()", (Assert.ThrowingRunnable) () -> new OISSubclass()},
62+
};
63+
}
64+
65+
/**
66+
* Test each method that should throw IllegalStateException based on
67+
* the invalid arguments it was launched with.
68+
*/
69+
@Test(dataProvider = "MethodsToCall")
70+
public void initFaultTest(String name, Assert.ThrowingRunnable runnable) {
71+
IllegalStateException ex = Assert.expectThrows(IllegalStateException.class,
72+
runnable);
73+
final String msg = ex.getMessage();
5374

5475
if (factoryName.equals("ForcedError_NoSuchClass")) {
55-
Assert.assertEquals(cause.getClass(),
56-
ClassNotFoundException.class, "wrong exception");
76+
Assert.assertEquals(msg,
77+
"invalid jdk.serialFilterFactory: ForcedError_NoSuchClass: java.lang.ClassNotFoundException: ForcedError_NoSuchClass", "wrong exception");
5778
} else if (factoryName.equals("SerialFactoryFaults$NoPublicConstructor")) {
58-
Assert.assertEquals(cause.getClass(),
59-
NoSuchMethodException.class, "wrong exception");
79+
Assert.assertEquals(msg,
80+
"invalid jdk.serialFilterFactory: SerialFactoryFaults$NoPublicConstructor: java.lang.NoSuchMethodException: SerialFactoryFaults$NoPublicConstructor.<init>()", "wrong exception");
6081
} else if (factoryName.equals("SerialFactoryFaults$ConstructorThrows")) {
61-
Assert.assertEquals(cause.getClass(),
62-
IllegalStateException.class, "wrong exception");
82+
Assert.assertEquals(msg,
83+
"invalid jdk.serialFilterFactory: SerialFactoryFaults$ConstructorThrows: java.lang.RuntimeException: constructor throwing a runtime exception", "wrong exception");
6384
} else if (factoryName.equals("SerialFactoryFaults$FactorySetsFactory")) {
64-
Assert.assertEquals(cause.getClass(),
65-
IllegalStateException.class, "wrong exception");
66-
Assert.assertEquals(cause.getMessage(),
67-
"Cannot replace filter factory: initialization incomplete",
68-
"wrong message");
85+
Assert.assertEquals(msg,
86+
"invalid jdk.serialFilterFactory: SerialFactoryFaults$FactorySetsFactory: java.lang.IllegalStateException: Serial filter factory initialization incomplete", "wrong exception");
6987
} else {
7088
Assert.fail("No test for filter factory: " + factoryName);
7189
}
@@ -90,7 +108,7 @@ public ObjectInputFilter apply(ObjectInputFilter curr, ObjectInputFilter next) {
90108
public static final class ConstructorThrows
91109
implements BinaryOperator<ObjectInputFilter> {
92110
public ConstructorThrows() {
93-
throw new IllegalStateException("SerialFactoryFaults$ConstructorThrows");
111+
throw new RuntimeException("constructor throwing a runtime exception");
94112
}
95113

96114
public ObjectInputFilter apply(ObjectInputFilter curr, ObjectInputFilter next) {
@@ -112,4 +130,21 @@ public ObjectInputFilter apply(ObjectInputFilter curr, ObjectInputFilter next) {
112130
}
113131
}
114132

133+
public static final class NoopFactory implements BinaryOperator<ObjectInputFilter> {
134+
public NoopFactory() {}
135+
136+
public ObjectInputFilter apply(ObjectInputFilter curr, ObjectInputFilter next) {
137+
throw new RuntimeException("NYI");
138+
}
139+
}
140+
141+
/**
142+
* Subclass of ObjectInputStream to test subclassing constructor.
143+
*/
144+
private static class OISSubclass extends ObjectInputStream {
145+
146+
protected OISSubclass() throws IOException {
147+
}
148+
}
149+
115150
}

0 commit comments

Comments
 (0)
Please sign in to comment.