1 /*
2  * Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 import java.io.ByteArrayInputStream;
25 import java.io.ByteArrayOutputStream;
26 import java.io.EOFException;
27 import java.io.IOException;
28 import java.io.InvalidClassException;
29 import java.io.ObjectInputFilter;
30 import java.io.ObjectInputStream;
31 import java.io.ObjectOutputStream;
32 import java.io.Serial;
33 import java.io.Serializable;
34 import java.lang.invoke.SerializedLambda;
35 import java.lang.reflect.Constructor;
36 import java.lang.reflect.InvocationTargetException;
37 import java.lang.reflect.Proxy;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.Collections;
41 import java.util.HashSet;
42 import java.util.Hashtable;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.concurrent.atomic.LongAdder;
46 
47 import javax.net.ssl.SSLEngineResult;
48 
49 import org.testng.Assert;
50 import org.testng.annotations.Test;
51 import org.testng.annotations.DataProvider;
52 
53 /* @test
54  * @bug 8234836
55  * @build SerialFilterTest
56  * @run testng/othervm -Djava.util.logging.config.file=${test.src}/logging.properties
57  *                      SerialFilterTest
58  * @run testng/othervm -Djdk.serialSetFilterAfterRead=true SerialFilterTest
59  *
60  * @summary Test ObjectInputFilters using Builtin Filter Factory
61  */
62 @Test
63 public class SerialFilterTest implements Serializable {
64 
65     @Serial
66     private static final long serialVersionUID = -6999613679881262446L;
67 
68     /**
69      * Enable three arg lambda.
70      * @param <T> The pattern
71      * @param <U> The test object
72      * @param <V> Boolean for if the filter should allow or reject
73      */
74     interface TriConsumer< T, U, V> {
accept(T t, U u, V v)75         void accept(T t, U u, V v);
76     }
77 
78     /**
79      * Misc object to use that should always be accepted.
80      */
81     private static final Object otherObject = Integer.valueOf(0);
82 
83     // Cache value of jdk.serialSetFilterAfterRead property.
84     static final boolean SET_FILTER_AFTER_READ =
85             Boolean.getBoolean("jdk.serialSetFilterAfterRead");
86 
87     /**
88      * DataProvider for the individual patterns to test.
89      * Expand the patterns into cases for each of the Std and Compatibility APIs.
90      * @return an array of arrays of the parameters including factories
91      */
92     @DataProvider(name="Patterns")
patterns()93     static Object[][] patterns() {
94         Object[][] patterns = new Object[][]{
95                 {"java.util.Hashtable"},
96                 {"java.util.Hash*"},
97                 {"javax.net.ssl.*"},
98                 {"javax.net.**"},
99                 {"*"},
100                 {"maxarray=47"},
101                 {"maxdepth=5"},
102                 {"maxrefs=10"},
103                 {"maxbytes=100"},
104                 {"maxbytes=72"},
105                 {"maxbytes=+1024"},
106                 {"java.base/java.util.Hashtable"},
107         };
108         return patterns;
109     }
110 
111     @DataProvider(name="InvalidPatterns")
invalidPatterns()112     static Object[][] invalidPatterns() {
113         return new Object [][] {
114                 {".*"},
115                 {".**"},
116                 {"!"},
117                 {"/java.util.Hashtable"},
118                 {"java.base/"},
119                 {"/"},
120         };
121     }
122 
123     @DataProvider(name="Limits")
limits()124     static Object[][] limits() {
125         // The numbers are arbitrary > 1
126         return new Object[][] {
127                 {"maxrefs", 1},     // 0 is tested as n-1
128                 {"maxrefs", 10},
129                 {"maxdepth", 5},
130                 {"maxbytes", 100},
131                 {"maxarray", 16},
132                 {"maxbytes", Long.MAX_VALUE},
133         };
134     }
135 
136     @DataProvider(name="InvalidLimits")
invalidLimits()137     static Object[][] invalidLimits() {
138         return new Object[][] {
139                 {"maxrefs=-1"},
140                 {"maxdepth=-1"},
141                 {"maxbytes=-1"},
142                 {"maxarray=-1"},
143                 {"xyz=0"},
144                 {"xyz=-1"},
145                 {"maxrefs=0xabc"},
146                 {"maxrefs=abc"},
147                 {"maxrefs="},
148                 {"maxrefs=+"},
149                 {"maxbytes=-1"},
150                 {"maxbytes=9223372036854775808"},
151                 {"maxbytes=-9223372036854775807"},
152         };
153     }
154 
155     /**
156      * DataProvider of individual objects. Used to check the information
157      * available to the filter.
158      * @return  Arrays of parameters with objects
159      */
160     @DataProvider(name="Objects")
objects()161     static Object[][] objects() {
162         byte[] byteArray = new byte[0];
163         Object[] objArray = new Object[7];
164         objArray[objArray.length - 1] = objArray;
165 
166         Class<?> serClass = null;
167         String className = "java.util.concurrent.atomic.LongAdder$SerializationProxy";
168         try {
169             serClass = Class.forName(className);
170         } catch (Exception e) {
171             Assert.fail("missing class: " + className, e);
172         }
173 
174         Class<?>[] interfaces = {Runnable.class};
175         Runnable proxy = (Runnable) Proxy.newProxyInstance(null,
176                 interfaces, (p, m, args) -> p);
177 
178         Runnable runnable = (Runnable & Serializable) SerialFilterTest::noop;
179 
180         List<Class<?>> classList = new ArrayList<>();
181         classList.add(HashSet.class);
182         classList.addAll(Collections.nCopies(21, Map.Entry[].class));
183 
184         Object[][] objects = {
185                 { null, 0, -1, 0, 0, 0,
186                         Arrays.asList()},        // no callback, no values
187                 { objArray, 3, 7, 9, 2, 55,
188                         Arrays.asList(objArray.getClass(), objArray.getClass())},
189                 { Object[].class, 1, -1, 1, 1, 38,
190                         Arrays.asList(Object[].class)},
191                 { new SerialFilterTest(), 1, -1, 1, 1, 35,
192                         Arrays.asList(SerialFilterTest.class)},
193                 { new LongAdder(), 2, -1, 2, 1, 93,
194                         Arrays.asList(serClass, LongAdder.class)},
195                 { new byte[14], 2, 14, 2, 1, 27,
196                         Arrays.asList(byteArray.getClass(), byteArray.getClass())},
197                 { runnable, 13, 0, 13, 2, 514,
198                         Arrays.asList(java.lang.invoke.SerializedLambda.class,
199                                 objArray.getClass(),
200                                 objArray.getClass(),
201                                 SerialFilterTest.class,
202                                 java.lang.invoke.SerializedLambda.class)},
203                 { deepHashSet(10), 69, 4, 50, 11, 619, classList },
204                 { proxy.getClass(), 3, -1, 2, 2, 112,
205                         Arrays.asList(Runnable.class,
206                                 java.lang.reflect.Proxy.class,
207                                 java.lang.reflect.Proxy.class)},
208                 { new F(), 6, -1, 6, 6, 202,
209                         Arrays.asList(F.class, E.class, D.class,
210                                 C.class, B.class, A.class)},
211 
212         };
213         return objects;
214     }
215 
216     @DataProvider(name="Arrays")
arrays()217     static Object[][] arrays() {
218         return new Object[][]{
219                 {new Object[16], 16},
220                 {new boolean[16], 16},
221                 {new byte[16], 16},
222                 {new char[16], 16},
223                 {new int[16], 16},
224                 {new long[16], 16},
225                 {new short[16], 16},
226                 {new float[16], 16},
227                 {new double[16], 16},
228         };
229     }
230 
231 
232     /**
233      * Test each object and verify the classes identified by the filter,
234      * the count of calls to the filter, the max array size, max refs, max depth,
235      * max bytes.
236      * This test ignores/is not dependent on the global filter settings.
237      *
238      * @param object a Serializable object
239      * @param count the expected count of calls to the filter
240      * @param maxArray the maximum array size
241      * @param maxRefs the maximum references
242      * @param maxDepth the maximum depth
243      * @param maxBytes the maximum stream size
244      * @param classes  the expected (unique) classes
245      * @throws IOException
246      */
247     @Test(dataProvider="Objects")
t1(Object object, long count, long maxArray, long maxRefs, long maxDepth, long maxBytes, List<Class<?>> classes)248     void t1(Object object,
249                           long count, long maxArray, long maxRefs, long maxDepth, long maxBytes,
250                           List<Class<?>> classes) throws IOException {
251         byte[] bytes = writeObjects(object);
252         Validator validator = new Validator();
253         validate(bytes, validator);
254         System.out.printf("v: %s%n", validator);
255 
256         Assert.assertEquals(validator.count, count, "callback count wrong");
257         Assert.assertEquals(validator.classes, classes, "classes mismatch");
258         Assert.assertEquals(validator.maxArray, maxArray, "maxArray mismatch");
259         Assert.assertEquals(validator.maxRefs, maxRefs, "maxRefs wrong");
260         Assert.assertEquals(validator.maxDepth, maxDepth, "depth wrong");
261         Assert.assertEquals(validator.maxBytes, maxBytes, "maxBytes wrong");
262     }
263 
264     /**
265      * Test each pattern with an appropriate object.
266      * A filter is created from the pattern and used to serialize and
267      * deserialize a generated object with both the positive and negative case.
268      * This test ignores/is not dependent on the global filter settings.
269      *
270      * @param pattern a pattern
271      */
272     @Test(dataProvider="Patterns")
testPatterns(String pattern)273     void testPatterns(String pattern) {
274         evalPattern(pattern, (p, o, neg) -> testPatterns(p, o, neg));
275     }
276 
277     /**
278      * Test that the filter on a OIS can be set only on a fresh OIS,
279      * before deserializing any objects.
280      * This test is agnostic the global filter being set or not.
281      */
282     @Test
nonResettableFilter()283     void nonResettableFilter() {
284         Validator validator1 = new Validator();
285         Validator validator2 = new Validator();
286 
287         Validator[] filterCases = {
288                 validator1,     // setting filter to a non-null filter
289                 null,           // setting stream-specific filter to null
290         };
291 
292         for (Validator validator : filterCases) {
293             try {
294                 byte[] bytes = writeObjects("text1");    // an object
295 
296                 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais)) {
297                     // Check the initial filter is the global filter; may be null
298                     ObjectInputFilter global = ObjectInputFilter.Config.getSerialFilter();
299                     ObjectInputFilter initial = ois.getObjectInputFilter();
300                     Assert.assertEquals(global, initial, "initial filter should be the global filter");
301 
302                     ois.setObjectInputFilter(validator);
303                     Object o = ois.readObject();
304                     try {
305                         ois.setObjectInputFilter(validator2);
306                         Assert.fail("Should not be able to set filter twice");
307                     } catch (IllegalStateException ise) {
308                         // success, the exception was expected
309                     }
310                 } catch (EOFException eof) {
311                     Assert.fail("Should not reach end-of-file", eof);
312                 } catch (ClassNotFoundException cnf) {
313                     Assert.fail("Deserializing", cnf);
314                 }
315             } catch (IOException ex) {
316                 Assert.fail("Unexpected IOException", ex);
317             }
318         }
319     }
320 
321     /**
322      * After reading some objects from the stream, setting a filter is disallowed.
323      * If the filter was allowed to be set, it would have unpredictable behavior.
324      * Objects already read would not be checked again, including class descriptors.
325      *
326      * Note: To mitigate possible incompatibility a system property can be set
327      * to revert to the old behavior but it re-enables the incorrect use.
328      */
329     @Test
testNonSettableAfterReadObject()330     void testNonSettableAfterReadObject() throws IOException, ClassNotFoundException {
331         String expected1 = "text1";
332         String expected2 = "text2";
333         byte[] bytes = writeObjects(expected1, expected2);
334 
335         for (boolean toggle: new boolean[] {true, false}) {
336             try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
337                  ObjectInputStream ois = new ObjectInputStream(bais)) {
338                 Object actual1 = toggle ? ois.readObject() : ois.readUnshared();
339                 Assert.assertEquals(actual1, expected1, "unexpected string");
340                 // Attempt to set filter
341                 ois.setObjectInputFilter(new ObjectInputFilter() {
342                     @Override
343                     public Status checkInput(FilterInfo filterInfo) {
344                         return null;
345                     }
346                 });
347                 if (!SET_FILTER_AFTER_READ)
348                     Assert.fail("Should not be able to set filter after readObject has been called");
349             } catch (IllegalStateException ise) {
350                 // success, the exception was expected
351                 if (SET_FILTER_AFTER_READ)
352                     Assert.fail("With jdk.serialSetFilterAfterRead property set = true; " +
353                             "should be able to set the filter after a read");
354             } catch (EOFException eof) {
355                 Assert.fail("Should not reach end-of-file", eof);
356             }
357         }
358     }
359 
360     /**
361      * Test that if an Objects readReadResolve method returns an array
362      * that the callback to the filter includes the proper array length.
363      * @throws IOException if an error occurs
364      */
365     @Test(dataProvider="Arrays")
testReadResolveToArray(Object array, int length)366     void testReadResolveToArray(Object array, int length) throws IOException {
367         ReadResolveToArray object = new ReadResolveToArray(array, length);
368         byte[] bytes = writeObjects(object);
369         Object o = validate(bytes, object);    // the object is its own filter
370         Assert.assertEquals(o.getClass(), array.getClass(), "Filter not called with the array");
371     }
372 
373 
374     /**
375      * Test repeated limits use the last value.
376      * Construct a filter with the limit and the limit repeated -1.
377      * Invoke the filter with the limit to make sure it is rejected.
378      * Invoke the filter with the limit -1 to make sure it is accepted.
379      * @param name the name of the limit to test
380      * @param value a test value
381      */
382     @Test(dataProvider="Limits")
testLimits(String name, long value)383     void testLimits(String name, long value) {
384         Class<?> arrayClass = new int[0].getClass();
385         String pattern = String.format("%s=%d;%s=%d", name, value, name, value - 1);
386         ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern);
387         Assert.assertEquals(
388                 filter.checkInput(new FilterValues(arrayClass, value, value, value, value)),
389                 ObjectInputFilter.Status.REJECTED,
390                 "last limit value not used: " + filter);
391         Assert.assertEquals(
392                 filter.checkInput(new FilterValues(arrayClass, value-1, value-1, value-1, value-1)),
393                 ObjectInputFilter.Status.UNDECIDED,
394                 "last limit value not used: " + filter);
395     }
396 
397     /**
398      * Test invalid limits.
399      * Construct a filter with the limit, it should throw IllegalArgumentException.
400      * @param pattern a pattern to test
401      */
402     @Test(dataProvider="InvalidLimits", expectedExceptions=java.lang.IllegalArgumentException.class)
testInvalidLimits(String pattern)403     void testInvalidLimits(String pattern) {
404         try {
405             ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern);
406         } catch (IllegalArgumentException iae) {
407             System.out.printf("    success exception: %s%n", iae);
408             throw iae;
409         }
410     }
411 
412     /**
413      * Test that returning null from a filter causes deserialization to fail.
414      */
415     @Test(expectedExceptions=InvalidClassException.class)
testNullStatus()416     void testNullStatus() throws IOException {
417         byte[] bytes = writeObjects(0); // an Integer
418         try {
419             Object o = validate(bytes, new ObjectInputFilter() {
420                 public ObjectInputFilter.Status checkInput(ObjectInputFilter.FilterInfo f) {
421                     return null;
422                 }
423             });
424         } catch (InvalidClassException ice) {
425             System.out.printf("    success exception: %s%n", ice);
426             throw ice;
427         }
428     }
429 
430     /**
431      * Verify that malformed patterns throw IAE.
432      * @param pattern pattern from the data source
433      */
434     @Test(dataProvider="InvalidPatterns", expectedExceptions=IllegalArgumentException.class)
testInvalidPatterns(String pattern)435     void testInvalidPatterns(String pattern) {
436         try {
437             ObjectInputFilter.Config.createFilter(pattern);
438         } catch (IllegalArgumentException iae) {
439             System.out.printf("    success exception: %s%n", iae);
440             throw iae;
441         }
442     }
443 
444     /**
445      * Test that Config.create returns null if the argument does not contain any patterns or limits.
446      */
447     @Test()
testEmptyPattern()448     void testEmptyPattern() {
449         ObjectInputFilter filter = ObjectInputFilter.Config.createFilter("");
450         Assert.assertNull(filter, "empty pattern did not return null");
451 
452         filter = ObjectInputFilter.Config.createFilter(";;;;");
453         Assert.assertNull(filter, "pattern with only delimiters did not return null");
454     }
455 
456     /**
457      * Read objects from the serialized stream, validated with the filter.
458      *
459      * @param bytes a byte array to read objects from
460      * @param filter the ObjectInputFilter
461      * @return the object deserialized if any
462      * @throws IOException can be thrown
463      */
validate(byte[] bytes, ObjectInputFilter filter)464     static Object validate(byte[] bytes,
465                          ObjectInputFilter filter) throws IOException {
466         try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
467              ObjectInputStream ois = new ObjectInputStream(bais)) {
468             ois.setObjectInputFilter(filter);
469 
470             Object o = ois.readObject();
471             return o;
472         } catch (EOFException eof) {
473             // normal completion
474         } catch (ClassNotFoundException cnf) {
475             Assert.fail("Deserializing", cnf);
476         }
477         return null;
478     }
479 
480     /**
481      * Write objects and return a byte array with the bytes.
482      *
483      * @param objects zero or more objects to serialize
484      * @return the byte array of the serialized objects
485      * @throws IOException if an exception occurs
486      */
writeObjects(Object... objects)487     static byte[] writeObjects(Object... objects)  throws IOException {
488         byte[] bytes;
489         try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
490              ObjectOutputStream oos = new ObjectOutputStream(baos)) {
491             for (Object o : objects) {
492                 oos.writeObject(o);
493             }
494             bytes = baos.toByteArray();
495         }
496         return bytes;
497     }
498 
499     /**
500      * A filter that accumulates information about the checkInput callbacks
501      * that can be checked after readObject completes.
502      */
503     static class Validator implements ObjectInputFilter {
504         long count;          // Count of calls to checkInput
505         List<Class<?>> classes = new ArrayList<>();
506         long maxArray = -1;
507         long maxRefs;
508         long maxDepth;
509         long maxBytes;
510 
Validator()511         Validator() {
512         }
513 
514         @Override
checkInput(FilterInfo filter)515         public ObjectInputFilter.Status checkInput(FilterInfo filter) {
516             Class<?> serialClass = filter.serialClass();
517             System.out.printf("     checkInput: class: %s, arrayLen: %d, refs: %d, depth: %d, bytes; %d%n",
518                     serialClass, filter.arrayLength(), filter.references(),
519                     filter.depth(), filter.streamBytes());
520             count++;
521             if (serialClass != null) {
522                 if (serialClass.getName().contains("$$Lambda$")) {
523                     // TBD: proper identification of serialized Lambdas?
524                     // Fold the serialized Lambda into the SerializedLambda type
525                     classes.add(SerializedLambda.class);
526                 } else if (Proxy.isProxyClass(serialClass)) {
527                     classes.add(Proxy.class);
528                 } else {
529                     classes.add(serialClass);
530                 }
531 
532             }
533             this.maxArray = Math.max(this.maxArray, filter.arrayLength());
534             this.maxRefs = Math.max(this.maxRefs, filter.references());
535             this.maxDepth = Math.max(this.maxDepth, filter.depth());
536             this.maxBytes = Math.max(this.maxBytes, filter.streamBytes());
537             return ObjectInputFilter.Status.UNDECIDED;
538         }
539 
toString()540         public String toString(){
541             return "count: " + count
542                     + ", classes: " + classes.toString()
543                     + ", maxArray: " + maxArray
544                     + ", maxRefs: " + maxRefs
545                     + ", maxDepth: " + maxDepth
546                     + ", maxBytes: " + maxBytes;
547         }
548     }
549 
550 
551     /**
552      * Create a filter from a pattern and API factory, then serialize and
553      * deserialize an object and check allowed or reject.
554      *
555      * @param pattern the pattern
556      * @param object the test object
557      * @param allowed the expected result from ObjectInputStream (exception or not)
558      */
testPatterns(String pattern, Object object, boolean allowed)559     static void testPatterns(String pattern, Object object, boolean allowed) {
560         try {
561             byte[] bytes = SerialFilterTest.writeObjects(object);
562             ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern);
563             validate(bytes, filter);
564             Assert.assertTrue(allowed, "filter should have thrown an exception");
565         } catch (IllegalArgumentException iae) {
566             Assert.fail("bad format pattern", iae);
567         } catch (InvalidClassException ice) {
568             Assert.assertFalse(allowed, "filter should not have thrown an exception: " + ice);
569         } catch (IOException ioe) {
570             Assert.fail("Unexpected IOException", ioe);
571         }
572     }
573 
574     /**
575      * For a filter pattern, generate and apply a test object to the action.
576      * @param pattern a pattern
577      * @param action an action to perform on positive and negative cases
578      */
evalPattern(String pattern, TriConsumer<String, Object, Boolean> action)579     static void evalPattern(String pattern, TriConsumer<String, Object, Boolean> action) {
580         Object o = genTestObject(pattern, true);
581         Assert.assertNotNull(o, "success generation failed");
582         action.accept(pattern, o, true);
583 
584         // Test the negative pattern
585         o = genTestObject(pattern, false);
586         Assert.assertNotNull(o, "fail generation failed");
587         String negPattern = pattern.contains("=") ? pattern : "!" + pattern;
588         action.accept(negPattern, o, false);
589     }
590 
591     /**
592      * Generate a test object based on the pattern.
593      * Handles each of the forms of the pattern, wildcards,
594      * class name, various limit forms.
595      * @param pattern a pattern
596      * @param allowed a boolean indicating to generate the allowed or disallowed case
597      * @return an object or {@code null} to indicate no suitable object could be generated
598      */
genTestObject(String pattern, boolean allowed)599     static Object genTestObject(String pattern, boolean allowed) {
600         if (pattern.contains("=")) {
601             return genTestLimit(pattern, allowed);
602         } else if (pattern.endsWith("*")) {
603             return genTestObjectWildcard(pattern, allowed);
604         } else {
605             // class
606             // isolate module name, if any
607             int poffset = 0;
608             int soffset = pattern.indexOf('/', poffset);
609             String module = null;
610             if (soffset >= 0) {
611                 poffset = soffset + 1;
612                 module = pattern.substring(0, soffset);
613             }
614             try {
615                 Class<?> clazz = Class.forName(pattern.substring(poffset));
616                 Constructor<?> cons = clazz.getConstructor();
617                 return cons.newInstance();
618             } catch (ClassNotFoundException ex) {
619                 Assert.fail("no such class available: " + pattern);
620             } catch (InvocationTargetException
621                     | NoSuchMethodException
622                     | InstantiationException
623                     | IllegalAccessException ex1) {
624                 Assert.fail("newInstance: " + ex1);
625             }
626         }
627         return null;
628     }
629 
630     /**
631      * Generate an object to be used with the various wildcard pattern forms.
632      * Explicitly supports only specific package wildcards with specific objects.
633      * @param pattern a wildcard pattern ending in "*"
634      * @param allowed a boolean indicating to generate the allowed or disallowed case
635      * @return an object within or outside the wildcard
636      */
genTestObjectWildcard(String pattern, boolean allowed)637     static Object genTestObjectWildcard(String pattern, boolean allowed) {
638         if (pattern.endsWith(".**")) {
639             // package hierarchy wildcard
640             if (pattern.startsWith("javax.net.")) {
641                 return SSLEngineResult.Status.BUFFER_OVERFLOW;
642             }
643             if (pattern.startsWith("java.")) {
644                 return 4;
645             }
646             if (pattern.startsWith("javax.")) {
647                 return SSLEngineResult.Status.BUFFER_UNDERFLOW;
648             }
649             return otherObject;
650         } else if (pattern.endsWith(".*")) {
651             // package wildcard
652             if (pattern.startsWith("javax.net.ssl")) {
653                 return SSLEngineResult.Status.BUFFER_UNDERFLOW;
654             }
655         } else {
656             // class wildcard
657             if (pattern.equals("*")) {
658                 return otherObject; // any object will do
659             }
660             if (pattern.startsWith("java.util.Hash")) {
661                 return new Hashtable<String, String>();
662             }
663         }
664         Assert.fail("Object could not be generated for pattern: "
665                 + pattern
666                 + ", allowed: " + allowed);
667         return null;
668     }
669 
670     /**
671      * Generate a limit test object for the pattern.
672      * For positive cases, the object exactly hits the limit.
673      * For negative cases, the object is 1 greater than the limit
674      * @param pattern the pattern, containing "=" and a maxXXX keyword
675      * @param allowed a boolean indicating to generate the allowed or disallowed case
676      * @return a sitable object
677      */
genTestLimit(String pattern, boolean allowed)678     static Object genTestLimit(String pattern, boolean allowed) {
679         int ndx = pattern.indexOf('=');
680         Assert.assertNotEquals(ndx, -1, "missing value in limit");
681         long value = Long.parseUnsignedLong(pattern.substring(ndx+1));
682         if (pattern.startsWith("maxdepth=")) {
683             // Return an object with the requested depth (or 1 greater)
684             long depth = allowed ? value : value + 1;
685             Object[] array = new Object[1];
686             for (int i = 1; i < depth; i++) {
687                 Object[] n = new Object[1];
688                 n[0] = array;
689                 array = n;
690             }
691             return array;
692         } else if (pattern.startsWith("maxbytes=")) {
693             // Return a byte array that when written to OOS creates
694             // a stream of exactly the size requested.
695             return genMaxBytesObject(allowed, value);
696         } else if (pattern.startsWith("maxrefs=")) {
697             // 4 references to classes in addition to the array contents
698             Object[] array = new Object[allowed ? (int)value - 4 : (int)value - 3];
699             for (int i = 0; i < array.length; i++) {
700                 array[i] = otherObject;
701             }
702             return array;
703         } else if (pattern.startsWith("maxarray=")) {
704                 return allowed ? new int[(int)value] : new int[(int)value+1];
705         }
706         Assert.fail("Object could not be generated for pattern: "
707                 + pattern
708                 + ", allowed: " + allowed);
709         return null;
710     }
711 
712     /**
713      * Generate an an object that will be serialized to some number of bytes.
714      * Or 1 greater if allowed is false.
715      * It returns a two element Object array holding a byte array sized
716      * to achieve the desired total size.
717      * @param allowed true if the stream should be allowed at that size,
718      *                false if the stream should be larger
719      * @param maxBytes the number of bytes desired in the stream;
720      *                 should not be less than 72 (due to protocol overhead).
721      * @return a object that will be serialized to the length requested
722      */
genMaxBytesObject(boolean allowed, long maxBytes)723     private static Object genMaxBytesObject(boolean allowed, long maxBytes) {
724         Object[] holder = new Object[2];
725         long desiredSize = allowed ? maxBytes : maxBytes + 1;
726         long actualSize = desiredSize;
727         long byteSize = desiredSize - 72;  // estimate needed array size
728         do {
729             byteSize += (desiredSize - actualSize);
730             byte[] a = new byte[(int)byteSize];
731             holder[0] = a;
732             holder[1] = a;
733             try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
734                  ObjectOutputStream os = new ObjectOutputStream(baos)) {
735                 os.writeObject(holder);
736                 os.flush();
737                 actualSize = baos.size();
738             } catch (IOException ie) {
739                 Assert.fail("exception generating stream", ie);
740             }
741         } while (actualSize != desiredSize);
742         return holder;
743     }
744 
745     /**
746      * Returns a HashSet of a requested depth.
747      * @param depth the depth
748      * @return a HashSet of HashSets...
749      */
deepHashSet(int depth)750     static HashSet<Object> deepHashSet(int depth) {
751         HashSet<Object> hashSet = new HashSet<>();
752         HashSet<Object> s1 = hashSet;
753         HashSet<Object> s2 = new HashSet<>();
754         for (int i = 0; i < depth; i++ ) {
755             HashSet<Object> t1 = new HashSet<>();
756             HashSet<Object> t2 = new HashSet<>();
757             // make t1 not equal to t2
758             t1.add("by Jimminy");
759             s1.add(t1);
760             s1.add(t2);
761             s2.add(t1);
762             s2.add(t2);
763             s1 = t1;
764             s2 = t2;
765         }
766         return hashSet;
767     }
768 
769     /**
770      * Simple method to use with Serialized Lambda.
771      */
noop()772     private static void noop() {}
773 
774 
775     /**
776      * Class that returns an array from readResolve and also implements
777      * the ObjectInputFilter to check that it has the expected length.
778      */
779     static class ReadResolveToArray implements Serializable, ObjectInputFilter {
780         @Serial
781         private static final long serialVersionUID = 123456789L;
782 
783         @SuppressWarnings("serial") /* Incorrect declarations are being tested */
784         private final Object array;
785         private final int length;
786 
ReadResolveToArray(Object array, int length)787         ReadResolveToArray(Object array, int length) {
788             this.array = array;
789             this.length = length;
790         }
791 
792         @Serial
readResolve()793         Object readResolve() {
794             return array;
795         }
796 
797         @Override
checkInput(FilterInfo filter)798         public ObjectInputFilter.Status checkInput(FilterInfo filter) {
799             if (ReadResolveToArray.class.isAssignableFrom(filter.serialClass())) {
800                 return ObjectInputFilter.Status.ALLOWED;
801             }
802             if (filter.serialClass() != array.getClass() ||
803                     (filter.arrayLength() >= 0 && filter.arrayLength() != length)) {
804                 return ObjectInputFilter.Status.REJECTED;
805             }
806             return ObjectInputFilter.Status.UNDECIDED;
807         }
808 
809     }
810 
811     /**
812      * Hold a snapshot of values to be passed to an ObjectInputFilter.
813      */
814     static class FilterValues implements ObjectInputFilter.FilterInfo {
815         private final Class<?> clazz;
816         private final long arrayLength;
817         private final long depth;
818         private final long references;
819         private final long streamBytes;
820 
FilterValues(Class<?> clazz, long arrayLength, long depth, long references, long streamBytes)821         public FilterValues(Class<?> clazz, long arrayLength, long depth, long references, long streamBytes) {
822             this.clazz = clazz;
823             this.arrayLength = arrayLength;
824             this.depth = depth;
825             this.references = references;
826             this.streamBytes = streamBytes;
827         }
828 
829         @Override
serialClass()830         public Class<?> serialClass() {
831             return clazz;
832         }
833 
arrayLength()834         public long arrayLength() {
835             return arrayLength;
836         }
837 
depth()838         public long depth() {
839             return depth;
840         }
841 
references()842         public long references() {
843             return references;
844         }
845 
streamBytes()846         public long streamBytes() {
847             return streamBytes;
848         }
849     }
850 
851     // Deeper superclass hierarchy
852     static class A implements Serializable {
853         @Serial
854         private static final long serialVersionUID = 1L;
855     };
856     static class B extends A {
857         @Serial
858         private static final long serialVersionUID = 2L;
859     }
860     static class C extends B {
861         @Serial
862         private static final long serialVersionUID = 3L;
863     }
864     static class D extends C {
865         @Serial
866         private static final long serialVersionUID = 4L;
867     }
868     static class E extends D {
869         @Serial
870         private static final long serialVersionUID = 5L;
871     }
872     static class F extends E {
873         @Serial
874         private static final long serialVersionUID = 6L;
875     }
876 
877 }
878