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