• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tcalmant / python-javaobj / 8591112367

07 Apr 2024 07:18PM UTC coverage: 78.701%. First build
8591112367

push

github

tcalmant
Added 3.11 & 3.12 to GitHub actions

1611 of 2047 relevant lines covered (78.7%)

4.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.16
/javaobj/v1/unmarshaller.py
1
#!/usr/bin/python
2
# -- Content-Encoding: utf-8 --
3
"""
6✔
4
Provides functions for reading Java objects serialized by ObjectOutputStream.
5
This form of object representation is a standard data interchange format in
6
Java world.
7

8
javaobj module exposes an API familiar to users of the standard library
9
marshal, pickle and json modules.
10

11
See:
12
http://download.oracle.com/javase/6/docs/platform/serialization/spec/protocol.html
13

14
:authors: Volodymyr Buell, Thomas Calmant
15
:license: Apache License 2.0
16
:version: 0.4.4
17
:status: Alpha
18

19
..
20

21
    Copyright 2024 Thomas Calmant
22

23
    Licensed under the Apache License, Version 2.0 (the "License");
24
    you may not use this file except in compliance with the License.
25
    You may obtain a copy of the License at
26

27
        http://www.apache.org/licenses/LICENSE-2.0
28

29
    Unless required by applicable law or agreed to in writing, software
30
    distributed under the License is distributed on an "AS IS" BASIS,
31
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
32
    See the License for the specific language governing permissions and
33
    limitations under the License.
34
"""
35

36
from __future__ import absolute_import
6✔
37

38
# Standard library
39
from typing import Any, Union
6✔
40
import os
6✔
41
import struct
6✔
42

43
# Javaobj modules
44
from .beans import (
6✔
45
    JavaClass,
46
    JavaString,
47
    JavaObject,
48
    JavaByteArray,
49
    JavaEnum,
50
    JavaArray,
51
)
52
from ..constants import (
6✔
53
    StreamConstants,
54
    ClassDescFlags,
55
    TerminalCode,
56
    TypeCode,
57
    StreamCodeDebug,
58
)
59
from ..utils import (
6✔
60
    log_debug,
61
    log_error,
62
    read_to_str,
63
    to_unicode,
64
    unicode_char,
65
    hexdump,
66
)
67

68
numpy = None  # Imported only when really used
6✔
69

70
# ------------------------------------------------------------------------------
71

72
__all__ = ("JavaObjectUnmarshaller",)
6✔
73

74
# Module version
75
__version_info__ = (0, 4, 4)
6✔
76
__version__ = ".".join(str(x) for x in __version_info__)
6✔
77

78
# Documentation strings format
79
__docformat__ = "restructuredtext en"
6✔
80

81
# ------------------------------------------------------------------------------
82

83
# Convertion of a Java type char to its NumPy equivalent
84
NUMPY_TYPE_MAP = {
6✔
85
    TypeCode.TYPE_BYTE: "B",
86
    TypeCode.TYPE_CHAR: "b",
87
    TypeCode.TYPE_DOUBLE: ">d",
88
    TypeCode.TYPE_FLOAT: ">f",
89
    TypeCode.TYPE_INTEGER: ">i",
90
    TypeCode.TYPE_LONG: ">l",
91
    TypeCode.TYPE_SHORT: ">h",
92
    TypeCode.TYPE_BOOLEAN: ">B",
93
}
94

95
# ------------------------------------------------------------------------------
96

97

98
class JavaObjectUnmarshaller:
6✔
99
    """
100
    Deserializes a Java serialization stream
101
    """
102

103
    def __init__(self, stream, use_numpy_arrays=False):
6✔
104
        """
105
        Sets up members
106

107
        :param stream: An input stream (opened in binary/bytes mode)
108
        :raise IOError: Invalid input stream
109
        """
110
        self.use_numpy_arrays = use_numpy_arrays
6✔
111

112
        # Numpy array support
113
        if self.use_numpy_arrays:
6✔
114
            try:
×
115
                global numpy
116
                import numpy as np
×
117

118
                numpy = np
×
119
            except ImportError:
×
120
                pass
×
121

122
        # Check stream
123
        if stream is None:
6✔
124
            raise IOError("No input stream given")
×
125

126
        # Prepare the association Terminal Symbol -> Reading method
127
        self.opmap = {
6✔
128
            TerminalCode.TC_NULL: self.do_null,
129
            TerminalCode.TC_CLASSDESC: self.do_classdesc,
130
            TerminalCode.TC_OBJECT: self.do_object,
131
            TerminalCode.TC_STRING: self.do_string,
132
            TerminalCode.TC_LONGSTRING: self.do_string_long,
133
            TerminalCode.TC_ARRAY: self.do_array,
134
            TerminalCode.TC_CLASS: self.do_class,
135
            TerminalCode.TC_BLOCKDATA: self.do_blockdata,
136
            TerminalCode.TC_BLOCKDATALONG: self.do_blockdata_long,
137
            TerminalCode.TC_REFERENCE: self.do_reference,
138
            TerminalCode.TC_ENUM: self.do_enum,
139
            # note that we are reusing do_null:
140
            TerminalCode.TC_ENDBLOCKDATA: self.do_null,
141
        }
142

143
        # Set up members
144
        self.current_object = None
6✔
145
        self.reference_counter = 0
6✔
146
        self.references = []
6✔
147
        self.object_transformers = []
6✔
148
        self.object_stream = stream
6✔
149

150
        # Read the stream header (magic & version)
151
        self._readStreamHeader()
6✔
152

153
    def readObject(self, ignore_remaining_data=False):
6✔
154
        """
155
        Reads an object from the input stream
156

157
        :param ignore_remaining_data: If True, don't log an error when
158
                                      unused trailing bytes are remaining
159
        :return: The unmarshalled object
160
        :raise Exception: Any exception that occurred during unmarshalling
161
        """
162
        try:
6✔
163
            # TODO: add expects
164
            _, res = self._read_and_exec_opcode(ident=0)
6✔
165

166
            position_bak = self.object_stream.tell()
6✔
167
            the_rest = self.object_stream.read()
6✔
168
            if not ignore_remaining_data and len(the_rest) != 0:
6✔
169
                log_error(
6✔
170
                    "Warning!!!!: Stream still has {0} bytes left. "
171
                    "Enable debug mode of logging to see the hexdump.".format(
172
                        len(the_rest)
173
                    )
174
                )
175
                log_debug("\n{0}".format(hexdump(the_rest)))
6✔
176
            else:
177
                log_debug("Java Object unmarshalled successfully!")
6✔
178

179
            self.object_stream.seek(position_bak)
6✔
180
            return res
6✔
181
        except Exception:
×
182
            self._oops_dump_state(ignore_remaining_data)
×
183
            raise
×
184

185
    def add_transformer(self, transformer):
6✔
186
        """
187
        Appends an object transformer to the deserialization process
188

189
        :param transformer: An object with a transform(obj) method
190
        """
191
        self.object_transformers.append(transformer)
6✔
192

193
    def _readStreamHeader(self):
6✔
194
        """
195
        Reads the magic header of a Java serialization stream
196

197
        :raise IOError: Invalid magic header (not a Java stream)
198
        """
199
        (magic, version) = self._readStruct(">HH")
6✔
200
        if (
6✔
201
            magic != StreamConstants.STREAM_MAGIC
202
            or version != StreamConstants.STREAM_VERSION
203
        ):
204
            raise IOError(
×
205
                "The stream is not java serialized object. "
206
                "Invalid stream header: {0:04X}{1:04X}".format(magic, version)
207
            )
208

209
    def _read_and_exec_opcode(self, ident=0, expect=None):
6✔
210
        """
211
        Reads the next opcode, and executes its handler
212

213
        :param ident: Log identation level
214
        :param expect: A list of expected opcodes
215
        :return: A tuple: (opcode, result of the handler)
216
        :raise IOError: Read opcode is not one of the expected ones
217
        :raise RuntimeError: Unknown opcode
218
        """
219
        position = self.object_stream.tell()
6✔
220
        (opid,) = self._readStruct(">B")
6✔
221
        log_debug(
6✔
222
            "OpCode: 0x{0:X} -- {1} (at offset 0x{2:X})".format(
223
                opid, StreamCodeDebug.op_id(opid), position
224
            ),
225
            ident,
226
        )
227

228
        if expect and opid not in expect:
6✔
229
            raise IOError(
×
230
                "Unexpected opcode 0x{0:X} -- {1} "
231
                "(at offset 0x{2:X})".format(
232
                    opid, StreamCodeDebug.op_id(opid), position
233
                )
234
            )
235

236
        try:
6✔
237
            handler = self.opmap[opid]
6✔
238
        except KeyError:
×
239
            raise RuntimeError(
×
240
                "Unknown OpCode in the stream: 0x{0:X} "
241
                "(at offset 0x{1:X})".format(opid, position)
242
            )
243
        else:
244
            return opid, handler(ident=ident)
6✔
245

246
    def _readStruct(self, unpack):
6✔
247
        """
248
        Reads from the input stream, using struct
249

250
        :param unpack: An unpack format string
251
        :return: The result of struct.unpack (tuple)
252
        :raise RuntimeError: End of stream reached during unpacking
253
        """
254
        length = struct.calcsize(unpack)
6✔
255
        ba = self.object_stream.read(length)
6✔
256

257
        if len(ba) != length:
6✔
258
            raise RuntimeError(
×
259
                "Stream has been ended unexpectedly while unmarshaling."
260
            )
261

262
        return struct.unpack(unpack, ba)
6✔
263

264
    def _readString(self, length_fmt="H"):
6✔
265
        """
266
        Reads a serialized string
267

268
        :param length_fmt: Structure format of the string length (H or Q)
269
        :return: The deserialized string
270
        :raise RuntimeError: Unexpected end of stream
271
        """
272
        (length,) = self._readStruct(">{0}".format(length_fmt))
6✔
273
        ba = self.object_stream.read(length)
6✔
274
        return to_unicode(ba)
6✔
275

276
    def do_classdesc(self, parent=None, ident=0):
6✔
277
        """
278
        Handles a TC_CLASSDESC opcode
279

280
        :param parent:
281
        :param ident: Log indentation level
282
        :return: A JavaClass object
283
        """
284
        # TC_CLASSDESC className serialVersionUID newHandle classDescInfo
285
        # classDescInfo:
286
        #   classDescFlags fields classAnnotation superClassDesc
287
        # classDescFlags:
288
        #   (byte)                 // Defined in Terminal Symbols and Constants
289
        # fields:
290
        #   (short)<count>  fieldDesc[count]
291

292
        # fieldDesc:
293
        #   primitiveDesc
294
        #   objectDesc
295
        # primitiveDesc:
296
        #   prim_typecode fieldName
297
        # objectDesc:
298
        #   obj_typecode fieldName className1
299
        clazz = JavaClass()
6✔
300
        log_debug("[classdesc]", ident)
6✔
301
        class_name = self._readString()
6✔
302
        clazz.name = class_name
6✔
303
        log_debug("Class name: %s" % class_name, ident)
6✔
304

305
        # serialVersionUID is a Java (signed) long => 8 bytes
306
        serialVersionUID, classDescFlags = self._readStruct(">qB")
6✔
307
        clazz.serialVersionUID = serialVersionUID
6✔
308
        clazz.flags = classDescFlags
6✔
309

310
        self._add_reference(clazz, ident)
6✔
311

312
        log_debug(
6✔
313
            "Serial: 0x{0:X} / {0:d} - classDescFlags: 0x{1:X} {2}".format(
314
                serialVersionUID,
315
                classDescFlags,
316
                StreamCodeDebug.flags(classDescFlags),
317
            ),
318
            ident,
319
        )
320
        (length,) = self._readStruct(">H")
6✔
321
        log_debug("Fields num: 0x{0:X}".format(length), ident)
6✔
322

323
        clazz.fields_names = []
6✔
324
        clazz.fields_types = []
6✔
325
        for fieldId in range(length):
6✔
326
            (typecode,) = self._readStruct(">B")
6✔
327
            field_name = self._readString()
6✔
328
            base_field_type = self._convert_char_to_type(typecode)
6✔
329

330
            log_debug("> Reading field {0}".format(field_name), ident)
6✔
331

332
            if base_field_type == TypeCode.TYPE_ARRAY:
6✔
333
                _, field_type = self._read_and_exec_opcode(
6✔
334
                    ident=ident + 1,
335
                    expect=(TerminalCode.TC_STRING, TerminalCode.TC_REFERENCE),
336
                )
337

338
                if type(field_type) is not JavaString:  # pylint:disable=C0123
6✔
339
                    raise AssertionError(
×
340
                        "Field type must be a JavaString, "
341
                        "not {0}".format(type(field_type))
342
                    )
343

344
            elif base_field_type == TypeCode.TYPE_OBJECT:
6✔
345
                _, field_type = self._read_and_exec_opcode(
6✔
346
                    ident=ident + 1,
347
                    expect=(TerminalCode.TC_STRING, TerminalCode.TC_REFERENCE),
348
                )
349

350
                if isinstance(field_type, JavaClass):
6✔
351
                    # FIXME: ugly trick
352
                    field_type = JavaString(field_type.name)
×
353

354
                if type(field_type) is not JavaString:  # pylint:disable=C0123
6✔
355
                    raise AssertionError(
×
356
                        "Field type must be a JavaString, "
357
                        "not {0}".format(type(field_type))
358
                    )
359
            else:
360
                # Convert the TypeCode to its char value
361
                field_type = JavaString(str(chr(base_field_type.value)))
6✔
362

363
            log_debug(
6✔
364
                "< FieldName: 0x{0:X} Name:{1} Type:{2} ID:{3}".format(
365
                    typecode, field_name, field_type, fieldId
366
                ),
367
                ident,
368
            )
369
            assert field_name is not None
6✔
370
            assert field_type is not None
6✔
371

372
            clazz.fields_names.append(field_name)
6✔
373
            clazz.fields_types.append(field_type)
6✔
374

375
        if parent:
6✔
376
            parent.__fields = clazz.fields_names  # pylint:disable=W0212
×
377
            parent.__types = clazz.fields_types  # pylint:disable=W0212
×
378

379
        # classAnnotation
380
        (opid,) = self._readStruct(">B")
6✔
381
        log_debug(
6✔
382
            "OpCode: 0x{0:X} -- {1} (classAnnotation)".format(
383
                opid, StreamCodeDebug.op_id(opid)
384
            ),
385
            ident,
386
        )
387
        if opid != TerminalCode.TC_ENDBLOCKDATA:
6✔
388
            raise NotImplementedError("classAnnotation isn't implemented yet")
×
389

390
        # superClassDesc
391
        log_debug("Reading Super Class of {0}".format(clazz.name), ident)
6✔
392
        _, superclassdesc = self._read_and_exec_opcode(
6✔
393
            ident=ident + 1,
394
            expect=(
395
                TerminalCode.TC_CLASSDESC,
396
                TerminalCode.TC_NULL,
397
                TerminalCode.TC_REFERENCE,
398
            ),
399
        )
400
        log_debug(
6✔
401
            "Super Class for {0}: {1}".format(clazz.name, str(superclassdesc)),
402
            ident,
403
        )
404
        clazz.superclass = superclassdesc
6✔
405
        return clazz
6✔
406

407
    def do_blockdata(self, parent=None, ident=0):
6✔
408
        """
409
        Handles TC_BLOCKDATA opcode
410

411
        :param parent:
412
        :param ident: Log indentation level
413
        :return: A string containing the block data
414
        """
415
        # TC_BLOCKDATA (unsigned byte)<size> (byte)[size]
416
        log_debug("[blockdata]", ident)
6✔
417
        (length,) = self._readStruct(">B")
6✔
418
        ba = self.object_stream.read(length)
6✔
419

420
        # Ensure we have an str
421
        return read_to_str(ba)
6✔
422

423
    def do_blockdata_long(self, parent=None, ident=0):
6✔
424
        """
425
        Handles TC_BLOCKDATALONG opcode
426

427
        :param parent:
428
        :param ident: Log indentation level
429
        :return: A string containing the block data
430
        """
431
        # TC_BLOCKDATALONG (int)<size> (byte)[size]
432
        log_debug("[blockdatalong]", ident)
×
433
        (length,) = self._readStruct(">I")
×
434
        ba = self.object_stream.read(length)
×
435

436
        # Ensure we have an str
437
        return read_to_str(ba)
×
438

439
    def do_class(self, parent=None, ident=0):
6✔
440
        """
441
        Handles TC_CLASS opcode
442

443
        :param parent:
444
        :param ident: Log indentation level
445
        :return: A JavaClass object
446
        """
447
        # TC_CLASS classDesc newHandle
448
        log_debug("[class]", ident)
6✔
449

450
        # TODO: what to do with "(ClassDesc)prevObject".
451
        # (see 3rd line for classDesc:)
452
        _, classdesc = self._read_and_exec_opcode(
6✔
453
            ident=ident + 1,
454
            expect=(
455
                TerminalCode.TC_CLASSDESC,
456
                TerminalCode.TC_PROXYCLASSDESC,
457
                TerminalCode.TC_NULL,
458
                TerminalCode.TC_REFERENCE,
459
            ),
460
        )
461
        log_debug("Classdesc: {0}".format(classdesc), ident)
6✔
462
        self._add_reference(classdesc, ident)
6✔
463
        return classdesc
6✔
464

465
    def do_object(self, parent=None, ident=0):
6✔
466
        """
467
        Handles a TC_OBJECT opcode
468

469
        :param parent:
470
        :param ident: Log indentation level
471
        :return: A JavaClass object
472
        """
473
        # TC_OBJECT classDesc newHandle classdata[]  // data for each class
474
        java_object = JavaObject()
6✔
475
        log_debug("[object]", ident)
6✔
476
        log_debug(
6✔
477
            "java_object.annotations just after instantiation: {0}".format(
478
                java_object.annotations
479
            ),
480
            ident,
481
        )
482

483
        # TODO: what to do with "(ClassDesc)prevObject".
484
        # (see 3rd line for classDesc:)
485
        opcode, classdesc = self._read_and_exec_opcode(
6✔
486
            ident=ident + 1,
487
            expect=(
488
                TerminalCode.TC_CLASSDESC,
489
                TerminalCode.TC_PROXYCLASSDESC,
490
                TerminalCode.TC_NULL,
491
                TerminalCode.TC_REFERENCE,
492
            ),
493
        )
494
        # self.TC_REFERENCE hasn't shown in spec, but actually is here
495

496
        # Create object
497
        for transformer in self.object_transformers:
6✔
498
            java_object = transformer.create(classdesc, self)
6✔
499
            if java_object is not None:
6✔
500
                break
6✔
501

502
        # Store classdesc of this object
503
        java_object.classdesc = classdesc
6✔
504

505
        # Store the reference
506
        self._add_reference(java_object, ident)
6✔
507

508
        # classdata[]
509

510
        if (
6✔
511
            classdesc.flags & ClassDescFlags.SC_EXTERNALIZABLE
512
            and not classdesc.flags & ClassDescFlags.SC_BLOCK_DATA
513
        ):
514
            # TODO:
515
            raise NotImplementedError("externalContents isn't implemented yet")
×
516

517
        if classdesc.flags & ClassDescFlags.SC_SERIALIZABLE:
6✔
518
            # TODO: look at ObjectInputStream.readSerialData()
519
            # FIXME: Handle the SC_WRITE_METHOD flag
520

521
            # create megalist
522
            tempclass = classdesc
6✔
523
            megalist = []
6✔
524
            megatypes = []
6✔
525
            log_debug("Constructing class...", ident)
6✔
526
            while tempclass:
6✔
527
                log_debug("Class: {0}".format(tempclass.name), ident + 1)
6✔
528
                class_fields_str = " - ".join(
6✔
529
                    " ".join((str(field_type), field_name))
530
                    for field_type, field_name in zip(
531
                        tempclass.fields_types, tempclass.fields_names
532
                    )
533
                )
534
                if class_fields_str:
6✔
535
                    log_debug(class_fields_str, ident + 2)
6✔
536

537
                fieldscopy = tempclass.fields_names[:]
6✔
538
                fieldscopy.extend(megalist)
6✔
539
                megalist = fieldscopy
6✔
540

541
                fieldscopy = tempclass.fields_types[:]
6✔
542
                fieldscopy.extend(megatypes)
6✔
543
                megatypes = fieldscopy
6✔
544

545
                tempclass = tempclass.superclass
6✔
546

547
            log_debug("Values count: {0}".format(len(megalist)), ident)
6✔
548
            log_debug("Prepared list of values: {0}".format(megalist), ident)
6✔
549
            log_debug("Prepared list of types: {0}".format(megatypes), ident)
6✔
550

551
            for field_name, field_type in zip(megalist, megatypes):
6✔
552
                log_debug(
6✔
553
                    "Reading field: {0} - {1}".format(field_type, field_name)
554
                )
555
                res = self._read_value(field_type, ident, name=field_name)
6✔
556
                java_object.__setattr__(field_name, res)
6✔
557

558
        if (
6✔
559
            classdesc.flags & ClassDescFlags.SC_SERIALIZABLE
560
            and classdesc.flags & ClassDescFlags.SC_WRITE_METHOD
561
            or classdesc.flags & ClassDescFlags.SC_EXTERNALIZABLE
562
            and classdesc.flags & ClassDescFlags.SC_BLOCK_DATA
563
            or classdesc.superclass is not None
564
            and classdesc.superclass.flags & ClassDescFlags.SC_SERIALIZABLE
565
            and classdesc.superclass.flags & ClassDescFlags.SC_WRITE_METHOD
566
        ):
567
            # objectAnnotation
568
            log_debug(
6✔
569
                "java_object.annotations before: {0}".format(
570
                    java_object.annotations
571
                ),
572
                ident,
573
            )
574

575
            while opcode != TerminalCode.TC_ENDBLOCKDATA:
6✔
576
                opcode, obj = self._read_and_exec_opcode(ident=ident + 1)
6✔
577
                # , expect=[self.TC_ENDBLOCKDATA, self.TC_BLOCKDATA,
578
                # self.TC_OBJECT, self.TC_NULL, self.TC_REFERENCE])
579
                if opcode != TerminalCode.TC_ENDBLOCKDATA:
6✔
580
                    java_object.annotations.append(obj)
6✔
581

582
                log_debug("objectAnnotation value: {0}".format(obj), ident)
6✔
583

584
            log_debug(
6✔
585
                "java_object.annotations after: {0}".format(
586
                    java_object.annotations
587
                ),
588
                ident,
589
            )
590

591
        # Allow extra loading operations
592
        if hasattr(java_object, "__extra_loading__"):
6✔
593
            log_debug("Java object has extra loading capability.")
6✔
594
            java_object.__extra_loading__(self, ident)
6✔
595

596
        log_debug(">>> java_object: {0}".format(java_object), ident)
6✔
597
        return java_object
6✔
598

599
    def do_string(self, parent=None, ident=0):
6✔
600
        """
601
        Handles a TC_STRING opcode
602

603
        :param parent:
604
        :param ident: Log indentation level
605
        :return: A string
606
        """
607
        log_debug("[string]", ident)
6✔
608
        ba = JavaString(self._readString())
6✔
609
        self._add_reference(ba, ident)
6✔
610
        return ba
6✔
611

612
    def do_string_long(self, parent=None, ident=0):
6✔
613
        """
614
        Handles a TC_LONGSTRING opcode
615

616
        :param parent:
617
        :param ident: Log indentation level
618
        :return: A string
619
        """
620
        log_debug("[long string]", ident)
×
621
        ba = JavaString(self._readString("Q"))
×
622
        self._add_reference(ba, ident)
×
623
        return ba
×
624

625
    def do_array(self, parent=None, ident=0):
6✔
626
        """
627
        Handles a TC_ARRAY opcode
628

629
        :param parent:
630
        :param ident: Log indentation level
631
        :return: A list of deserialized objects
632
        """
633
        # TC_ARRAY classDesc newHandle (int)<size> values[size]
634
        log_debug("[array]", ident)
6✔
635
        _, classdesc = self._read_and_exec_opcode(
6✔
636
            ident=ident + 1,
637
            expect=(
638
                TerminalCode.TC_CLASSDESC,
639
                TerminalCode.TC_PROXYCLASSDESC,
640
                TerminalCode.TC_NULL,
641
                TerminalCode.TC_REFERENCE,
642
            ),
643
        )
644

645
        array = JavaArray(classdesc)
6✔
646

647
        self._add_reference(array, ident)
6✔
648

649
        (size,) = self._readStruct(">i")
6✔
650
        log_debug("size: {0}".format(size), ident)
6✔
651

652
        array_type_code = TypeCode(ord(classdesc.name[0]))
6✔
653
        assert array_type_code == TypeCode.TYPE_ARRAY
6✔
654
        type_code = TypeCode(ord(classdesc.name[1]))
6✔
655

656
        if type_code in (TypeCode.TYPE_OBJECT, TypeCode.TYPE_ARRAY):
6✔
657
            for _ in range(size):
6✔
658
                _, res = self._read_and_exec_opcode(ident=ident + 1)
6✔
659
                log_debug("Object value: {0}".format(res), ident)
6✔
660
                array.append(res)
6✔
661
        elif type_code == TypeCode.TYPE_BYTE:
6✔
662
            array = JavaByteArray(self.object_stream.read(size), classdesc)
6✔
663
        elif self.use_numpy_arrays and numpy is not None:
6✔
664
            array = numpy.fromfile(
×
665
                self.object_stream,
666
                dtype=NUMPY_TYPE_MAP[type_code],
667
                count=size,
668
            )
669
        else:
670
            for _ in range(size):
6✔
671
                res = self._read_value(type_code, ident)
6✔
672
                log_debug("Native value: {0}".format(repr(res)), ident)
6✔
673
                array.append(res)
6✔
674

675
        return array
6✔
676

677
    def do_reference(self, parent=None, ident=0):
6✔
678
        """
679
        Handles a TC_REFERENCE opcode
680

681
        :param parent:
682
        :param ident: Log indentation level
683
        :return: The referenced object
684
        """
685
        (handle,) = self._readStruct(">L")
6✔
686
        log_debug("## Reference handle: 0x{0:X}".format(handle), ident)
6✔
687
        ref = self.references[handle - StreamConstants.BASE_REFERENCE_IDX]
6✔
688
        log_debug("###-> Type: {0} - Value: {1}".format(type(ref), ref), ident)
6✔
689
        return ref
6✔
690

691
    @staticmethod
6✔
692
    def do_null(parent=None, ident=0):
6✔
693
        """
694
        Handles a TC_NULL opcode
695

696
        :param parent:
697
        :param ident: Log indentation level
698
        :return: Always None
699
        """
700
        return None
6✔
701

702
    def do_enum(self, parent=None, ident=0):
6✔
703
        """
704
        Handles a TC_ENUM opcode
705

706
        :param parent:
707
        :param ident: Log indentation level
708
        :return: A JavaEnum object
709
        """
710
        # TC_ENUM classDesc newHandle enumConstantName
711
        enum = JavaEnum()
6✔
712
        _, classdesc = self._read_and_exec_opcode(
6✔
713
            ident=ident + 1,
714
            expect=(
715
                TerminalCode.TC_CLASSDESC,
716
                TerminalCode.TC_PROXYCLASSDESC,
717
                TerminalCode.TC_NULL,
718
                TerminalCode.TC_REFERENCE,
719
            ),
720
        )
721
        enum.classdesc = classdesc
6✔
722
        self._add_reference(enum, ident)
6✔
723
        (
6✔
724
            _,
725
            enumConstantName,
726
        ) = self._read_and_exec_opcode(  # pylint:disable=C0103
727
            ident=ident + 1,
728
            expect=(TerminalCode.TC_STRING, TerminalCode.TC_REFERENCE),
729
        )
730
        enum.constant = enumConstantName
6✔
731
        return enum
6✔
732

733
    def _read_value(self, raw_field_type, ident, name=""):
6✔
734
        # type: (Union[bytes, int, TypeCode], int, str) -> Any
735
        """
736
        Reads the next value, of the given type
737

738
        :param raw_field_type: A serialization typecode
739
        :param ident: Log indentation
740
        :param name: Field name (for logs)
741
        :return: The read value
742
        :raise RuntimeError: Unknown field type
743
        """
744
        if isinstance(raw_field_type, TypeCode):
6✔
745
            field_type = raw_field_type
6✔
746
        elif isinstance(raw_field_type, int):
6✔
747
            field_type = TypeCode(raw_field_type)
×
748
        else:
749
            # We don't need details for arrays and objects
750
            raw_code = raw_field_type[0]
6✔
751
            if isinstance(raw_code, int):
6✔
752
                field_type = TypeCode(raw_code)
×
753
            else:
754
                field_type = TypeCode(ord(raw_code))
6✔
755

756
        if field_type == TypeCode.TYPE_BOOLEAN:
6✔
757
            (val,) = self._readStruct(">B")
6✔
758
            res = bool(val)  # type: Any
6✔
759
        elif field_type == TypeCode.TYPE_BYTE:
6✔
760
            (res,) = self._readStruct(">b")
×
761
        elif field_type == TypeCode.TYPE_CHAR:
6✔
762
            # TYPE_CHAR is defined by the serialization specification
763
            # but not used in the implementation, so this is
764
            # a hypothetical code
765
            res = unicode_char(self._readStruct(">H")[0])
6✔
766
        elif field_type == TypeCode.TYPE_SHORT:
6✔
767
            (res,) = self._readStruct(">h")
×
768
        elif field_type == TypeCode.TYPE_INTEGER:
6✔
769
            (res,) = self._readStruct(">i")
6✔
770
        elif field_type == TypeCode.TYPE_LONG:
6✔
771
            (res,) = self._readStruct(">q")
×
772
        elif field_type == TypeCode.TYPE_FLOAT:
6✔
773
            (res,) = self._readStruct(">f")
6✔
774
        elif field_type == TypeCode.TYPE_DOUBLE:
6✔
775
            (res,) = self._readStruct(">d")
×
776
        elif field_type in (TypeCode.TYPE_OBJECT, TypeCode.TYPE_ARRAY):
6✔
777
            _, res = self._read_and_exec_opcode(ident=ident + 1)
6✔
778
        else:
779
            raise RuntimeError("Unknown typecode: {0}".format(field_type))
×
780

781
        log_debug(
6✔
782
            "* {0} {1}: {2}".format(chr(field_type.value), name, repr(res)),
783
            ident,
784
        )
785
        return res
6✔
786

787
    @staticmethod
6✔
788
    def _convert_char_to_type(type_char):
5✔
789
        # type: (Any) -> TypeCode
790
        """
791
        Ensures a read character is a typecode.
792

793
        :param type_char: Read typecode
794
        :return: The typecode as an integer (using ord)
795
        :raise RuntimeError: Unknown typecode
796
        """
797
        typecode = type_char
6✔
798
        if not isinstance(type_char, int):
6✔
799
            typecode = ord(type_char)
×
800

801
        try:
6✔
802
            return TypeCode(typecode)
6✔
803
        except ValueError:
×
804
            raise RuntimeError(
×
805
                "Typecode {0} ({1}) isn't supported.".format(
806
                    type_char, typecode
807
                )
808
            )
809

810
    def _add_reference(self, obj, ident=0):
6✔
811
        """
812
        Adds a read reference to the marshaler storage
813

814
        :param obj: Reference to add
815
        :param ident: Log indentation level
816
        """
817
        log_debug(
6✔
818
            "## New reference handle 0x{0:X}: {1} -> {2}".format(
819
                len(self.references) + StreamConstants.BASE_REFERENCE_IDX,
820
                type(obj).__name__,
821
                repr(obj),
822
            ),
823
            ident,
824
        )
825
        self.references.append(obj)
6✔
826

827
    def _oops_dump_state(self, ignore_remaining_data=False):
6✔
828
        """
829
        Log a deserialization error
830

831
        :param ignore_remaining_data: If True, don't log an error when
832
                                      unused trailing bytes are remaining
833
        """
834
        log_error("==Oops state dump" + "=" * (30 - 17))
×
835
        log_error("References: {0}".format(self.references))
×
836
        log_error(
×
837
            "Stream seeking back at -16 byte "
838
            "(2nd line is an actual position!):"
839
        )
840

841
        # Do not use a keyword argument
842
        self.object_stream.seek(-16, os.SEEK_CUR)
×
843
        position = self.object_stream.tell()
×
844
        the_rest = self.object_stream.read()
×
845

846
        if not ignore_remaining_data and len(the_rest) != 0:
×
847
            log_error(
×
848
                "Warning!!!!: Stream still has {0} bytes left:\n{1}".format(
849
                    len(the_rest), hexdump(the_rest, position)
850
                )
851
            )
852

853
        log_error("=" * 30)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc