1 /*
2  * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /*
25  * @test
26  * @bug 8246774
27  * @summary Tests constructor invocation exceptions are handled appropriately
28  * @run testng ThrowingConstructorTest
29  * @run testng/othervm/java.security.policy=empty_security.policy ThrowingConstructorTest
30  */
31 package test.java.io.Serializable.records;
32 
33 import java.io.ByteArrayInputStream;
34 import java.io.ByteArrayOutputStream;
35 import java.io.IOException;
36 import java.io.InvalidObjectException;
37 import java.io.ObjectInputStream;
38 import java.io.ObjectOutputStream;
39 import java.io.Serializable;
40 import org.testng.annotations.DataProvider;
41 import org.testng.annotations.Test;
42 import static java.lang.System.out;
43 import static org.testng.Assert.assertEquals;
44 import static org.testng.Assert.assertTrue;
45 import static org.testng.Assert.expectThrows;
46 
47 /**
48  * If the constructor invocation throws an exception, an
49  * `InvalidObjectException` is thrown with that exception as its cause.
50  */
51 public class ThrowingConstructorTest {
52 
53     /** "big switch" that can be used to allow/disallow record construction
54      * set to true after the data provider has constructed all record objects */
55     private static volatile boolean firstDataSetCreated;
56 
57     record R1 () implements Serializable {
R1()58         public R1() {
59             if (firstDataSetCreated)
60                 throw new NullPointerException("thrown from R1");
61         }
62     }
63 
64     record R2 (int x) implements Serializable {
R2(int x)65         public R2(int x) {
66             if (firstDataSetCreated)
67                 throw new IllegalArgumentException("thrown from R2");
68             this.x = x;
69         }
70     }
71 
72     record R3 (int x, int y) implements Serializable {
R3(int x, int y)73         public R3(int x, int y) {
74             if (firstDataSetCreated)
75                 throw new NumberFormatException("thrown from R3");
76             this.x = x;
77             this.y = y;
78         }
79     }
80 
81     static class C implements Serializable {
82         final Object obj ;
C(Object obj)83         C(Object obj) { this.obj= obj; }
toString()84         @Override public String toString() { return "C[" + obj + "]"; }
85     }
86 
87     static final Class<InvalidObjectException> IOE = InvalidObjectException.class;
88 
89     @DataProvider(name = "exceptionInstances")
exceptionInstances()90     public Object[][] exceptionInstances() {
91         Object[][] objs =  new Object[][] {
92             new Object[] { new R1(),            NullPointerException.class,     "thrown from R1" },
93             new Object[] { new R2(1),           IllegalArgumentException.class, "thrown from R2" },
94             new Object[] { new R3(2, 3),        NumberFormatException .class,   "thrown from R3" },
95             new Object[] { new C(new R1()),     NullPointerException.class,     "thrown from R1" },
96             new Object[] { new C(new R2(4)),    IllegalArgumentException.class, "thrown from R2" },
97             new Object[] { new C(new R3(5, 6)), NumberFormatException .class,   "thrown from R3" },
98         };
99         firstDataSetCreated = true;
100         return  objs;
101     }
102 
103     @Test(dataProvider = "exceptionInstances")
testExceptions(Object objectToSerialize, Class<? extends Throwable> expectedExType, String expectedExMessage)104     public void testExceptions(Object objectToSerialize,
105                                Class<? extends Throwable> expectedExType,
106                                String expectedExMessage)
107         throws Exception
108     {
109         out.println("\n---");
110         out.println("serializing: " + objectToSerialize);
111         byte[] bytes = serialize(objectToSerialize);
112         InvalidObjectException ioe = expectThrows(IOE, () -> deserialize(bytes));
113         out.println("caught expected IOE: " + ioe);
114         Throwable t = ioe.getCause();
115         assertTrue(t.getClass().equals(expectedExType),
116                    "Expected:" + expectedExType + ", got:" + t);
117         out.println("expected cause " + expectedExType +" : " + t);
118         assertEquals(t.getMessage(), expectedExMessage);
119     }
120 
121     //  -- errors ( pass through unwrapped )
122 
123     private static volatile boolean secondDataSetCreated;
124 
125     record R4 () implements Serializable {
R4()126         public R4() {
127             if (secondDataSetCreated)
128                 throw new OutOfMemoryError("thrown from R4"); }
129     }
130 
131     record R5 (int x) implements Serializable {
R5(int x)132         public R5(int x) {
133             if (secondDataSetCreated)
134                 throw new StackOverflowError("thrown from R5");
135             this.x = x;
136         }
137     }
138 
139     record R6 (int x, int y) implements Serializable {
R6(int x, int y)140         public R6(int x, int y) {
141             if (secondDataSetCreated)
142                 throw new AssertionError("thrown from R6");
143             this.x = x;
144             this.y = y;
145         }
146     }
147 
148     @DataProvider(name = "errorInstances")
errorInstances()149     public Object[][] errorInstances() {
150         Object[][] objs =  new Object[][] {
151             new Object[] { new R4(),              OutOfMemoryError.class,   "thrown from R4" },
152             new Object[] { new R5(11),            StackOverflowError.class, "thrown from R5" },
153             new Object[] { new R6(12, 13),        AssertionError .class,    "thrown from R6" },
154             new Object[] { new C(new R4()),       OutOfMemoryError.class,   "thrown from R4" },
155             new Object[] { new C(new R5(14)),     StackOverflowError.class, "thrown from R5" },
156             new Object[] { new C(new R6(15, 16)), AssertionError .class,    "thrown from R6" },
157         };
158         secondDataSetCreated = true;
159         return objs;
160     }
161 
162     @Test(dataProvider = "errorInstances")
testErrors(Object objectToSerialize, Class<? extends Throwable> expectedExType, String expectedExMessage)163     public void testErrors(Object objectToSerialize,
164                            Class<? extends Throwable> expectedExType,
165                            String expectedExMessage)
166         throws Exception
167     {
168         out.println("\n---");
169         out.println("serializing: " + objectToSerialize);
170         byte[] bytes = serialize(objectToSerialize);
171         Throwable t = expectThrows(expectedExType, () -> deserialize(bytes));
172         assertTrue(t.getClass().equals(expectedExType),
173                    "Expected:" + expectedExType + ", got:" + t);
174         out.println("caught expected " + expectedExType +" : " + t);
175         assertEquals(t.getMessage(), expectedExMessage);
176     }
177 
178     // --- infra
179 
serialize(T obj)180     static <T> byte[] serialize(T obj) throws IOException {
181         ByteArrayOutputStream baos = new ByteArrayOutputStream();
182         ObjectOutputStream oos = new ObjectOutputStream(baos);
183         oos.writeObject(obj);
184         oos.close();
185         return baos.toByteArray();
186     }
187 
188     @SuppressWarnings("unchecked")
deserialize(byte[] streamBytes)189     static <T> T deserialize(byte[] streamBytes)
190         throws IOException, ClassNotFoundException
191     {
192         ByteArrayInputStream bais = new ByteArrayInputStream(streamBytes);
193         ObjectInputStream ois  = new ObjectInputStream(bais);
194         return (T) ois.readObject();
195     }
196 
serializeDeserialize(T obj)197     static <T> T serializeDeserialize(T obj)
198         throws IOException, ClassNotFoundException
199     {
200         return deserialize(serialize(obj));
201     }
202 }
203