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 Basic tests for SUID in the serial stream
28  * @run testng SerialVersionUIDTest
29  * @run testng/othervm/java.security.policy=empty_security.policy SerialVersionUIDTest
30  */
31 package test.java.io.Serializable.records;
32 
33 import java.io.ByteArrayInputStream;
34 import java.io.ByteArrayOutputStream;
35 import java.io.DataInputStream;
36 import java.io.DataOutputStream;
37 import java.io.IOException;
38 import java.io.ObjectInputStream;
39 import java.io.ObjectOutputStream;
40 import java.io.Serializable;
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.stream.LongStream;
44 import org.testng.annotations.DataProvider;
45 import org.testng.annotations.Test;
46 import static java.io.ObjectStreamConstants.*;
47 import static java.lang.System.out;
48 import static org.testng.Assert.assertEquals;
49 import static org.testng.Assert.assertTrue;
50 
51 public class SerialVersionUIDTest {
52 
53     record R1 () implements Serializable {
54         private static final long serialVersionUID = 1L;
55     }
56 
57     record R2 (int x, int y) implements Serializable {
58         private static final long serialVersionUID = 0L;
59     }
60 
61     record R3 () implements Serializable { }
62 
63     record R4 (String s) implements Serializable { }
64 
65     record R5 (long l) implements Serializable {
66         private static final long serialVersionUID = 5678L;
67     }
68 
69     @DataProvider(name = "recordObjects")
recordObjects()70     public Object[][] recordObjects() {
71         return new Object[][] {
72             new Object[] { new R1(),        1L    },
73             new Object[] { new R2(1, 2),    0L    },
74             new Object[] { new R3(),        0L    },
75             new Object[] { new R4("s"),     0L    },
76             new Object[] { new R5(7L),      5678L },
77         };
78     }
79 
80     /**
81      * Tests that a declared SUID for a record class is inserted into the stream.
82      */
83     @Test(dataProvider = "recordObjects")
testSerialize(Object objectToSerialize, long expectedUID)84     public void testSerialize(Object objectToSerialize, long expectedUID)
85         throws Exception
86     {
87         out.println("\n---");
88         out.println("serializing : " + objectToSerialize);
89         byte[] bytes = serialize(objectToSerialize);
90 
91         ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
92         DataInputStream dis = new DataInputStream(bais);
93 
94         // sanity
95         assertEquals(dis.readShort(), STREAM_MAGIC);
96         assertEquals(dis.readShort(), STREAM_VERSION);
97         assertEquals(dis.readByte(), TC_OBJECT);
98         assertEquals(dis.readByte(), TC_CLASSDESC);
99         assertEquals(dis.readUTF(), objectToSerialize.getClass().getName());
100 
101         // verify that the UID is as expected
102         assertEquals(dis.readLong(), expectedUID);
103     }
104 
105     @DataProvider(name = "recordClasses")
recordClasses()106     public Object[][] recordClasses() {
107         List<Object[]> list = new ArrayList<>();
108         List<Class<?>> recordClasses = List.of(R1.class, R2.class, R3.class, R4.class, R5.class);
109         LongStream.of(0L, 1L, 100L, 10_000L, 1_000_000L).forEach(suid ->
110                 recordClasses.stream()
111                              .map(cl -> new Object[] {cl, suid})
112                              .forEach(list::add));
113         return list.stream().toArray(Object[][]::new);
114     }
115 
116     /**
117      * Tests that matching of the serialVersionUID values ( stream value
118      * and runtime class value ) is waived for record classes.
119      */
120     @Test(dataProvider = "recordClasses")
testSerializeFromClass(Class<? extends Record> cl, long suid)121     public void testSerializeFromClass(Class<? extends Record> cl, long suid)
122         throws Exception
123     {
124         out.println("\n---");
125         byte[] bytes = byteStreamFor(cl.getName(), suid);
126         Object obj = deserialize(bytes);
127         assertEquals(obj.getClass(), cl);
128         assertTrue(obj.getClass().isRecord());
129     }
130 
131     // --- infra
132 
133     /**
134      * Returns a stream of bytes for the given class and uid. The
135      * stream will have no stream field values.
136      */
byteStreamFor(String className, long uid)137     static byte[] byteStreamFor(String className, long uid)
138         throws Exception
139     {
140         ByteArrayOutputStream baos = new ByteArrayOutputStream();
141         DataOutputStream dos = new DataOutputStream(baos);
142         dos.writeShort(STREAM_MAGIC);
143         dos.writeShort(STREAM_VERSION);
144         dos.writeByte(TC_OBJECT);
145         dos.writeByte(TC_CLASSDESC);
146         dos.writeUTF(className);
147         dos.writeLong(uid);
148         dos.writeByte(SC_SERIALIZABLE);
149         dos.writeShort(0);                // number of fields
150         dos.writeByte(TC_ENDBLOCKDATA);   // no annotations
151         dos.writeByte(TC_NULL);           // no superclasses
152         dos.close();
153         return baos.toByteArray();
154     }
155 
serialize(T obj)156     static <T> byte[] serialize(T obj) throws IOException {
157         ByteArrayOutputStream baos = new ByteArrayOutputStream();
158         ObjectOutputStream oos = new ObjectOutputStream(baos);
159         oos.writeObject(obj);
160         oos.close();
161         return baos.toByteArray();
162     }
163 
deserialize(byte[] streamBytes)164     static <T> T deserialize(byte[] streamBytes)
165         throws IOException, ClassNotFoundException
166     {
167         ByteArrayInputStream bais = new ByteArrayInputStream(streamBytes);
168         ObjectInputStream ois  = new ObjectInputStream(bais);
169         return (T) ois.readObject();
170     }
171 }
172