1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package libcore.java.io;
18
19import junit.framework.TestCase;
20
21import java.io.InvalidClassException;
22import java.io.InvalidObjectException;
23import java.io.NotSerializableException;
24import java.io.ObjectStreamClass;
25import java.io.ObjectStreamField;
26import java.io.Serializable;
27import java.lang.reflect.InvocationHandler;
28import java.lang.reflect.Method;
29import libcore.util.SerializationTester;
30
31public final class SerializationTest extends TestCase {
32
33    // http://b/4471249
34    public void testSerializeFieldMadeTransient() throws Exception {
35        // Does ObjectStreamClass have the right idea?
36        ObjectStreamClass osc = ObjectStreamClass.lookup(FieldMadeTransient.class);
37        ObjectStreamField[] fields = osc.getFields();
38        assertEquals(1, fields.length);
39        assertEquals("nonTransientInt", fields[0].getName());
40        assertEquals(int.class, fields[0].getType());
41
42        // this was created by serializing a FieldMadeTransient with a non-0 transientInt
43        String s = "aced0005737200346c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6e54657"
44                + "374244669656c644d6164655472616e7369656e74000000000000000002000149000c7472616e736"
45                + "9656e74496e747870abababab";
46        FieldMadeTransient deserialized = (FieldMadeTransient) SerializationTester.deserializeHex(s);
47        assertEquals(0, deserialized.transientInt);
48    }
49
50    static class FieldMadeTransient implements Serializable {
51        private static final long serialVersionUID = 0L;
52        @SuppressWarnings("unused")
53        private transient int transientInt;
54        @SuppressWarnings("unused")
55        private int nonTransientInt;
56    }
57
58    public void testSerializeFieldMadeStatic() throws Exception {
59        // Does ObjectStreamClass have the right idea?
60        ObjectStreamClass osc = ObjectStreamClass.lookup(FieldMadeStatic.class);
61        ObjectStreamField[] fields = osc.getFields();
62        assertEquals(0, fields.length);
63
64        // This was created by serializing a FieldMadeStatic with a non-static staticInt
65        String s = "aced0005737200316c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6e54657"
66                + "374244669656c644d6164655374617469630000000000000000020001490009737461746963496e7"
67                + "47870000022b8";
68        FieldMadeStatic deserialized = (FieldMadeStatic) SerializationTester.deserializeHex(s);
69        // The field data must be ignored if it is static.
70        assertEquals(9999, deserialized.staticInt);
71    }
72
73    static class FieldMadeStatic implements Serializable {
74        private static final long serialVersionUID = 0L;
75        // private int staticInt = 8888;
76        private static int staticInt = 9999;
77    }
78
79    public static boolean serializableContainer1InitializedFlag = false;
80    public static boolean unserializable1InitializedFlag = false;
81
82    public static class Unserializable1 {
83        static {
84            SerializationTest.unserializable1InitializedFlag = true;
85        }
86    }
87
88    static class SerializableContainer1 implements Serializable {
89        private static final long serialVersionUID = 0L;
90        private Unserializable1 unserializable = null;
91
92        static {
93            serializableContainer1InitializedFlag = true;
94        }
95    }
96
97    // We can serialize an object that has an unserializable field providing it is null.
98    public void testDeserializeNullUnserializableField() throws Exception {
99        // This was created by creating a new SerializableContainer and not setting the
100        // unserializable field. A canned serialized form is used so we can tell if the static
101        // initializers were executed during deserialization.
102        // SerializationTester.serializeHex(new SerializableContainer1());
103        String s = "aced0005737200386c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6e54657"
104                  + "3742453657269616c697a61626c65436f6e7461696e65723100000000000000000200014c000e7"
105                  + "56e73657269616c697a61626c657400124c6a6176612f6c616e672f4f626a6563743b787070";
106
107        assertFalse(serializableContainer1InitializedFlag);
108        assertFalse(unserializable1InitializedFlag);
109
110        SerializableContainer1 sc = (SerializableContainer1) SerializationTester.deserializeHex(s);
111        assertNull(sc.unserializable);
112
113        // Confirm the container was initialized, but the class for the null field was not.
114        assertTrue(serializableContainer1InitializedFlag);
115        assertFalse(unserializable1InitializedFlag);
116    }
117
118    static class Unserializable2 {
119    }
120
121    static class HasUnserializableField implements Serializable {
122        private static final long serialVersionUID = 0L;
123        @SuppressWarnings("unused") // Required to make objects unserializable.
124        private Unserializable2 unserializable = new Unserializable2();
125    }
126
127    // We must not serialize an object that has a non-null unserializable field.
128    public void testSerializeUnserializableField() throws Exception {
129        HasUnserializableField uf = new HasUnserializableField();
130        try {
131            SerializationTester.serializeHex(uf);
132            fail();
133        } catch (NotSerializableException expected) {
134        }
135    }
136
137    public static boolean serializableContainer2InitializedFlag = false;
138
139    @SuppressWarnings("unused") // Required for deserialization test
140    static class SerializableContainer2 implements Serializable {
141        private static final long serialVersionUID = 0L;
142        private WasSerializable unserializable = null;
143
144        static {
145            serializableContainer2InitializedFlag = true;
146        }
147    }
148
149    // It must not be possible to deserialize an object if a field is no longer serializable.
150    public void testDeserializeUnserializableField() throws Exception {
151        // This was generated by creating a SerializableContainer2 and setting the unserializable
152        // field to a WasSerializable when it was still Serializable. A canned serialized form is
153        // used so we can tell if the static initializers were executed during deserialization.
154        // SerializableContainer2 sc = new SerializableContainer2();
155        // sc.unserializable = new WasSerializable();
156        // SerializationTester.serializeHex(sc);
157        String s = "aced0005737200386c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6e54657"
158                  + "3742453657269616c697a61626c65436f6e7461696e65723200000000000000000200014c000e7"
159                  + "56e73657269616c697a61626c657400334c6c6962636f72652f6a6176612f696f2f53657269616"
160                  + "c697a6174696f6e546573742457617353657269616c697a61626c653b7870737200316c6962636"
161                  + "f72652e6a6176612e696f2e53657269616c697a6174696f6e546573742457617353657269616c6"
162                  + "97a61626c65000000000000000002000149000169787000000000";
163
164        assertFalse(serializableContainer2InitializedFlag);
165        assertFalse(wasSerializableInitializedFlag);
166        try {
167            SerializationTester.deserializeHex(s);
168            fail();
169        } catch (InvalidClassException expected) {
170        }
171        // The container class will be initialized to establish the serialVersionUID.
172        assertTrue(serializableContainer2InitializedFlag);
173        // Confirm the contained class was initialized.
174        assertFalse(wasSerializableInitializedFlag);
175    }
176
177    public void testSerialVersionUidChange() throws Exception {
178        // this was created by serializing a SerialVersionUidChanged with serialVersionUID = 0L
179        String s = "aced0005737200396c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6e54657"
180                + "3742453657269616c56657273696f6e5569644368616e67656400000000000000000200014900016"
181                + "1787000000003";
182        try {
183            SerializationTester.deserializeHex(s);
184            fail();
185        } catch (InvalidClassException expected) {
186        }
187    }
188
189    @SuppressWarnings("unused") // Required for deserialization test
190    static class SerialVersionUidChanged implements Serializable {
191        private static final long serialVersionUID = 1L; // was 0L
192        private int a;
193    }
194
195    public void testMissingSerialVersionUid() throws Exception {
196        // this was created by serializing a FieldsChanged with one int field named 'a'
197        String s = "aced00057372002f6c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6e54657"
198                + "374244669656c64734368616e6765643bcfb934e310fa1c02000149000161787000000003";
199        try {
200            SerializationTester.deserializeHex(s);
201            fail();
202        } catch (InvalidClassException expected) {
203        }
204    }
205
206    @SuppressWarnings("unused") // Required for deserialization test
207    static class FieldsChanged implements Serializable {
208        private int b; // was 'a'
209    }
210
211    public static boolean wasSerializableInitializedFlag = false;
212
213    @SuppressWarnings("unused")  // Required for deserialization test.
214    public static class WasSerializable /* implements java.io.Serializable */ {
215        static final long serialVersionUID = 0L;
216        static {
217            SerializationTest.wasSerializableInitializedFlag = true;
218        }
219        private int i;
220    }
221
222    public void testDeserializeWasSerializableClass() throws Exception {
223        // This was created by serializing a WasSerializable when it was serializable.
224        // String s = SerializationTester.serializeHex(new WasSerializable());
225        final String s = "aced0005737200316c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6"
226                + "e546573742457617353657269616c697a61626c65000000000000000002000149000169787000000"
227                + "000";
228
229        assertFalse(wasSerializableInitializedFlag);
230        try {
231            SerializationTester.deserializeHex(s);
232            fail();
233        } catch (InvalidClassException expected) {
234        }
235        assertFalse(wasSerializableInitializedFlag);
236    }
237
238    // The WasExternalizable class before it was modified.
239    /*
240    public static class WasExternalizable implements Externalizable {
241        static final long serialVersionUID = 0L;
242
243        @Override
244        public void readExternal(ObjectInput input) throws IOException, ClassNotFoundException {
245
246        }
247
248        @Override
249        public void writeExternal(ObjectOutput output) throws IOException {
250
251        }
252    }
253    */
254
255    public static boolean wasExternalizableInitializedFlag = false;
256
257    @SuppressWarnings("unused") // Required for deserialization test
258    public static class WasExternalizable implements Serializable {
259        static final long serialVersionUID = 0L;
260        static {
261            SerializationTest.wasExternalizableInitializedFlag = true;
262        }
263
264    }
265
266    public void testDeserializeWasExternalizableClass() throws Exception {
267        // This was created by serializing a WasExternalizable when it was externalizable.
268        // String s = SerializationTester.serializeHex(new WasExternalizable());
269        final String s = "aced0005737200336c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6"
270                + "e546573742457617345787465726e616c697a61626c6500000000000000000c0000787078";
271
272        assertFalse(wasExternalizableInitializedFlag);
273        try {
274            SerializationTester.deserializeHex(s);
275            fail();
276        } catch (InvalidClassException expected) {
277        }
278        // Unlike other similar tests static initialization will take place if the local class is
279        // Serializable or Externalizable because serialVersionUID field is accessed.
280        // The RI appears to do the same.
281        assertTrue(wasExternalizableInitializedFlag);
282    }
283
284    // The WasEnum class before it was modified.
285    /*
286    public enum WasEnum {
287        VALUE
288    }
289    */
290
291    public static boolean wasEnumInitializedFlag = false;
292
293    @SuppressWarnings("unused") // Required for deserialization test
294    public static class WasEnum {
295        static final long serialVersionUID = 0L;
296        static {
297            SerializationTest.wasEnumInitializedFlag = true;
298        }
299    }
300
301    public void testDeserializeWasEnum() throws Exception {
302        // This was created by serializing a WasEnum when it was an enum.
303        // String s = SerializationTester.serializeHex(WasEnum.VALUE);
304        final String s = "aced00057e7200296c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6"
305                + "e5465737424576173456e756d00000000000000001200007872000e6a6176612e6c616e672e456e7"
306                + "56d0000000000000000120000787074000556414c5545";
307
308        assertFalse(wasEnumInitializedFlag);
309        try {
310            SerializationTester.deserializeHex(s);
311            fail();
312        } catch (InvalidClassException expected) {
313        }
314        assertFalse(wasEnumInitializedFlag);
315    }
316
317    // The WasObject class before it was modified.
318    /*
319    public static class WasObject implements java.io.Serializable {
320        static final long serialVersionUID = 0L;
321        private int i;
322    }
323    */
324
325    public static boolean wasObjectInitializedFlag;
326
327    @SuppressWarnings("unused") // Required for deserialization test
328    public enum WasObject {
329        VALUE;
330
331        static {
332            SerializationTest.wasObjectInitializedFlag = true;
333        }
334    }
335
336    public void testDeserializeWasObject() throws Exception {
337        // This was created by serializing a WasObject when it wasn't yet an enum.
338        // String s = SerializationTester.serializeHex(new WasObject());
339        final String s = "aced00057372002b6c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6"
340                + "e54657374245761734f626a656374000000000000000002000149000169787000000000";
341
342        assertFalse(wasObjectInitializedFlag);
343        try {
344            SerializationTester.deserializeHex(s);
345            fail();
346        } catch (InvalidClassException expected) {
347        }
348        assertFalse(wasObjectInitializedFlag);
349    }
350
351    @SuppressWarnings("unused") // Required for deserialization test
352    public enum EnumMissingValue {
353        /*MISSING_VALUE*/
354    }
355
356    public void testDeserializeEnumMissingValue() throws Exception {
357        // This was created by serializing a EnumMissingValue when it had MISSING_VALUE.
358        // String s = SerializationTester.serializeHex(EnumMissingValue.MISSING_VALUE);
359        final String s = "aced00057e7200326c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6"
360                + "e5465737424456e756d4d697373696e6756616c756500000000000000001200007872000e6a61766"
361                + "12e6c616e672e456e756d0000000000000000120000787074000d4d495353494e475f56414c5545";
362
363        try {
364            SerializationTester.deserializeHex(s);
365            fail();
366        } catch (InvalidObjectException expected) {
367        }
368    }
369
370
371    public static Object hasStaticInitializerObject;
372
373    public static class HasStaticInitializer implements Serializable {
374        static {
375            SerializationTest.hasStaticInitializerObject = new Object();
376        }
377    }
378
379    public void testDeserializeStaticInitializerIsRunEventually() throws Exception {
380        // This was created by serializing a HasStaticInitializer
381        // String s = SerializationTester.serializeHex(new HasStaticInitializer());
382        final String s = "aced0005737200366c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6"
383                + "e5465737424486173537461746963496e697469616c697a6572138aa8ed9e9b660a0200007870";
384
385        // Confirm the ClassLoader behaves as it should.
386        Class.forName(
387                HasStaticInitializer.class.getName(),
388                false /* shouldInitialize */,
389                Thread.currentThread().getContextClassLoader());
390        assertNull(hasStaticInitializerObject);
391
392        SerializationTester.deserializeHex(s);
393
394        assertNotNull(hasStaticInitializerObject);
395    }
396
397    @SuppressWarnings("unused") // Required for deserialization test
398    public static /*interface*/ class WasInterface {
399    }
400
401    @SuppressWarnings("unused") // Required for deserialization test
402    public static class SerializableInvocationHandler implements InvocationHandler, Serializable {
403        @Override
404        public Object invoke(Object proxy, Method method, Object[] args) {
405            return null;
406        }
407    }
408
409    public void testDeserializeProxyWasInterface() throws Exception {
410        // This was created by serializing a proxy referencing WasInterface when it was an
411        // interface.
412        // Object o = Proxy.newProxyInstance(
413        //        Thread.currentThread().getContextClassLoader(),
414        //        new Class[] { WasInterface.class },
415        //        new SerializableInvocationHandler());
416        // String s = SerializationTester.serializeHex(o);
417        final String s = "aced0005737d00000001002e6c6962636f72652e6a6176612e696f2e53657269616c697a6"
418                + "174696f6e5465737424576173496e74657266616365787200176a6176612e6c616e672e7265666c6"
419                + "563742e50726f7879e127da20cc1043cb0200014c0001687400254c6a6176612f6c616e672f72656"
420                + "66c6563742f496e766f636174696f6e48616e646c65723b78707372003f6c6962636f72652e6a617"
421                + "6612e696f2e53657269616c697a6174696f6e546573742453657269616c697a61626c65496e766f6"
422                + "36174696f6e48616e646c6572e6ceffa2941ee3210200007870";
423        try {
424            SerializationTester.deserializeHex(s);
425            fail();
426        } catch (ClassNotFoundException expected) {
427        }
428    }
429
430    @SuppressWarnings("unused") // Required for deserialization test
431    public static class WasSerializableInvocationHandler
432            implements InvocationHandler /*, Serializable*/ {
433        static final long serialVersionUID = 0L;
434
435        @Override
436        public Object invoke(Object proxy, Method method, Object[] args) {
437            return null;
438        }
439    }
440
441    public void testDeserializeProxyInvocationHandlerWasSerializable() throws Exception {
442        // This was created by serializing a proxy referencing WasSerializableInvocationHandler when
443        // it was Serializable.
444        // Object o = Proxy.newProxyInstance(
445        //        Thread.currentThread().getContextClassLoader(),
446        //        new Class[] { Comparable.class },
447        //        new WasSerializableInvocationHandler());
448        // String s = SerializationTester.serializeHex(o);
449        final String s = "aced0005737d0000000100146a6176612e6c616e672e436f6d70617261626c65787200176"
450                + "a6176612e6c616e672e7265666c6563742e50726f7879e127da20cc1043cb0200014c00016874002"
451                + "54c6a6176612f6c616e672f7265666c6563742f496e766f636174696f6e48616e646c65723b78707"
452                + "37200426c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6e546573742457617"
453                + "353657269616c697a61626c65496e766f636174696f6e48616e646c6572000000000000000002000"
454                + "07870";
455        try {
456            SerializationTester.deserializeHex(s);
457            fail();
458        } catch (InvalidClassException expected) {
459        }
460    }
461}
462