diff --git a/javaobj/v1/unmarshaller.py b/javaobj/v1/unmarshaller.py index c3c7709..17c9343 100644 --- a/javaobj/v1/unmarshaller.py +++ b/javaobj/v1/unmarshaller.py @@ -514,56 +514,51 @@ def do_object(self, parent=None, ident=0): # TODO: raise NotImplementedError("externalContents isn't implemented yet") + # Process class data for the entire hierarchy + self._read_serializable_data(java_object, classdesc, ident) + + log_debug(">>> java_object: {0}".format(java_object), ident) + return java_object + + def _read_serializable_data(self, java_object, classdesc, ident): + """ + Read serializable data following the Java specification more closely. + According to the spec, for each class in the hierarchy (from super to sub): + 1. Read primitive fields + 2. If SC_WRITE_METHOD is set, read custom writeObject data until TC_ENDBLOCKDATA + """ + if classdesc.superclass: + self._read_serializable_data(java_object, classdesc.superclass, ident + 1) + if classdesc.flags & ClassDescFlags.SC_SERIALIZABLE: # TODO: look at ObjectInputStream.readSerialData() # FIXME: Handle the SC_WRITE_METHOD flag - # create megalist - tempclass = classdesc - megalist = [] - megatypes = [] log_debug("Constructing class...", ident) - while tempclass: - log_debug("Class: {0}".format(tempclass.name), ident + 1) + log_debug("Class: {0}".format(classdesc.name), ident + 1) + if classdesc.fields_names: class_fields_str = " - ".join( " ".join((str(field_type), field_name)) for field_type, field_name in zip( - tempclass.fields_types, tempclass.fields_names + classdesc.fields_types, classdesc.fields_names ) ) - if class_fields_str: - log_debug(class_fields_str, ident + 2) - - fieldscopy = tempclass.fields_names[:] - fieldscopy.extend(megalist) - megalist = fieldscopy + log_debug(class_fields_str, ident + 2) - fieldscopy = tempclass.fields_types[:] - fieldscopy.extend(megatypes) - megatypes = fieldscopy + log_debug("Values count: {0}".format(len(classdesc.fields_names)), ident) + log_debug("Prepared list of values: {0}".format(classdesc.fields_names), ident) + log_debug("Prepared list of types: {0}".format(classdesc.fields_types), ident) - tempclass = tempclass.superclass - - log_debug("Values count: {0}".format(len(megalist)), ident) - log_debug("Prepared list of values: {0}".format(megalist), ident) - log_debug("Prepared list of types: {0}".format(megatypes), ident) - - for field_name, field_type in zip(megalist, megatypes): + for field_name, field_type in zip(classdesc.fields_names, classdesc.fields_types): log_debug( "Reading field: {0} - {1}".format(field_type, field_name) ) res = self._read_value(field_type, ident, name=field_name) java_object.__setattr__(field_name, res) - if ( - classdesc.flags & ClassDescFlags.SC_SERIALIZABLE - and classdesc.flags & ClassDescFlags.SC_WRITE_METHOD - or classdesc.flags & ClassDescFlags.SC_EXTERNALIZABLE - and classdesc.flags & ClassDescFlags.SC_BLOCK_DATA - or classdesc.superclass is not None - and classdesc.superclass.flags & ClassDescFlags.SC_SERIALIZABLE - and classdesc.superclass.flags & ClassDescFlags.SC_WRITE_METHOD - ): + has_write_method = classdesc.flags & ClassDescFlags.SC_SERIALIZABLE and classdesc.flags & ClassDescFlags.SC_WRITE_METHOD + has_block_data = classdesc.flags & ClassDescFlags.SC_EXTERNALIZABLE and classdesc.flags & ClassDescFlags.SC_BLOCK_DATA + if has_write_method or has_block_data: # objectAnnotation log_debug( "java_object.annotations before: {0}".format( @@ -572,6 +567,7 @@ def do_object(self, parent=None, ident=0): ident, ) + opcode = None while opcode != TerminalCode.TC_ENDBLOCKDATA: opcode, obj = self._read_and_exec_opcode(ident=ident + 1) # , expect=[self.TC_ENDBLOCKDATA, self.TC_BLOCKDATA, @@ -593,9 +589,6 @@ def do_object(self, parent=None, ident=0): log_debug("Java object has extra loading capability.") java_object.__extra_loading__(self, ident) - log_debug(">>> java_object: {0}".format(java_object), ident) - return java_object - def do_string(self, parent=None, ident=0): """ Handles a TC_STRING opcode diff --git a/tests/issue60_custom_reader_endblock.ser b/tests/issue60_custom_reader_endblock.ser new file mode 100644 index 0000000..8d2ce55 Binary files /dev/null and b/tests/issue60_custom_reader_endblock.ser differ diff --git a/tests/java/src/test/java/Issue60CustomReaderEndblock.java b/tests/java/src/test/java/Issue60CustomReaderEndblock.java new file mode 100644 index 0000000..e1ef601 --- /dev/null +++ b/tests/java/src/test/java/Issue60CustomReaderEndblock.java @@ -0,0 +1,72 @@ +import java.io.*; +import java.util.List; + +class SuperClass implements Serializable { + private List superItems = null; + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + // Custom serialization logic that triggers SC_WRITE_METHOD flag + out.writeUTF("custom_marker"); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + // Custom deserialization logic + String marker = in.readUTF(); + System.out.println("Read custom marker: " + marker); + } +} + +class CustomClass extends SuperClass { + private static final long serialVersionUID = 1L; + + private String name; + private List items = null; + private int port = 443; + + public CustomClass(String name) { + this.name = name; + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + // Custom serialization for child class too + out.writeInt(42); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + int customValue = in.readInt(); + System.out.println("Read custom value: " + customValue); + } + + @Override + public String toString() { + return "CustomClass{name='" + name + "', items=" + items + "', port=" + port + "}"; + } +} + +public class SerializationExample { + public static void main(String[] args) { + try { + // Create and serialize + CustomClass obj = new CustomClass("test"); + System.out.println("Original: " + obj); + + // Serialize to file + try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("issue60_custom_reader_endblock.ser"))) { + oos.writeObject(obj); + } + + // Deserialize from file + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("issue60_custom_reader_endblock.ser"))) { + CustomClass deserialized = (CustomClass) ois.readObject(); + System.out.println("Deserialized: " + deserialized); + } + + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } + } +} diff --git a/tests/test_v1.py b/tests/test_v1.py index 162b2db..5fc1194 100644 --- a/tests/test_v1.py +++ b/tests/test_v1.py @@ -508,6 +508,17 @@ def test_qistoph_pr_27(self): for key, value in pobj.items(): self.assertEqual(parent_map[key], value) + def test_read_custom(self): + """ + Tests to verify that the super-class is properly read when a custom writer is involved. + """ + ser = self.read_file("issue60_custom_reader_endblock.ser") + pobj = javaobj.loads(ser) + self.assertIsNone(pobj.superItems) + self.assertIsNone(pobj.items) + self.assertEquals(pobj.name, "test") + self.assertEquals(pobj.port, 443) + # ------------------------------------------------------------------------------ diff --git a/tests/test_v2.py b/tests/test_v2.py index 301db9c..ea8b63d 100644 --- a/tests/test_v2.py +++ b/tests/test_v2.py @@ -644,7 +644,6 @@ def test_writeObject(self): self.assertEqual(expected["custom_obj"]["field_data"], child_data) self.assertEqual(expected["custom_obj"]["annotations"], super_data) - # ------------------------------------------------------------------------------