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