1 /*
2  * Copyright (c) 2019, 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  * @summary Tests constructor invocation exceptions are handled appropriately
27  * @compile --enable-preview -source ${jdk.version} ThrowingConstructorTest.java
28  * @run testng/othervm --enable-preview ThrowingConstructorTest
29  * @run testng/othervm/java.security.policy=empty_security.policy --enable-preview ThrowingConstructorTest
30  */
31 
32 import java.io.ByteArrayInputStream;
33 import java.io.ByteArrayOutputStream;
34 import java.io.IOException;
35 import java.io.InvalidObjectException;
36 import java.io.ObjectInputStream;
37 import java.io.ObjectOutputStream;
38 import java.io.Serializable;
39 import org.testng.annotations.DataProvider;
40 import org.testng.annotations.Test;
41 import static java.lang.System.out;
42 import static org.testng.Assert.assertEquals;
43 import static org.testng.Assert.assertTrue;
44 import static org.testng.Assert.expectThrows;
45 
46 /**
47  * If the constructor invocation throws an exception, an
48  * `InvalidObjectException` is thrown with that exception as its cause.
49  */
50 public class ThrowingConstructorTest {
51 
52     /** "big switch" that can be used to allow/disallow record construction
53      * set to true after the data provider has constructed all record objects */
54     private static volatile boolean firstDataSetCreated;
55 
56     record R1 () implements Serializable {
R1()57         public R1() {
58             if (firstDataSetCreated)
59                 throw new NullPointerException("thrown from R1");
60         }
61     }
62 
63     record R2 (int x) implements Serializable {
R2(int x)64         public R2(int x) {
65             if (firstDataSetCreated)
66                 throw new IllegalArgumentException("thrown from R2");
67             this.x = x;
68         }
69     }
70 
71     record R3 (int x, int y) implements Serializable {
R3(int x, int y)72         public R3(int x, int y) {
73             if (firstDataSetCreated)
74                 throw new NumberFormatException("thrown from R3");
75             this.x = x;
76             this.y = y;
77         }
78     }
79 
80     static class C implements Serializable {
81         final Object obj ;
C(Object obj)82         C(Object obj) { this.obj= obj; }
toString()83         @Override public String toString() { return "C[" + obj + "]"; }
84     }
85 
86     static final Class<InvalidObjectException> IOE = InvalidObjectException.class;
87 
88     @DataProvider(name = "exceptionInstances")
exceptionInstances()89     public Object[][] exceptionInstances() {
90         Object[][] objs =  new Object[][] {
91             new Object[] { new R1(),            NullPointerException.class,     "thrown from R1" },
92             new Object[] { new R2(1),           IllegalArgumentException.class, "thrown from R2" },
93             new Object[] { new R3(2, 3),        NumberFormatException .class,   "thrown from R3" },
94             new Object[] { new C(new R1()),     NullPointerException.class,     "thrown from R1" },
95             new Object[] { new C(new R2(4)),    IllegalArgumentException.class, "thrown from R2" },
96             new Object[] { new C(new R3(5, 6)), NumberFormatException .class,   "thrown from R3" },
97         };
98         firstDataSetCreated = true;
99         return  objs;
100     }
101 
102     @Test(dataProvider = "exceptionInstances")
testExceptions(Object objectToSerialize, Class<? extends Throwable> expectedExType, String expectedExMessage)103     public void testExceptions(Object objectToSerialize,
104                                Class<? extends Throwable> expectedExType,
105                                String expectedExMessage)
106         throws Exception
107     {
108         out.println("\n---");
109         out.println("serializing: " + objectToSerialize);
110         byte[] bytes = serialize(objectToSerialize);
111         InvalidObjectException ioe = expectThrows(IOE, () -> deserialize(bytes));
112         out.println("caught expected IOE: " + ioe);
113         Throwable t = ioe.getCause();
114         assertTrue(t.getClass().equals(expectedExType),
115                    "Expected:" + expectedExType + ", got:" + t);
116         out.println("expected cause " + expectedExType +" : " + t);
117         assertEquals(t.getMessage(), expectedExMessage);
118     }
119 
120     //  -- errors ( pass through unwrapped )
121 
122     private static volatile boolean secondDataSetCreated;
123 
124     record R4 () implements Serializable {
R4()125         public R4() {
126             if (secondDataSetCreated)
127                 throw new OutOfMemoryError("thrown from R4"); }
128     }
129 
130     record R5 (int x) implements Serializable {
R5(int x)131         public R5(int x) {
132             if (secondDataSetCreated)
133                 throw new StackOverflowError("thrown from R5");
134             this.x = x;
135         }
136     }
137 
138     record R6 (int x, int y) implements Serializable {
R6(int x, int y)139         public R6(int x, int y) {
140             if (secondDataSetCreated)
141                 throw new AssertionError("thrown from R6");
142             this.x = x;
143             this.y = y;
144         }
145     }
146 
147     @DataProvider(name = "errorInstances")
errorInstances()148     public Object[][] errorInstances() {
149         Object[][] objs =  new Object[][] {
150             new Object[] { new R4(),              OutOfMemoryError.class,   "thrown from R4" },
151             new Object[] { new R5(11),            StackOverflowError.class, "thrown from R5" },
152             new Object[] { new R6(12, 13),        AssertionError .class,    "thrown from R6" },
153             new Object[] { new C(new R4()),       OutOfMemoryError.class,   "thrown from R4" },
154             new Object[] { new C(new R5(14)),     StackOverflowError.class, "thrown from R5" },
155             new Object[] { new C(new R6(15, 16)), AssertionError .class,    "thrown from R6" },
156         };
157         secondDataSetCreated = true;
158         return objs;
159     }
160 
161     @Test(dataProvider = "errorInstances")
testErrors(Object objectToSerialize, Class<? extends Throwable> expectedExType, String expectedExMessage)162     public void testErrors(Object objectToSerialize,
163                            Class<? extends Throwable> expectedExType,
164                            String expectedExMessage)
165         throws Exception
166     {
167         out.println("\n---");
168         out.println("serializing: " + objectToSerialize);
169         byte[] bytes = serialize(objectToSerialize);
170         Throwable t = expectThrows(expectedExType, () -> deserialize(bytes));
171         assertTrue(t.getClass().equals(expectedExType),
172                    "Expected:" + expectedExType + ", got:" + t);
173         out.println("caught expected " + expectedExType +" : " + t);
174         assertEquals(t.getMessage(), expectedExMessage);
175     }
176 
177     // --- infra
178 
serialize(T obj)179     static <T> byte[] serialize(T obj) throws IOException {
180         ByteArrayOutputStream baos = new ByteArrayOutputStream();
181         ObjectOutputStream oos = new ObjectOutputStream(baos);
182         oos.writeObject(obj);
183         oos.close();
184         return baos.toByteArray();
185     }
186 
187     @SuppressWarnings("unchecked")
deserialize(byte[] streamBytes)188     static <T> T deserialize(byte[] streamBytes)
189         throws IOException, ClassNotFoundException
190     {
191         ByteArrayInputStream bais = new ByteArrayInputStream(streamBytes);
192         ObjectInputStream ois  = new ObjectInputStream(bais);
193         return (T) ois.readObject();
194     }
195 
serializeDeserialize(T obj)196     static <T> T serializeDeserialize(T obj)
197         throws IOException, ClassNotFoundException
198     {
199         return deserialize(serialize(obj));
200     }
201 }
202