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