1 /*
   2  * Copyright (c) 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  * @summary Checks that the appropriate value is given to the canonical ctr
  27  * @compile --enable-preview -source ${jdk.version} DifferentStreamFieldsTest.java
  28  * @run testng/othervm --enable-preview DifferentStreamFieldsTest
  29  * @run testng/othervm/java.security.policy=empty_security.policy --enable-preview DifferentStreamFieldsTest
  30  */
  31 
  32 import java.io.ByteArrayInputStream;
  33 import java.io.ByteArrayOutputStream;
  34 import java.io.DataOutputStream;
  35 import java.io.IOException;
  36 import java.io.ObjectInputStream;
  37 import java.io.ObjectOutputStream;
  38 import java.io.Serializable;
  39 import java.util.LinkedHashMap;
  40 import java.util.Map;
  41 import org.testng.annotations.DataProvider;
  42 import org.testng.annotations.Test;
  43 import static java.io.ObjectStreamConstants.*;
  44 import static java.lang.System.out;
  45 import static org.testng.Assert.*;
  46 
  47 /**
  48  * ...
  49  */
  50 public class DifferentStreamFieldsTest {
  51 
  52     record R01(boolean  x) implements Serializable { }
  53     record R02(byte     x) implements Serializable { }
  54     record R03(short    x) implements Serializable { }
  55     record R04(char     x) implements Serializable { }
  56     record R05(int      x) implements Serializable { }
  57     record R06(long     x) implements Serializable { }
  58     record R07(float    x) implements Serializable { }
  59     record R08(double   x) implements Serializable { }
  60     record R09(Object   x) implements Serializable { }
  61     record R10(String   x) implements Serializable { }
  62     record R11(int[]    x) implements Serializable { }
  63     record R12(Object[] x) implements Serializable { }
  64     record R13(R12      x) implements Serializable { }
  65     record R14(R13[]    x) implements Serializable { }
  66 
  67     @DataProvider(name = "recordTypeAndExpectedValue")
  68     public Object[][] recordTypeAndExpectedValue() {
  69         return new Object[][] {
  70                 new Object[] { R01.class, false    },
  71                 new Object[] { R02.class, (byte)0  },
  72                 new Object[] { R03.class, (short)0 },
  73                 new Object[] { R04.class, '\u0000' },
  74                 new Object[] { R05.class, 0        },
  75                 new Object[] { R06.class, 0L       },
  76                 new Object[] { R07.class, 0.0f     },
  77                 new Object[] { R08.class, 0.0d     },
  78                 new Object[] { R09.class, null     },
  79                 new Object[] { R10.class, null     },
  80                 new Object[] { R11.class, null     },
  81                 new Object[] { R12.class, null     },
  82                 new Object[] { R13.class, null     },
  83                 new Object[] { R14.class, null     },
  84         };
  85     }
  86 
  87     @Test(dataProvider = "recordTypeAndExpectedValue")
  88     public void testWithDifferentTypes(Class<?> clazz, Object expectedXValue)
  89         throws Exception
  90     {
  91         out.println("\n---");
  92         assert clazz.isRecord();
  93         byte[] bytes = SerialByteStreamBuilder
  94                 .newBuilder(clazz.getName())
  95                 .build();
  96 
  97         Object obj = deserialize(bytes);
  98         out.println("deserialized: " + obj);
  99         Object actualXValue = clazz.getDeclaredMethod("x").invoke(obj);
 100         assertEquals(actualXValue, expectedXValue);
 101 
 102         bytes = SerialByteStreamBuilder
 103                 .newBuilder(clazz.getName())
 104                 .addPrimitiveField("y", int.class, 5)  // stream junk
 105                 .build();
 106 
 107         obj = deserialize(bytes);
 108         out.println("deserialized: " + obj);
 109         actualXValue = clazz.getDeclaredMethod("x").invoke(obj);
 110         assertEquals(actualXValue, expectedXValue);
 111     }
 112 
 113     // --- all together
 114 
 115     @Test
 116     public void testWithAllTogether() throws Exception {
 117         out.println("\n---");
 118         record R15(boolean a, byte b, short c, char d, int e, long f, float g, double h, Object i, String j, long[] k, Object[] l)
 119                 implements Serializable { }
 120 
 121         byte[] bytes = SerialByteStreamBuilder
 122                 .newBuilder(R15.class.getName())
 123                 .addPrimitiveField("x", int.class, 5)  // stream junk
 124                 .build();
 125 
 126         R15 obj = deserialize(bytes);
 127         out.println("deserialized: " + obj);
 128         assertEquals(obj.a, false);
 129         assertEquals(obj.b, 0);
 130         assertEquals(obj.c, 0);
 131         assertEquals(obj.d, '\u0000');
 132         assertEquals(obj.e, 0);
 133         assertEquals(obj.f, 0l);
 134         assertEquals(obj.g, 0f);
 135         assertEquals(obj.h, 0d);
 136         assertEquals(obj.i, null);
 137         assertEquals(obj.j, null);
 138         assertEquals(obj.k, null);
 139         assertEquals(obj.l, null);
 140     }
 141 
 142     @Test
 143     public void testInt() throws Exception {
 144         out.println("\n---");
 145         {
 146             record R(int x) implements Serializable { }
 147 
 148             var r = new R(5);
 149             byte[] OOSBytes = serialize(r);
 150 
 151             byte[] builderBytes = SerialByteStreamBuilder
 152                     .newBuilder(R.class.getName())
 153                     .addPrimitiveField("x", int.class, 5)
 154                     .build();
 155 
 156             var deser1 = deserialize(OOSBytes);
 157             var deser2 = deserialize(builderBytes);
 158             assertEquals(deser2, deser1);
 159         }
 160         {
 161             record R(int x, int y) implements Serializable {  }
 162 
 163             var r = new R(7, 8);
 164             byte[] OOSBytes = serialize(r);
 165             var deser1 = deserialize(OOSBytes);
 166 
 167             byte[] builderBytes = SerialByteStreamBuilder
 168                     .newBuilder(R.class.getName())
 169                     .addPrimitiveField("x", int.class, 7)
 170                     .addPrimitiveField("y", int.class, 8)
 171                     .build();
 172 
 173             var deser2 = deserialize(builderBytes);
 174             assertEquals(deser2, deser1);
 175 
 176             builderBytes = SerialByteStreamBuilder
 177                     .newBuilder(R.class.getName())
 178                     .addPrimitiveField("y", int.class, 8)  // reverse order
 179                     .addPrimitiveField("x", int.class, 7)
 180                     .build();
 181             deser2 = deserialize(builderBytes);
 182             assertEquals(deser2, deser1);
 183 
 184             builderBytes = SerialByteStreamBuilder
 185                     .newBuilder(R.class.getName())
 186                     .addPrimitiveField("w", int.class, 6) // additional fields
 187                     .addPrimitiveField("x", int.class, 7)
 188                     .addPrimitiveField("y", int.class, 8)
 189                     .addPrimitiveField("z", int.class, 9) // additional fields
 190                     .build();
 191             deser2 = deserialize(builderBytes);
 192             assertEquals(deser2, deser1);
 193 
 194             r = new R(0, 0);
 195             OOSBytes = serialize(r);
 196             deser1 = deserialize(OOSBytes);
 197 
 198             builderBytes = SerialByteStreamBuilder
 199                     .newBuilder(R.class.getName())
 200                     .addPrimitiveField("y", int.class, 0)
 201                     .addPrimitiveField("x", int.class, 0)
 202                     .build();
 203             deser2 = deserialize(builderBytes);
 204             assertEquals(deser2, deser1);
 205 
 206             builderBytes = SerialByteStreamBuilder
 207                     .newBuilder(R.class.getName())  // no field values
 208                     .build();
 209             deser2 = deserialize(builderBytes);
 210             assertEquals(deser2, deser1);
 211         }
 212     }
 213 
 214     @Test
 215     public void testString() throws Exception {
 216         out.println("\n---");
 217 
 218         record Str(String part1, String part2) implements Serializable { }
 219 
 220         var r = new Str("Hello", "World!");
 221         var deser1 = deserialize(serialize(r));
 222 
 223         byte[] builderBytes = SerialByteStreamBuilder
 224                 .newBuilder(Str.class.getName())
 225                 .addField("part1", String.class, "Hello")
 226                 .addField("part2", String.class, "World!")
 227                 .build();
 228 
 229         var deser2 = deserialize(builderBytes);
 230         assertEquals(deser2, deser1);
 231 
 232         builderBytes = SerialByteStreamBuilder
 233                 .newBuilder(Str.class.getName())
 234                 .addField("cruft", String.class, "gg")
 235                 .addField("part1", String.class, "Hello")
 236                 .addField("part2", String.class, "World!")
 237                 .addPrimitiveField("x", int.class, 13)
 238                 .build();
 239 
 240         var deser3 = deserialize(builderBytes);
 241         assertEquals(deser3, deser1);
 242     }
 243 
 244     @Test
 245     public void testArrays() throws Exception {
 246         out.println("\n---");
 247         {
 248             record IntArray(int[] ints, long[] longs) implements Serializable { }
 249             IntArray r = new IntArray(new int[] { 5, 4, 3,2, 1 }, new long[] { 9L });
 250             IntArray deser1 = deserialize(serialize(r));
 251 
 252             byte[] builderBytes = SerialByteStreamBuilder
 253                     .newBuilder(IntArray.class.getName())
 254                     .addField("ints", String[].class, new int[] { 5, 4, 3,2, 1 })
 255                     .addField("longs", String[].class, new long[] { 9L })
 256                     .build();
 257 
 258             IntArray deser2 = deserialize(builderBytes);
 259             assertEquals(deser2.ints(), deser1.ints());
 260             assertEquals(deser2.longs(), deser1.longs());
 261         }
 262         {
 263             record StrArray(String[] stringArray) implements Serializable { }
 264             StrArray r = new StrArray(new String[] { "foo", "bar" });
 265             StrArray deser1 = deserialize(serialize(r));
 266 
 267             byte[] builderBytes = SerialByteStreamBuilder
 268                     .newBuilder(StrArray.class.getName())
 269                     .addField("stringArray", String[].class, new String[] { "foo", "bar" })
 270                     .build();
 271 
 272             StrArray deser2 = deserialize(builderBytes);
 273             assertEquals(deser2.stringArray(), deser1.stringArray());
 274         }
 275     }
 276 
 277     static class SerialByteStreamBuilder {
 278 
 279         private final ObjectOutputStream objectOutputStream;
 280         private final ByteArrayOutputStream byteArrayOutputStream;
 281 
 282         record NameAndType<T> (String name, Class<T> type) { }
 283 
 284         private String className;
 285         private LinkedHashMap<NameAndType,Object> primFields = new LinkedHashMap<>();
 286         private LinkedHashMap<NameAndType,Object> objectFields = new LinkedHashMap<>();
 287 
 288         private SerialByteStreamBuilder() {
 289             try {
 290                 byteArrayOutputStream = new ByteArrayOutputStream();
 291                 objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
 292             } catch (IOException e) {
 293                 throw new AssertionError(e);
 294             }
 295         }
 296 
 297         static SerialByteStreamBuilder newBuilder(String className) {
 298             return (new SerialByteStreamBuilder()).className(className);
 299         }
 300 
 301         private SerialByteStreamBuilder className(String className) {
 302             this.className = className;
 303             return this;
 304         }
 305 
 306         SerialByteStreamBuilder addPrimitiveField(String name, Class<?> type, Object value) {
 307             if (!type.isPrimitive())
 308                 throw new IllegalArgumentException("Unexpected non-primitive field: " + type);
 309             primFields.put(new NameAndType(name, type), value);
 310             return this;
 311         }
 312 
 313         SerialByteStreamBuilder addField(String name, Class<?> type, Object value) {
 314             if (type.isPrimitive())
 315                 throw new IllegalArgumentException("Unexpected primitive field: " + type);
 316             objectFields.put(new NameAndType(name, type), value);
 317             return this;
 318         }
 319 
 320         private static int getPrimitiveSignature(Class<?> cl) {
 321             if      (cl == Integer.TYPE)   return 'I';
 322             else if (cl == Byte.TYPE)      return 'B';
 323             else if (cl == Long.TYPE)      return 'J';
 324             else if (cl == Float.TYPE)     return 'F';
 325             else if (cl == Double.TYPE)    return 'D';
 326             else if (cl == Short.TYPE)     return 'S';
 327             else if (cl == Character.TYPE) return 'C';
 328             else if (cl == Boolean.TYPE)   return 'Z';
 329             else throw new InternalError();
 330         }
 331 
 332         private static void writeUTF(DataOutputStream out, String str) throws IOException {
 333             int utflen = str.length(); // assume ASCII
 334             assert utflen <= 0xFFFF;
 335             out.writeShort(utflen);
 336             out.writeBytes(str);
 337         }
 338 
 339         private void writePrimFieldsDesc(DataOutputStream out) throws IOException {
 340             for (Map.Entry<NameAndType,Object> entry : primFields.entrySet()) {
 341                 assert entry.getKey().type() != void.class;
 342                 out.writeByte(getPrimitiveSignature(entry.getKey().type())); // prim_typecode
 343                 out.writeUTF(entry.getKey().name());                         // fieldName
 344             }
 345         }
 346 
 347         private void writePrimFieldsValues(DataOutputStream out) throws IOException {
 348             for (Map.Entry<NameAndType,Object> entry : primFields.entrySet()) {
 349                 Class<?> cl  = entry.getKey().type();
 350                 Object value = entry.getValue();
 351                 if      (cl == Integer.TYPE)   out.writeInt((int) value);
 352                 else if (cl == Byte.TYPE)      out.writeByte((byte) value);
 353                 else if (cl == Long.TYPE)      out.writeLong((long) value);
 354                 else if (cl == Float.TYPE)     out.writeFloat((float) value);
 355                 else if (cl == Double.TYPE)    out.writeDouble((double) value);
 356                 else if (cl == Short.TYPE)     out.writeShort((short) value);
 357                 else if (cl == Character.TYPE) out.writeChar((char) value);
 358                 else if (cl == Boolean.TYPE)   out.writeBoolean((boolean) value);
 359                 else throw new InternalError();
 360             }
 361         }
 362 
 363         private void writeObjectFieldDesc(DataOutputStream out) throws IOException {
 364             for (Map.Entry<NameAndType,Object> entry : objectFields.entrySet()) {
 365                 Class<?> cl = entry.getKey().type();
 366                 assert !cl.isPrimitive();
 367                 // obj_typecode
 368                 if (cl.isArray()) {
 369                     out.writeByte('[');
 370                 } else {
 371                     out.writeByte('L');
 372                 }
 373                 writeUTF(out, entry.getKey().name());
 374                 out.writeByte(TC_STRING);
 375                 writeUTF(out, "L" + cl.getName().replace('.', '/') + ";");
 376 
 377             }
 378         }
 379 
 380         private void writeObject(DataOutputStream out, Object value) throws IOException {
 381             objectOutputStream.reset();
 382             byteArrayOutputStream.reset();
 383             objectOutputStream.writeUnshared(value);
 384             out.write(byteArrayOutputStream.toByteArray());
 385         }
 386 
 387         private void writeObjectFieldValues(DataOutputStream out) throws IOException {
 388             for (Map.Entry<NameAndType,Object> entry : objectFields.entrySet()) {
 389                 Class<?> cl = entry.getKey().type();
 390                 assert !cl.isPrimitive();
 391                 if (cl == String.class) {
 392                     out.writeByte(TC_STRING);
 393                     writeUTF(out, (String) entry.getValue());
 394                 } else {
 395                     writeObject(out, entry.getValue());
 396                 }
 397             }
 398         }
 399 
 400         private int numFields() {
 401             return primFields.size() + objectFields.size();
 402         }
 403 
 404         byte[] build() {
 405             try {
 406                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 407                 DataOutputStream dos = new DataOutputStream(baos);
 408                 dos.writeShort(STREAM_MAGIC);
 409                 dos.writeShort(STREAM_VERSION);
 410                 dos.writeByte(TC_OBJECT);
 411                 dos.writeByte(TC_CLASSDESC);
 412                 dos.writeUTF(className);
 413                 dos.writeLong(0L);
 414                 dos.writeByte(SC_SERIALIZABLE);
 415                 dos.writeShort(numFields());      // number of fields
 416                 writePrimFieldsDesc(dos);
 417                 writeObjectFieldDesc(dos);
 418                 dos.writeByte(TC_ENDBLOCKDATA);   // no annotations
 419                 dos.writeByte(TC_NULL);           // no superclasses
 420                 writePrimFieldsValues(dos);
 421                 writeObjectFieldValues(dos);
 422                 dos.close();
 423                 return baos.toByteArray();
 424             } catch (IOException unexpected) {
 425                 throw new AssertionError(unexpected);
 426             }
 427         }
 428     }
 429 
 430     <T> byte[] serialize(T obj) throws IOException {
 431         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 432         ObjectOutputStream oos = new ObjectOutputStream(baos);
 433         oos.writeObject(obj);
 434         oos.close();
 435         return baos.toByteArray();
 436     }
 437 
 438     @SuppressWarnings("unchecked")
 439     static <T> T deserialize(byte[] streamBytes)
 440         throws IOException, ClassNotFoundException
 441     {
 442         ByteArrayInputStream bais = new ByteArrayInputStream(streamBytes);
 443         ObjectInputStream ois  = new ObjectInputStream(bais);
 444         return (T) ois.readObject();
 445     }
 446 }