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 No Serialization support of primitive classes, without a proxy
 27  * @build Point Line NonFlattenValue Serialization
 28  * @run testng/othervm Serialization
 29  */
 30 
 31 import java.io.ByteArrayInputStream;
 32 import java.io.ByteArrayOutputStream;
 33 import java.io.DataOutputStream;
 34 import java.io.Externalizable;
 35 import java.io.IOException;
 36 import java.io.InvalidClassException;
 37 import java.io.InvalidObjectException;
 38 import java.io.NotSerializableException;
 39 import java.io.ObjectInput;
 40 import java.io.ObjectInputStream;
 41 import java.io.ObjectOutput;
 42 import java.io.ObjectOutputStream;
 43 import java.io.ObjectStreamClass;
 44 import java.io.ObjectStreamException;
 45 import java.io.Serializable;
 46 import org.testng.annotations.DataProvider;
 47 import org.testng.annotations.Test;
 48 import static java.io.ObjectStreamConstants.*;
 49 import static org.testng.Assert.assertEquals;
 50 import static org.testng.Assert.assertThrows;
 51 import static org.testng.Assert.expectThrows;
 52 
 53 public class Serialization {
 54 
 55     static final Class<NotSerializableException> NSE = NotSerializableException.class;
 56 
 57     @DataProvider(name = "doesNotImplementSerializable")
 58     public Object[][] doesNotImplementSerializable() {
 59         return new Object[][] {
 60             new Object[] { Point.makePoint(10, 100) },
 61             new Object[] { Line.makeLine(Point.makePoint(99, 99),
 62                                          Point.makePoint(888, 888)) },
 63             new Object[] { NonFlattenValue.make(1001, 10005) },
 64             // an array of Points
 65             new Object[] { new Point[] {
 66                     Point.makePoint(1, 5),
 67                     Point.makePoint(2, 6) } },
 68             new Object[] { new Object[] {
 69                     Point.makePoint(3, 7),
 70                     Point.makePoint(4, 8) } },
 71         };
 72     }
 73 
 74     // primitive class that DOES NOT implement Serializable should throw NSE
 75     @Test(dataProvider = "doesNotImplementSerializable")
 76     public void doesNotImplementSerializable(Object obj) {
 77         assertThrows(NSE, () -> serialize(obj));
 78     }
 79 
 80     /** A Serializable Point */
 81     static primitive class SerializablePoint implements Serializable {
 82         public int x;
 83         public int y;
 84         SerializablePoint(int x, int y) { this.x = x; this.y = y; }
 85         static SerializablePoint make(int x, int y) {
 86             return new SerializablePoint(x, y);
 87         }
 88         @Override public String toString() {
 89             return "[SerializablePoint x=" + x + " y=" + y + "]"; }
 90     }
 91 
 92     /** An Externalizable Point */
 93     static primitive class ExternalizablePoint implements Externalizable {
 94         public int x;
 95         public int y;
 96         ExternalizablePoint(int x, int y) { this.x = x; this.y = y; }
 97         static ExternalizablePoint make(int x, int y) {
 98             return new ExternalizablePoint(x, y);
 99         }
100         @Override public void readExternal(ObjectInput in) {  }
101         @Override public void writeExternal(ObjectOutput out) {  }
102         @Override public String toString() {
103             return "[ExternalizablePoint x=" + x + " y=" + y + "]"; }
104     }
105 
106     @DataProvider(name = "doesImplementSerializable")
107     public Object[][] doesImplementSerializable() {
108         return new Object[][] {
109             new Object[] { SerializablePoint.make(11, 101) },
110             new Object[] { ExternalizablePoint.make(12, 102) },
111             new Object[] { new ExternalizablePoint[] {
112                     ExternalizablePoint.make(3, 7),
113                     ExternalizablePoint.make(2, 8) } },
114             new Object[] { new SerializablePoint[] {
115                     SerializablePoint.make(1, 5),
116                     SerializablePoint.make(2, 6) } },
117             new Object[] { new Object[] {
118                     SerializablePoint.make(3, 7),
119                     SerializablePoint.make(4, 8) } },
120             new Object[] { new Object[] {
121                     ExternalizablePoint.make(13, 17),
122                     ExternalizablePoint.make(14, 18) } },
123         };
124     }
125 
126     // primitive class that DOES implement Serializable should throw NSE
127     @Test(dataProvider = "doesImplementSerializable")
128     public void doesImplementSerializable(Object obj) {
129         assertThrows(NSE, () -> serialize(obj));
130     }
131 
132     /** A Serializable Foo, with a serial proxy */
133     static primitive class SerializableFoo implements Serializable {
134         public int x;
135         SerializableFoo(int x) { this.x = x; }
136         static SerializableFoo make(int x) {
137             return new SerializableFoo(x);
138         }
139         Object writeReplace() throws ObjectStreamException {
140             return new SerialFooProxy(x);
141         }
142         private void readObject(ObjectInputStream s) throws InvalidObjectException {
143             throw new InvalidObjectException("Proxy required");
144         }
145         private static class SerialFooProxy implements Serializable {
146             final int x;
147             SerialFooProxy(int x) { this.x = x; }
148             Object readResolve() throws ObjectStreamException {
149                 return SerializableFoo.make(this.x);
150             }
151         }
152     }
153 
154     /** An Externalizable Foo, with a serial proxy */
155     static primitive class ExternalizableFoo implements Externalizable {
156         public String s;
157         ExternalizableFoo(String s) {  this.s = s; }
158         static ExternalizableFoo make(String s) {
159             return new ExternalizableFoo(s);
160         }
161         Object writeReplace() throws ObjectStreamException {
162             return new SerialFooProxy(s);
163         }
164         private void readObject(ObjectInputStream s) throws InvalidObjectException {
165             throw new InvalidObjectException("Proxy required");
166         }
167         private static class SerialFooProxy implements Serializable {
168             final String s;
169             SerialFooProxy(String s) { this.s = s; }
170             Object readResolve() throws ObjectStreamException {
171                 return ExternalizableFoo.make(this.s);
172             }
173         }
174         @Override public void readExternal(ObjectInput in) {  }
175         @Override public void writeExternal(ObjectOutput out) {  }
176     }
177 
178     // primitive classes that DO implement Serializable, but have a serial proxy
179     @Test
180     public void serializableFooWithProxy() throws Exception {
181         SerializableFoo foo = SerializableFoo.make(45);
182         SerializableFoo foo1 = serializeDeserialize(foo);
183         assertEquals(foo.x, foo1.x);
184     }
185     @Test
186     public void serializableFooArrayWithProxy() throws Exception {
187         SerializableFoo[] fooArray = new SerializableFoo[]{SerializableFoo.make(46)};
188         SerializableFoo[] fooArray1 = serializeDeserialize(fooArray);
189         assertEquals(fooArray.length, fooArray1.length);
190         assertEquals(fooArray[0].x, fooArray1[0].x);
191     }
192     @Test
193     public void externalizableFooWithProxy() throws Exception {
194         ExternalizableFoo foo = ExternalizableFoo.make("hello");
195         ExternalizableFoo foo1 = serializeDeserialize(foo);
196         assertEquals(foo.s, foo1.s);
197     }
198     @Test
199     public void externalizableFooArrayWithProxy() throws Exception {
200         ExternalizableFoo[] fooArray = new ExternalizableFoo[] { ExternalizableFoo.make("there") };
201         ExternalizableFoo[] fooArray1 = serializeDeserialize(fooArray);
202         assertEquals(fooArray.length, fooArray1.length);
203         assertEquals(fooArray[0].s, fooArray1[0].s);
204     }
205 
206     private static byte[] byteStreamFor(String className, long uid, byte flags)
207         throws Exception
208     {
209         ByteArrayOutputStream baos = new ByteArrayOutputStream();
210         DataOutputStream dos = new DataOutputStream(baos);
211         dos.writeShort(STREAM_MAGIC);
212         dos.writeShort(STREAM_VERSION);
213         dos.writeByte(TC_OBJECT);
214         dos.writeByte(TC_CLASSDESC);
215         dos.writeUTF(className);
216         dos.writeLong(uid);
217         dos.writeByte(flags);
218         dos.writeShort(0);             // number of fields
219         dos.writeByte(TC_ENDBLOCKDATA);   // no annotations
220         dos.writeByte(TC_NULL);           // no superclasses
221         dos.close();
222         return baos.toByteArray();
223     }
224 
225     private static byte[] serializableByteStreamFor(String className, long uid)
226         throws Exception
227     {
228         return byteStreamFor(className, uid, SC_SERIALIZABLE);
229     }
230 
231     private static byte[] externalizableByteStreamFor(String className, long uid)
232          throws Exception
233     {
234         return byteStreamFor(className, uid, SC_EXTERNALIZABLE);
235     }
236 
237     @DataProvider(name = "classes")
238     public Object[][] classes() {
239         return new Object[][] {
240             new Object[] { Point.class             },
241             new Object[] { SerializablePoint.class }
242         };
243     }
244 
245     static final Class<InvalidClassException> ICE = InvalidClassException.class;
246 
247     // primitive class read directly from a byte stream
248     @Test(dataProvider = "classes")
249     public void deserialize(Class<?> cls) throws Exception {
250         var clsDesc = ObjectStreamClass.lookup(cls);
251         long uid = clsDesc == null ? 0L : clsDesc.getSerialVersionUID();
252 
253         byte[] serialBytes = serializableByteStreamFor(cls.getName(), uid);
254         expectThrows(ICE, () -> deserialize(serialBytes));
255 
256         byte[] extBytes = externalizableByteStreamFor(cls.getName(), uid);
257         expectThrows(ICE, () -> deserialize(extBytes));
258     }
259 
260     static <T> byte[] serialize(T obj) throws IOException {
261         ByteArrayOutputStream baos = new ByteArrayOutputStream();
262         ObjectOutputStream oos = new ObjectOutputStream(baos);
263         oos.writeObject(obj);
264         oos.close();
265         return baos.toByteArray();
266     }
267 
268     @SuppressWarnings("unchecked")
269     static <T> T deserialize(byte[] streamBytes)
270         throws IOException, ClassNotFoundException
271     {
272         ByteArrayInputStream bais = new ByteArrayInputStream(streamBytes);
273         ObjectInputStream ois  = new ObjectInputStream(bais);
274         return (T) ois.readObject();
275     }
276 
277     @SuppressWarnings("unchecked")
278     static <T> T serializeDeserialize(T obj)
279         throws IOException, ClassNotFoundException
280     {
281         return (T) deserialize(serialize(obj));
282     }
283 }