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