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 }