Skip to content

Commit 567210e

Browse files
committed
Fixed the reading of arrays referenced more than once in v2
Arrays were given a handle but were never stored, so a reference to an array could not be resolved. A reference found in an array field was also read as a class description instead of the array itself. Class descriptions are now stored before their annotations, as v3 already does. Fixes #62. Signed-off-by: Thomas Calmant <thomas.calmant@gmail.com>
1 parent 08d0f20 commit 567210e

5 files changed

Lines changed: 99 additions & 4 deletions

File tree

‎javaobj/v2/core.py‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -404,14 +404,18 @@ def _do_classdesc(self, type_code):
404404
class_desc.handle = handle
405405
class_desc.desc_flags = desc_flags
406406
class_desc.fields = fields
407+
408+
# Store the reference to the parsed bean before reading the
409+
# annotations and the super class: their content can refer to
410+
# this class description
411+
self._set_handle(handle, class_desc)
412+
407413
class_desc.annotations = self._read_class_annotations(class_desc)
408414
class_desc.super_class = self._read_classdesc()
409415

410416
if class_desc.super_class:
411417
class_desc.super_class.is_super_class = True
412418

413-
# Store the reference to the parsed bean
414-
self._set_handle(handle, class_desc)
415419
return class_desc
416420
elif type_code == TerminalCode.TC_NULL:
417421
# Null reference
@@ -628,7 +632,9 @@ def _read_field_value(self, field_type):
628632
# Seems required, according to issue #46
629633
return None
630634
if sub_type_code == TerminalCode.TC_REFERENCE:
631-
return self._do_classdesc(sub_type_code)
635+
# Reference to an array which has already been read,
636+
# not to a class description
637+
return self._do_reference(sub_type_code)
632638
if sub_type_code != TerminalCode.TC_ARRAY:
633639
raise ValueError(
634640
"Array type listed, but type code != TC_ARRAY"
@@ -716,7 +722,11 @@ def _do_array(self, type_code):
716722
else:
717723
content = [self._read_field_value(field_type) for _ in range(size)]
718724

719-
return JavaArray(handle, cd, field_type, content)
725+
array = JavaArray(handle, cd, field_type, content)
726+
727+
# Store the array, so that it can be found back by a reference
728+
self._set_handle(handle, array)
729+
return array
720730

721731
def _do_exception(self, type_code):
722732
# type: (int) -> ParsedJavaContent
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import java.io.FileOutputStream;
2+
import java.io.IOException;
3+
import java.io.ObjectOutputStream;
4+
import java.io.Serializable;
5+
6+
/**
7+
* Generates the fixture of issue #62: the same array is stored in two
8+
* fields, so it is written once and referenced the second time with a
9+
* TC_REFERENCE.
10+
*
11+
* Reading it required two fixes in v2: arrays were given a handle but were
12+
* never stored, so the reference could not be resolved, and a reference
13+
* found in an array field was read as a class description.
14+
*
15+
* The trailing 'marker' field detects a desynchronized stream: it is read
16+
* right after the shared array.
17+
*
18+
* Run it with: java SharedArrayExample.java
19+
*/
20+
class SharedArrayHolder implements Serializable {
21+
private static final long serialVersionUID = 1L;
22+
23+
private byte[] first;
24+
/** Same array as 'first': written as a reference. */
25+
private byte[] second;
26+
private String[] strings;
27+
/** Same array as 'strings': written as a reference. */
28+
private String[] sameStrings;
29+
/** Read after the references: wrong if the stream is desynchronized. */
30+
private int marker = 443;
31+
32+
SharedArrayHolder() {
33+
first = new byte[] {1, 2, 3};
34+
second = first;
35+
strings = new String[] {"a", "b"};
36+
sameStrings = strings;
37+
}
38+
}
39+
40+
public class SharedArrayExample {
41+
public static void main(String[] args) throws IOException {
42+
try (ObjectOutputStream oos = new ObjectOutputStream(
43+
new FileOutputStream("testSharedArray.ser"))) {
44+
oos.writeObject(new SharedArrayHolder());
45+
}
46+
}
47+
}

‎tests/testSharedArray.ser‎

213 Bytes
Binary file not shown.

‎tests/test_v2.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,29 @@ def test_collections(self):
586586

587587
# FIXME: referencing problems with the collection class
588588

589+
def test_shared_array(self):
590+
"""
591+
Tests the reference to an array stored in two fields (issue #62)
592+
593+
The array is written once and referenced the second time: the
594+
reference must be resolved to the array, and not read as a class
595+
description.
596+
"""
597+
pobj = javaobj.loads(self.read_file("testSharedArray.ser"))
598+
599+
self.assertEqual(list(pobj.first), [1, 2, 3])
600+
self.assertEqual(list(pobj.second), [1, 2, 3])
601+
self.assertEqual(list(pobj.strings), ["a", "b"])
602+
self.assertEqual(list(pobj.sameStrings), ["a", "b"])
603+
604+
# Both fields must give the very same array
605+
self.assertIs(pobj.first, pobj.second)
606+
self.assertIs(pobj.strings, pobj.sameStrings)
607+
608+
# Field written after the shared arrays: a wrong value here means
609+
# the stream has been desynchronized
610+
self.assertEqual(pobj.marker, 443)
611+
589612
def test_jceks_issue_5(self):
590613
"""
591614
Tests the handling of JCEKS issue #5

‎tests/test_v3.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,21 @@ def test_collections_obj(self) -> None:
447447
self.assertIsInstance(pobj.hashMap, dict)
448448
self.assertIsInstance(pobj.linkedList, list)
449449

450+
def test_shared_array(self) -> None:
451+
"""testSharedArray.ser - an array referenced by two fields (#62)."""
452+
pobj = self.load_bytes("testSharedArray.ser")
453+
454+
self.assertEqual(list(pobj.first), [1, 2, 3])
455+
self.assertEqual(list(pobj.second), [1, 2, 3])
456+
self.assertEqual(list(pobj.strings), ["a", "b"])
457+
458+
# Both fields must give the very same array
459+
self.assertIs(pobj.first, pobj.second)
460+
self.assertIs(pobj.strings, pobj.sameStrings)
461+
462+
# Detects a desynchronized stream
463+
self.assertEqual(pobj.marker, 443)
464+
450465
def test_bool_int_long(self) -> None:
451466
"""testBoolIntLong.ser – HashMap with Boolean / Integer / Long values."""
452467
pobj = self.load_bytes("testBoolIntLong.ser")

0 commit comments

Comments
 (0)