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