From 14aa44172355dec4c674af9a0263480813e5fcec Mon Sep 17 00:00:00 2001 From: Steven Van Ingelgem Date: Mon, 9 Jun 2025 09:58:18 +0200 Subject: [PATCH 1/4] Adding test data --- tests/issue60_custom_reader_endblock.ser | Bin 0 -> 175 bytes .../java/Issue60CustomReaderEndblock.java | 72 ++++++++++++++++++ tests/test_v2.py | 7 ++ 3 files changed, 79 insertions(+) create mode 100644 tests/issue60_custom_reader_endblock.ser create mode 100644 tests/java/src/test/java/Issue60CustomReaderEndblock.java diff --git a/tests/issue60_custom_reader_endblock.ser b/tests/issue60_custom_reader_endblock.ser new file mode 100644 index 0000000000000000000000000000000000000000..8d2ce553bd8c7ef55d75b93095857da7c0ad4344 GIT binary patch literal 175 zcmXAjF%E(-7)HN_vS5tH*%Pp^xiB)3bmIV~F%79wsO^t*@sjg!YsR@Kzc{*vwBO+d8Yr z>9)>;LsIotxk2JHn9Kz literal 0 HcmV?d00001 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_v2.py b/tests/test_v2.py index 301db9c..70a3e2f 100644 --- a/tests/test_v2.py +++ b/tests/test_v2.py @@ -644,6 +644,13 @@ def test_writeObject(self): self.assertEqual(expected["custom_obj"]["field_data"], child_data) self.assertEqual(expected["custom_obj"]["annotations"], super_data) + def test_read_custom(self): + 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) # ------------------------------------------------------------------------------ From 67c629f9f777b828faa59a47c3c070fe3ac56d1e Mon Sep 17 00:00:00 2001 From: Steven Van Ingelgem Date: Mon, 9 Jun 2025 10:23:47 +0200 Subject: [PATCH 2/4] Changed code --- javaobj/v1/unmarshaller.py | 98 +++++++++++--------------------------- 1 file changed, 27 insertions(+), 71 deletions(-) diff --git a/javaobj/v1/unmarshaller.py b/javaobj/v1/unmarshaller.py index c3c7709..f62f3ad 100644 --- a/javaobj/v1/unmarshaller.py +++ b/javaobj/v1/unmarshaller.py @@ -515,87 +515,43 @@ def do_object(self, parent=None, ident=0): raise NotImplementedError("externalContents isn't implemented yet") 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) - class_fields_str = " - ".join( - " ".join((str(field_type), field_name)) - for field_type, field_name in zip( - tempclass.fields_types, tempclass.fields_names - ) - ) - if class_fields_str: - log_debug(class_fields_str, ident + 2) + # Handle each class in the hierarchy separately, from most derived to root + self._read_class_data(java_object, classdesc, ident) - fieldscopy = tempclass.fields_names[:] - fieldscopy.extend(megalist) - megalist = fieldscopy + log_debug(">>> java_object: {0}".format(java_object), ident) + return java_object - fieldscopy = tempclass.fields_types[:] - fieldscopy.extend(megatypes) - megatypes = fieldscopy + def _read_class_data(self, java_object, classdesc, ident): + """ + Recursively reads class data for the inheritance hierarchy + """ + if classdesc is None: + return - tempclass = tempclass.superclass + # First, handle the superclass data + if classdesc.superclass is not None: + self._read_class_data(java_object, classdesc.superclass, ident) - 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) + # Then handle this class's data + log_debug("Reading class data for: {0}".format(classdesc.name), ident) - for field_name, field_type in zip(megalist, megatypes): - 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) + # Read field values for this specific class + for field_name, field_type in zip(classdesc.fields_names, classdesc.fields_types): + log_debug("Reading field: {0} - {1}".format(field_type, field_name), ident) + 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 - ): - # objectAnnotation - log_debug( - "java_object.annotations before: {0}".format( - java_object.annotations - ), - ident, - ) + # Handle annotations if this class has the SC_WRITE_METHOD flag + if classdesc.flags & ClassDescFlags.SC_WRITE_METHOD: + log_debug("Reading annotations for class: {0}".format(classdesc.name), ident) - while opcode != TerminalCode.TC_ENDBLOCKDATA: + while True: opcode, obj = self._read_and_exec_opcode(ident=ident + 1) - # , expect=[self.TC_ENDBLOCKDATA, self.TC_BLOCKDATA, - # self.TC_OBJECT, self.TC_NULL, self.TC_REFERENCE]) - if opcode != TerminalCode.TC_ENDBLOCKDATA: - java_object.annotations.append(obj) - + if opcode == TerminalCode.TC_ENDBLOCKDATA: + break + java_object.annotations.append(obj) log_debug("objectAnnotation value: {0}".format(obj), ident) - log_debug( - "java_object.annotations after: {0}".format( - java_object.annotations - ), - ident, - ) - - # Allow extra loading operations - if hasattr(java_object, "__extra_loading__"): - 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 From eebea7ef53f8303a076a66075ffd8457f8ab9412 Mon Sep 17 00:00:00 2001 From: Steven Van Ingelgem Date: Mon, 9 Jun 2025 10:52:39 +0200 Subject: [PATCH 3/4] Fixed the issue. --- javaobj/v1/unmarshaller.py | 85 +++++++++++++++++++++++++++----------- tests/test_v2.py | 3 ++ 2 files changed, 64 insertions(+), 24 deletions(-) diff --git a/javaobj/v1/unmarshaller.py b/javaobj/v1/unmarshaller.py index f62f3ad..17c9343 100644 --- a/javaobj/v1/unmarshaller.py +++ b/javaobj/v1/unmarshaller.py @@ -514,44 +514,81 @@ def do_object(self, parent=None, ident=0): # TODO: raise NotImplementedError("externalContents isn't implemented yet") - if classdesc.flags & ClassDescFlags.SC_SERIALIZABLE: - # Handle each class in the hierarchy separately, from most derived to root - self._read_class_data(java_object, classdesc, ident) + # 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_class_data(self, java_object, classdesc, ident): + def _read_serializable_data(self, java_object, classdesc, ident): """ - Recursively reads class data for the inheritance hierarchy + 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 is None: - return + if classdesc.superclass: + self._read_serializable_data(java_object, classdesc.superclass, ident + 1) - # First, handle the superclass data - if classdesc.superclass is not None: - self._read_class_data(java_object, classdesc.superclass, ident) + if classdesc.flags & ClassDescFlags.SC_SERIALIZABLE: + # TODO: look at ObjectInputStream.readSerialData() + # FIXME: Handle the SC_WRITE_METHOD flag + + log_debug("Constructing class...", ident) + 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( + classdesc.fields_types, classdesc.fields_names + ) + ) + log_debug(class_fields_str, ident + 2) - # Then handle this class's data - log_debug("Reading class data for: {0}".format(classdesc.name), ident) + 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) - # Read field values for this specific class - for field_name, field_type in zip(classdesc.fields_names, classdesc.fields_types): - log_debug("Reading field: {0} - {1}".format(field_type, field_name), ident) - res = self._read_value(field_type, ident, name=field_name) - java_object.__setattr__(field_name, res) + 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) - # Handle annotations if this class has the SC_WRITE_METHOD flag - if classdesc.flags & ClassDescFlags.SC_WRITE_METHOD: - log_debug("Reading annotations for class: {0}".format(classdesc.name), ident) + 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( + java_object.annotations + ), + ident, + ) - while True: + opcode = None + while opcode != TerminalCode.TC_ENDBLOCKDATA: opcode, obj = self._read_and_exec_opcode(ident=ident + 1) - if opcode == TerminalCode.TC_ENDBLOCKDATA: - break - java_object.annotations.append(obj) + # , expect=[self.TC_ENDBLOCKDATA, self.TC_BLOCKDATA, + # self.TC_OBJECT, self.TC_NULL, self.TC_REFERENCE]) + if opcode != TerminalCode.TC_ENDBLOCKDATA: + java_object.annotations.append(obj) + log_debug("objectAnnotation value: {0}".format(obj), ident) + log_debug( + "java_object.annotations after: {0}".format( + java_object.annotations + ), + ident, + ) + + # Allow extra loading operations + if hasattr(java_object, "__extra_loading__"): + log_debug("Java object has extra loading capability.") + java_object.__extra_loading__(self, ident) + def do_string(self, parent=None, ident=0): """ Handles a TC_STRING opcode diff --git a/tests/test_v2.py b/tests/test_v2.py index 70a3e2f..e915415 100644 --- a/tests/test_v2.py +++ b/tests/test_v2.py @@ -645,6 +645,9 @@ def test_writeObject(self): self.assertEqual(expected["custom_obj"]["annotations"], super_data) 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) From 1bf2a236d93142965cf9aafb4eb554f8bcca8e8b Mon Sep 17 00:00:00 2001 From: Steven Van Ingelgem Date: Mon, 9 Jun 2025 11:06:13 +0200 Subject: [PATCH 4/4] Only an issue under v1, not v2 --- tests/test_v1.py | 11 +++++++++++ tests/test_v2.py | 11 ----------- 2 files changed, 11 insertions(+), 11 deletions(-) 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 e915415..ea8b63d 100644 --- a/tests/test_v2.py +++ b/tests/test_v2.py @@ -644,17 +644,6 @@ def test_writeObject(self): self.assertEqual(expected["custom_obj"]["field_data"], child_data) self.assertEqual(expected["custom_obj"]["annotations"], super_data) - 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) - # ------------------------------------------------------------------------------