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