2
# Copyright 2010,2011 Mozilla Foundation. All rights reserved.
4
# Redistribution and use in source and binary forms, with or without
5
# modification, are permitted provided that the following conditions are
8
# 1. Redistributions of source code must retain the above copyright
9
# notice, this list of conditions and the following disclaimer.
11
# 2. Redistributions in binary form must reproduce the above copyright
12
# notice, this list of conditions and the following disclaimer in
13
# the documentation and/or other materials provided with the
16
# THIS SOFTWARE IS PROVIDED BY THE MOZILLA FOUNDATION ``AS IS'' AND ANY
17
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE MOZILLA FOUNDATION OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
# The views and conclusions contained in the software and documentation
29
# are those of the authors and should not be interpreted as representing
30
# official policies, either expressed or implied, of the Mozilla
34
A module for working with XPCOM Type Libraries.
36
The XPCOM Type Library File Format is described at:
37
http://www.mozilla.org/scriptable/typelib_file.html . It is used
38
to provide type information for calling methods on XPCOM objects
39
from scripting languages such as JavaScript.
41
This module provides a set of classes representing the parts of
42
a typelib in a high-level manner, as well as methods for reading
43
and writing them from files.
45
The usable public interfaces are currently:
46
Typelib.read(input_file) - read a typelib from a file on disk or file-like
47
object, return a Typelib object.
49
xpt_dump(filename) - read a typelib from a file on disk, dump
50
the contents to stdout in a human-readable
53
Typelib() - construct a new Typelib object
54
Interface() - construct a new Interface object
55
Method() - construct a new object representing a method
56
defined on an Interface
57
Constant() - construct a new object representing a constant
58
defined on an Interface
59
Param() - construct a new object representing a parameter
61
SimpleType() - construct a new object representing a simple
63
InterfaceType() - construct a new object representing a type that
64
is an IDL-defined interface
68
from __future__ import with_statement
74
XPT_MAGIC = "XPCOM\nTypeLib\r\n\x1a"
75
TYPELIB_VERSION = (1, 2)
77
class FileFormatError(Exception):
80
class DataError(Exception):
83
# Magic for creating enums
84
def M_add_class_attribs(attribs):
85
def foo(name, bases, dict_):
88
return type(name, bases, dict_)
93
__metaclass__ = M_add_class_attribs(enumerate(names))
94
def __setattr__(self, name, value): # this makes it read-only
95
raise NotImplementedError
98
# Descriptor types as described in the spec
101
Data type of a method parameter or return value. Do not instantiate
102
this class directly. Rather, use one of its subclasses.
105
_prefixdescriptor = struct.Struct(">B")
107
# The first 18 entries are SimpleTypeDescriptor
122
# the following four values are only valid as pointers
127
# InterfaceTypeDescriptor
129
# InterfaceIsTypeDescriptor
131
# ArrayTypeDescriptor
133
# StringWithSizeTypeDescriptor
135
# WideStringWithSizeTypeDescriptor
136
'WideStringWithSize',
137
#XXX: These are also SimpleTypes (but not in the spec)
138
# http://hg.mozilla.org/mozilla-central/annotate/0e0e2516f04e/xpcom/typelib/xpt/tools/xpt_dump.c#l69
145
def __init__(self, pointer=False, reference=False):
146
self.pointer = pointer
147
self.reference = reference
148
if reference and not pointer:
149
raise Exception("If reference is True pointer must be True too")
152
def decodeflags(byte):
154
Given |byte|, an unsigned uint8 containing flag bits,
155
decode the flag bits as described in
156
http://www.mozilla.org/scriptable/typelib_file.html#TypeDescriptor
157
and return a dict of flagname: (True|False) suitable
158
for passing to Type.__init__ as **kwargs.
161
return {'pointer': bool(byte & 0x80),
162
'reference': bool(byte & 0x20),
165
def encodeflags(self):
167
Encode the flag bits of this Type object. Returns a byte.
178
def read(typelib, map, data_pool, offset):
180
Read a TypeDescriptor at |offset| from the mmaped file |map| with
181
data pool offset |data_pool|. Returns (Type, next offset),
182
where |next offset| is an offset suitable for reading the data
183
following this TypeDescriptor.
186
start = data_pool + offset - 1
187
(data,) = Type._prefixdescriptor.unpack(map[start:start + Type._prefixdescriptor.size])
188
# first three bits are the flags
190
flags = Type.decodeflags(flags)
191
# last five bits is the tag
193
offset += Type._prefixdescriptor.size
195
if tag <= Type.Tags.wchar_t_ptr or tag >= Type.Tags.UTF8String:
196
t = SimpleType.get(data, tag, flags)
197
elif tag == Type.Tags.Interface:
198
t, offset = InterfaceType.read(typelib, map, data_pool, offset, flags)
199
elif tag == Type.Tags.InterfaceIs:
200
t, offset = InterfaceIsType.read(typelib, map, data_pool, offset, flags)
201
elif tag == Type.Tags.Array:
202
t, offset = ArrayType.read(typelib, map, data_pool, offset, flags)
203
elif tag == Type.Tags.StringWithSize:
204
t, offset = StringWithSizeType.read(typelib, map, data_pool, offset, flags)
205
elif tag == Type.Tags.WideStringWithSize:
206
t, offset = WideStringWithSizeType.read(typelib, map, data_pool, offset, flags)
209
def write(self, typelib, file):
211
Write a TypeDescriptor to |file|, which is assumed
212
to be seeked to the proper position. For types other than
213
SimpleType, this is not sufficient for writing the TypeDescriptor,
214
and the subclass method must be called.
217
file.write(Type._prefixdescriptor.pack(self.encodeflags() | self.tag))
219
class SimpleType(Type):
221
A simple data type. (SimpleTypeDescriptor from the typelib specification.)
226
def __init__(self, tag, **kwargs):
227
Type.__init__(self, **kwargs)
231
def get(data, tag, flags):
233
Get a SimpleType object representing |data| (a TypeDescriptorPrefix).
234
May return an already-created object. If no cached object is found,
235
construct one with |tag| and |flags|.
238
if data not in SimpleType._cache:
239
SimpleType._cache[data] = SimpleType(tag, **flags)
240
return SimpleType._cache[data]
244
if self.tag == Type.Tags.char_ptr and self.pointer:
246
if self.tag == Type.Tags.wchar_t_ptr and self.pointer:
248
for t in dir(Type.Tags):
249
if self.tag == getattr(Type.Tags, t):
260
class InterfaceType(Type):
262
A type representing a pointer to an IDL-defined interface.
263
(InterfaceTypeDescriptor from the typelib specification.)
266
_descriptor = struct.Struct(">H")
268
def __init__(self, iface, pointer=True, **kwargs):
270
raise DataError, "InterfaceType is not valid with pointer=False"
271
Type.__init__(self, pointer=pointer, **kwargs)
273
self.tag = Type.Tags.Interface
276
def read(typelib, map, data_pool, offset, flags):
278
Read an InterfaceTypeDescriptor at |offset| from the mmaped
279
file |map| with data pool offset |data_pool|.
280
Returns (InterfaceType, next offset),
281
where |next offset| is an offset suitable for reading the data
282
following this InterfaceTypeDescriptor.
285
if not flags['pointer']:
287
start = data_pool + offset - 1
288
(iface_index,) = InterfaceType._descriptor.unpack(map[start:start + InterfaceType._descriptor.size])
289
offset += InterfaceType._descriptor.size
291
# interface indices are 1-based
292
if iface_index > 0 and iface_index <= len(typelib.interfaces):
293
iface = typelib.interfaces[iface_index - 1]
294
return InterfaceType(iface, **flags), offset
296
def write(self, typelib, file):
298
Write an InterfaceTypeDescriptor to |file|, which is assumed
299
to be seeked to the proper position.
302
Type.write(self, typelib, file)
303
# write out the interface index (1-based)
304
file.write(InterfaceType._descriptor.pack(typelib.interfaces.index(self.iface) + 1))
308
return self.iface.name
309
return "unknown interface"
311
class InterfaceIsType(Type):
313
A type representing an interface described by one of the other
314
arguments to the method. (InterfaceIsTypeDescriptor from the
315
typelib specification.)
318
_descriptor = struct.Struct(">B")
321
def __init__(self, param_index, pointer=True, **kwargs):
323
raise DataError, "InterfaceIsType is not valid with pointer=False"
324
Type.__init__(self, pointer=pointer, **kwargs)
325
self.param_index = param_index
326
self.tag = Type.Tags.InterfaceIs
329
def read(typelib, map, data_pool, offset, flags):
331
Read an InterfaceIsTypeDescriptor at |offset| from the mmaped
332
file |map| with data pool offset |data_pool|.
333
Returns (InterfaceIsType, next offset),
334
where |next offset| is an offset suitable for reading the data
335
following this InterfaceIsTypeDescriptor.
336
May return a cached value.
339
if not flags['pointer']:
341
start = data_pool + offset - 1
342
(param_index,) = InterfaceIsType._descriptor.unpack(map[start:start + InterfaceIsType._descriptor.size])
343
offset += InterfaceIsType._descriptor.size
344
if param_index not in InterfaceIsType._cache:
345
InterfaceIsType._cache[param_index] = InterfaceIsType(param_index, **flags)
346
return InterfaceIsType._cache[param_index], offset
348
def write(self, typelib, file):
350
Write an InterfaceIsTypeDescriptor to |file|, which is assumed
351
to be seeked to the proper position.
354
Type.write(self, typelib, file)
355
file.write(InterfaceIsType._descriptor.pack(self.param_index))
358
return "InterfaceIs *"
360
class ArrayType(Type):
362
A type representing an Array of elements of another type, whose
363
size and length are passed as separate parameters to a method.
364
(ArrayTypeDescriptor from the typelib specification.)
367
_descriptor = struct.Struct(">BB")
369
def __init__(self, element_type, size_is_arg_num, length_is_arg_num,
370
pointer=True, **kwargs):
372
raise DataError, "ArrayType is not valid with pointer=False"
373
Type.__init__(self, pointer=pointer, **kwargs)
374
self.element_type = element_type
375
self.size_is_arg_num = size_is_arg_num
376
self.length_is_arg_num = length_is_arg_num
377
self.tag = Type.Tags.Array
380
def read(typelib, map, data_pool, offset, flags):
382
Read an ArrayTypeDescriptor at |offset| from the mmaped
383
file |map| with data pool offset |data_pool|.
384
Returns (ArrayType, next offset),
385
where |next offset| is an offset suitable for reading the data
386
following this ArrayTypeDescriptor.
388
if not flags['pointer']:
390
start = data_pool + offset - 1
391
(size_is_arg_num, length_is_arg_num) = ArrayType._descriptor.unpack(map[start:start + ArrayType._descriptor.size])
392
offset += ArrayType._descriptor.size
393
t, offset = Type.read(typelib, map, data_pool, offset)
394
return ArrayType(t, size_is_arg_num, length_is_arg_num, **flags), offset
396
def write(self, typelib, file):
398
Write an ArrayTypeDescriptor to |file|, which is assumed
399
to be seeked to the proper position.
402
Type.write(self, typelib, file)
403
file.write(ArrayType._descriptor.pack(self.size_is_arg_num,
404
self.length_is_arg_num))
405
self.element_type.write(typelib, file)
408
return "%s []" % str(self.element_type)
410
class StringWithSizeType(Type):
412
A type representing a UTF-8 encoded string whose size and length
413
are passed as separate arguments to a method. (StringWithSizeTypeDescriptor
414
from the typelib specification.)
417
_descriptor = struct.Struct(">BB")
419
def __init__(self, size_is_arg_num, length_is_arg_num,
420
pointer=True, **kwargs):
422
raise DataError, "StringWithSizeType is not valid with pointer=False"
423
Type.__init__(self, pointer=pointer, **kwargs)
424
self.size_is_arg_num = size_is_arg_num
425
self.length_is_arg_num = length_is_arg_num
426
self.tag = Type.Tags.StringWithSize
429
def read(typelib, map, data_pool, offset, flags):
431
Read an StringWithSizeTypeDescriptor at |offset| from the mmaped
432
file |map| with data pool offset |data_pool|.
433
Returns (StringWithSizeType, next offset),
434
where |next offset| is an offset suitable for reading the data
435
following this StringWithSizeTypeDescriptor.
437
if not flags['pointer']:
439
start = data_pool + offset - 1
440
(size_is_arg_num, length_is_arg_num) = StringWithSizeType._descriptor.unpack(map[start:start + StringWithSizeType._descriptor.size])
441
offset += StringWithSizeType._descriptor.size
442
return StringWithSizeType(size_is_arg_num, length_is_arg_num, **flags), offset
444
def write(self, typelib, file):
446
Write a StringWithSizeTypeDescriptor to |file|, which is assumed
447
to be seeked to the proper position.
450
Type.write(self, typelib, file)
451
file.write(StringWithSizeType._descriptor.pack(self.size_is_arg_num,
452
self.length_is_arg_num))
457
class WideStringWithSizeType(Type):
459
A type representing a UTF-16 encoded string whose size and length
460
are passed as separate arguments to a method.
461
(WideStringWithSizeTypeDescriptor from the typelib specification.)
464
_descriptor = struct.Struct(">BB")
466
def __init__(self, size_is_arg_num, length_is_arg_num,
467
pointer=True, **kwargs):
469
raise DataError, "WideStringWithSizeType is not valid with pointer=False"
470
Type.__init__(self, pointer=pointer, **kwargs)
471
self.size_is_arg_num = size_is_arg_num
472
self.length_is_arg_num = length_is_arg_num
473
self.tag = Type.Tags.WideStringWithSize
476
def read(typelib, map, data_pool, offset, flags):
478
Read an WideStringWithSizeTypeDescriptor at |offset| from the mmaped
479
file |map| with data pool offset |data_pool|.
480
Returns (WideStringWithSizeType, next offset),
481
where |next offset| is an offset suitable for reading the data
482
following this WideStringWithSizeTypeDescriptor.
484
if not flags['pointer']:
486
start = data_pool + offset - 1
487
(size_is_arg_num, length_is_arg_num) = WideStringWithSizeType._descriptor.unpack(map[start:start + WideStringWithSizeType._descriptor.size])
488
offset += WideStringWithSizeType._descriptor.size
489
return WideStringWithSizeType(size_is_arg_num, length_is_arg_num, **flags), offset
491
def write(self, typelib, file):
493
Write a WideStringWithSizeTypeDescriptor to |file|, which is assumed
494
to be seeked to the proper position.
497
Type.write(self, typelib, file)
498
file.write(WideStringWithSizeType._descriptor.pack(self.size_is_arg_num,
499
self.length_is_arg_num))
506
A parameter to a method, or the return value of a method.
507
(ParamDescriptor from the typelib specification.)
510
_descriptorstart = struct.Struct(">B")
512
def __init__(self, type, in_=True, out=False, retval=False,
513
shared=False, dipper=False, optional=False):
515
Construct a Param object with the specified |type| and
516
flags. Params default to "in".
526
self.optional = optional
529
def decodeflags(byte):
531
Given |byte|, an unsigned uint8 containing flag bits,
532
decode the flag bits as described in
533
http://www.mozilla.org/scriptable/typelib_file.html#ParamDescriptor
534
and return a dict of flagname: (True|False) suitable
535
for passing to Param.__init__ as **kwargs
537
return {'in_': bool(byte & 0x80),
538
'out': bool(byte & 0x40),
539
'retval': bool(byte & 0x20),
540
'shared': bool(byte & 0x10),
541
'dipper': bool(byte & 0x08),
542
#XXX: Not in the spec, see:
543
# http://hg.mozilla.org/mozilla-central/annotate/0e0e2516f04e/xpcom/typelib/xpt/public/xpt_struct.h#l456
544
'optional': bool(byte & 0x04),
547
def encodeflags(self):
549
Encode the flags of this Param. Return a byte suitable for
550
writing to a typelib file.
569
def read(typelib, map, data_pool, offset):
571
Read a ParamDescriptor at |offset| from the mmaped file |map| with
572
data pool offset |data_pool|. Returns (Param, next offset),
573
where |next offset| is an offset suitable for reading the data
574
following this ParamDescriptor.
576
start = data_pool + offset - 1
577
(flags,) = Param._descriptorstart.unpack(map[start:start + Param._descriptorstart.size])
578
# only the first five bits are flags
580
flags = Param.decodeflags(flags)
581
offset += Param._descriptorstart.size
582
t, offset = Type.read(typelib, map, data_pool, offset)
583
p = Param(t, **flags)
586
def write(self, typelib, file):
588
Write a ParamDescriptor to |file|, which is assumed to be seeked
589
to the correct position.
592
file.write(Param._descriptorstart.pack(self.encodeflags()))
593
self.type.write(typelib, file)
597
Return a human-readable string representing the flags set
620
return self.prefix() + str(self.type)
622
class Method(object):
624
A method of an interface, defining its associated parameters
626
(MethodDescriptor from the typelib specification.)
629
_descriptorstart = struct.Struct(">BIB")
631
def __init__(self, name, result,
632
params=[], getter=False, setter=False, notxpcom=False,
633
constructor=False, hidden=False, optargc=False,
634
implicit_jscontext=False):
636
self._name_offset = 0
639
self.notxpcom = notxpcom
640
self.constructor = constructor
642
self.optargc = optargc
643
self.implicit_jscontext = implicit_jscontext
644
self.params = list(params)
645
if result and not isinstance(result, Param):
646
raise Exception("result must be a Param!")
649
def read_params(self, typelib, map, data_pool, offset, num_args):
651
Read |num_args| ParamDescriptors representing this Method's arguments
652
from the mmaped file |map| with data pool at the offset |data_pool|,
653
starting at |offset| into self.params. Returns the offset
654
suitable for reading the data following the ParamDescriptor array.
657
for i in range(num_args):
658
p, offset = Param.read(typelib, map, data_pool, offset)
659
self.params.append(p)
662
def read_result(self, typelib, map, data_pool, offset):
664
Read a ParamDescriptor representing this Method's return type
665
from the mmaped file |map| with data pool at the offset |data_pool|,
666
starting at |offset| into self.result. Returns the offset
667
suitable for reading the data following the ParamDescriptor.
670
self.result, offset = Param.read(typelib, map, data_pool, offset)
674
def decodeflags(byte):
676
Given |byte|, an unsigned uint8 containing flag bits,
677
decode the flag bits as described in
678
http://www.mozilla.org/scriptable/typelib_file.html#MethodDescriptor
679
and return a dict of flagname: (True|False) suitable
680
for passing to Method.__init__ as **kwargs
683
return {'getter': bool(byte & 0x80),
684
'setter': bool(byte & 0x40),
685
'notxpcom': bool(byte & 0x20),
686
'constructor': bool(byte & 0x10),
687
'hidden': bool(byte & 0x08),
688
# Not in the spec, see
689
# http://hg.mozilla.org/mozilla-central/annotate/0e0e2516f04e/xpcom/typelib/xpt/public/xpt_struct.h#l489
690
'optargc': bool(byte & 0x04),
691
'implicit_jscontext': bool(byte & 0x02),
694
def encodeflags(self):
696
Encode the flags of this Method object, return a byte suitable
697
for writing to a typelib file.
713
if self.implicit_jscontext:
718
def read(typelib, map, data_pool, offset):
720
Read a MethodDescriptor at |offset| from the mmaped file |map| with
721
data pool offset |data_pool|. Returns (Method, next offset),
722
where |next offset| is an offset suitable for reading the data
723
following this MethodDescriptor.
726
start = data_pool + offset - 1
727
flags, name_offset, num_args = Method._descriptorstart.unpack(map[start:start + Method._descriptorstart.size])
728
# only the first seven bits are flags
730
flags = Method.decodeflags(flags)
731
name = Typelib.read_string(map, data_pool, name_offset)
732
m = Method(name, None, **flags)
733
offset += Method._descriptorstart.size
734
offset = m.read_params(typelib, map, data_pool, offset, num_args)
735
offset = m.read_result(typelib, map, data_pool, offset)
738
def write(self, typelib, file):
740
Write a MethodDescriptor to |file|, which is assumed to be
741
seeked to the right position.
744
file.write(Method._descriptorstart.pack(self.encodeflags(),
747
for p in self.params:
748
p.write(typelib, file)
749
self.result.write(typelib, file)
751
def write_name(self, file, data_pool_offset):
753
Write this method's name to |file|.
754
Assumes that |file| is currently seeked to an unused portion
759
self._name_offset = file.tell() - data_pool_offset + 1
760
file.write(self.name + "\x00")
762
self._name_offset = 0
764
class Constant(object):
766
A constant value of a specific type defined on an interface.
767
(ConstantDesciptor from the typelib specification.)
770
_descriptorstart = struct.Struct(">I")
771
# Actual value is restricted to this set of types
772
#XXX: the spec lies, the source allows a bunch more
773
# http://hg.mozilla.org/mozilla-central/annotate/9c85f9aaec8c/xpcom/typelib/xpt/src/xpt_struct.c#l689
774
typemap = {Type.Tags.int16: '>h',
775
Type.Tags.uint16: '>H',
776
Type.Tags.int32: '>i',
777
Type.Tags.uint32: '>I'}
779
def __init__(self, name, type, value):
781
self._name_offset = 0
786
def read(typelib, map, data_pool, offset):
788
Read a ConstDescriptor at |offset| from the mmaped file |map| with
789
data pool offset |data_pool|. Returns (Constant, next offset),
790
where |next offset| is an offset suitable for reading the data
791
following this ConstDescriptor.
794
start = data_pool + offset - 1
795
(name_offset,) = Constant._descriptorstart.unpack(map[start:start + Constant._descriptorstart.size])
796
name = Typelib.read_string(map, data_pool, name_offset)
797
offset += Constant._descriptorstart.size
798
# Read TypeDescriptor
799
t, offset = Type.read(typelib, map, data_pool, offset)
801
if isinstance(t, SimpleType) and t.tag in Constant.typemap:
802
tt = Constant.typemap[t.tag]
803
start = data_pool + offset - 1
804
(val,) = struct.unpack(tt, map[start:start + struct.calcsize(tt)])
805
offset += struct.calcsize(tt)
806
c = Constant(name, t, val)
809
def write(self, typelib, file):
811
Write a ConstDescriptor to |file|, which is assumed
812
to be seeked to the proper position.
815
file.write(Constant._descriptorstart.pack(self._name_offset))
816
self.type.write(typelib, file)
817
tt = Constant.typemap[self.type.tag]
818
file.write(struct.pack(tt, self.value))
820
def write_name(self, file, data_pool_offset):
822
Write this constants's name to |file|.
823
Assumes that |file| is currently seeked to an unused portion
828
self._name_offset = file.tell() - data_pool_offset + 1
829
file.write(self.name + "\x00")
831
self._name_offset = 0
834
return "Constant(%s, %s, %d)" % (self.name, str(self.type), self.value)
836
class Interface(object):
838
An Interface represents an object, with its associated methods
840
(InterfaceDescriptor from the typelib specification.)
843
_direntry = struct.Struct(">16sIII")
844
_descriptorstart = struct.Struct(">HH")
846
UNRESOLVED_IID = "00000000-0000-0000-0000-000000000000"
848
def __init__(self, name, iid=UNRESOLVED_IID, namespace="",
849
resolved=False, parent=None, methods=[], constants=[],
850
scriptable=False, function=False, builtinclass=False):
851
self.resolved = resolved
852
#TODO: should validate IIDs!
855
self.namespace = namespace
856
# if unresolved, all the members following this are unusable
858
self.methods = list(methods)
859
self.constants = list(constants)
860
self.scriptable = scriptable
861
self.function = function
862
self.builtinclass = builtinclass
863
# For sanity, if someone constructs an Interface and passes
864
# in methods or constants, then it's resolved.
865
if self.methods or self.constants:
866
# make sure it has a valid IID
867
if self.iid == Interface.UNRESOLVED_IID:
868
raise DataError, "Cannot instantiate Interface %s containing methods or constants with an unresolved IID" % self.name
870
# These are only used for writing out the interface
871
self._descriptor_offset = 0
872
self._name_offset = 0
873
self._namespace_offset = 0
874
self.xpt_filename = None
877
return "Interface('%s', '%s', '%s', methods=%s)" % (self.name, self.iid, self.namespace, self.methods)
880
return "Interface(name='%s', iid='%s')" % (self.name, self.iid)
883
return hash((self.name, self.iid))
885
def __cmp__(self, other):
886
c = cmp(self.iid, other.iid)
889
c = cmp(self.name, other.name)
892
c = cmp(self.namespace, other.namespace)
895
# names and IIDs are the same, check resolved
896
if self.resolved != other.resolved:
902
# both unresolved, but names and IIDs are the same, so equal
904
#TODO: actually compare methods etc
907
def read_descriptor(self, typelib, map, data_pool):
908
offset = self._descriptor_offset
911
start = data_pool + offset - 1
912
parent, num_methods = Interface._descriptorstart.unpack(map[start:start + Interface._descriptorstart.size])
913
if parent > 0 and parent <= len(typelib.interfaces):
914
self.parent = typelib.interfaces[parent - 1]
916
offset += Interface._descriptorstart.size
917
for i in range(num_methods):
918
m, offset = Method.read(typelib, map, data_pool, offset)
919
self.methods.append(m)
921
start = data_pool + offset - 1
922
(num_constants, ) = struct.unpack(">H", map[start:start + struct.calcsize(">H")])
923
offset = offset + struct.calcsize(">H")
924
for i in range(num_constants):
925
c, offset = Constant.read(typelib, map, data_pool, offset)
926
self.constants.append(c)
928
start = data_pool + offset - 1
929
(flags, ) = struct.unpack(">B", map[start:start + struct.calcsize(">B")])
930
offset = offset + struct.calcsize(">B")
931
# only the first two bits are flags
934
self.scriptable = True
938
self.builtinclass = True
941
def write_directory_entry(self, file):
943
Write an InterfaceDirectoryEntry for this interface
944
to |file|, which is assumed to be seeked to the correct offset.
947
file.write(Interface._direntry.pack(Typelib.string_to_iid(self.iid),
949
self._namespace_offset,
950
self._descriptor_offset))
952
def write(self, typelib, file, data_pool_offset):
954
Write an InterfaceDescriptor to |file|, which is assumed
955
to be seeked to the proper position. If this interface
956
is not resolved, do not write any data.
959
if not self.resolved:
960
self._descriptor_offset = 0
962
self._descriptor_offset = file.tell() - data_pool_offset + 1
965
parent_idx = typelib.interfaces.index(self.parent) + 1
966
file.write(Interface._descriptorstart.pack(parent_idx, len(self.methods)))
967
for m in self.methods:
968
m.write(typelib, file)
969
file.write(struct.pack(">H", len(self.constants)))
970
for c in self.constants:
971
c.write(typelib, file)
977
if self.builtinclass:
979
file.write(struct.pack(">B", flags))
981
def write_names(self, file, data_pool_offset):
983
Write this interface's name and namespace to |file|,
984
as well as the names of all of its methods and constants.
985
Assumes that |file| is currently seeked to an unused portion
990
self._name_offset = file.tell() - data_pool_offset + 1
991
file.write(self.name + "\x00")
993
self._name_offset = 0
995
self._namespace_offset = file.tell() - data_pool_offset + 1
996
file.write(self.namespace + "\x00")
998
self._namespace_offset = 0
999
for m in self.methods:
1000
m.write_name(file, data_pool_offset)
1001
for c in self.constants:
1002
c.write_name(file, data_pool_offset)
1004
class Typelib(object):
1006
A typelib represents one entire typelib file and all the interfaces
1007
referenced within, whether defined entirely within the typelib or
1008
merely referenced by name or IID.
1010
Typelib objects may be instantiated directly and populated with data,
1011
or the static Typelib.read method may be called to read one from a file.
1014
_header = struct.Struct(">16sBBHIII")
1016
def __init__(self, version=TYPELIB_VERSION, interfaces=[], annotations=[]):
1018
Instantiate a new Typelib.
1021
self.version = version
1022
self.interfaces = list(interfaces)
1023
self.annotations = list(annotations)
1024
self.filename = None
1027
def iid_to_string(iid):
1029
Convert a 16-byte IID into a UUID string.
1033
return ''.join(["%02x" % ord(x) for x in s])
1035
return "%s-%s-%s-%s-%s" % (hexify(iid[:4]), hexify(iid[4:6]),
1036
hexify(iid[6:8]), hexify(iid[8:10]),
1040
def string_to_iid(iid_str):
1042
Convert a UUID string into a 16-byte IID.
1045
s = iid_str.replace('-','')
1046
return ''.join([chr(int(s[i:i+2], 16)) for i in range(0, len(s), 2)])
1049
def read_string(map, data_pool, offset):
1052
sz = map.find('\x00', data_pool + offset - 1)
1055
return map[data_pool + offset - 1:sz]
1058
def read(input_file):
1060
Read a typelib from |input_file| and return
1061
the constructed Typelib object. |input_file| can be a filename
1062
or a file-like object.
1067
expected_size = None
1068
if isinstance(input_file, basestring):
1069
filename = input_file
1070
with open(input_file, "rb") as f:
1071
st = os.fstat(f.fileno())
1072
data = f.read(st.st_size)
1073
expected_size = st.st_size
1075
data = input_file.read()
1082
interface_directory_offset,
1083
data_pool_offset) = Typelib._header.unpack(data[:Typelib._header.size])
1084
if magic != XPT_MAGIC:
1085
raise FileFormatError, "Bad magic: %s" % magic
1086
xpt = Typelib((major_ver, minor_ver))
1087
xpt.filename = filename
1088
if expected_size and file_length != expected_size:
1089
raise FileFormatError, "File is of wrong length, got %d bytes, expected %d" % (expected_size, file_length)
1090
#XXX: by spec this is a zero-based file offset. however,
1091
# the xpt_xdr code always subtracts 1 from data offsets
1092
# (because that's what you do in the data pool) so it
1093
# winds up accidentally treating this as 1-based.
1094
# Filed as: https://bugzilla.mozilla.org/show_bug.cgi?id=575343
1095
interface_directory_offset -= 1
1096
# make a half-hearted attempt to read Annotations,
1097
# since XPIDL doesn't produce any anyway.
1098
start = Typelib._header.size
1099
(anno, ) = struct.unpack(">B", data[start:start + struct.calcsize(">B")])
1100
islast = anno & 0x80
1102
if tag == 0: # EmptyAnnotation
1103
xpt.annotations.append(None)
1104
# We don't bother handling PrivateAnnotations or anything
1106
for i in range(num_interfaces):
1107
# iid, name, namespace, interface_descriptor
1108
start = interface_directory_offset + i * Interface._direntry.size
1109
end = interface_directory_offset + (i+1) * Interface._direntry.size
1110
ide = Interface._direntry.unpack(data[start:end])
1111
iid = Typelib.iid_to_string(ide[0])
1112
name = Typelib.read_string(data, data_pool_offset, ide[1])
1113
namespace = Typelib.read_string(data, data_pool_offset, ide[2])
1114
iface = Interface(name, iid, namespace)
1115
iface._descriptor_offset = ide[3]
1116
iface.xpt_filename = xpt.filename
1117
xpt.interfaces.append(iface)
1118
for iface in xpt.interfaces:
1119
iface.read_descriptor(xpt, data, data_pool_offset)
1123
return "<Typelib with %d interfaces>" % len(self.interfaces)
1125
def _sanityCheck(self):
1127
Check certain assumptions about data contained in this typelib.
1128
Sort the interfaces array by IID, check that all interfaces
1129
referenced by methods exist in the array.
1132
self.interfaces.sort()
1133
for i in self.interfaces:
1134
if i.parent and i.parent not in self.interfaces:
1135
raise DataError, "Interface %s has parent %s not present in typelib!" % (i.name, i.parent.name)
1137
for n, p in enumerate(m.params):
1138
if isinstance(p, InterfaceType) and \
1139
p.iface not in self.interfaces:
1140
raise DataError, "Interface method %s::%s, parameter %d references interface %s not present in typelib!" % (i.name, m.name, n, p.iface.name)
1141
if isinstance(m.result, InterfaceType) and m.result.iface not in self.interfaces:
1142
raise DataError, "Interface method %s::%s, result references interface %s not present in typelib!" % (i.name, m.name, m.result.iface.name)
1144
def writefd(self, fd):
1145
# write out space for a header + one empty annotation,
1146
# padded to 4-byte alignment.
1147
headersize = (Typelib._header.size + 1)
1149
headersize += 4 - headersize % 4
1150
fd.write("\x00" * headersize)
1151
# save this offset, it's the interface directory offset.
1152
interface_directory_offset = fd.tell()
1153
# write out space for an interface directory
1154
fd.write("\x00" * Interface._direntry.size * len(self.interfaces))
1155
# save this offset, it's the data pool offset.
1156
data_pool_offset = fd.tell()
1157
# write out all the interface descriptors to the data pool
1158
for i in self.interfaces:
1159
i.write_names(fd, data_pool_offset)
1160
i.write(self, fd, data_pool_offset)
1161
# now, seek back and write the header
1162
file_len = fd.tell()
1164
fd.write(Typelib._header.pack(XPT_MAGIC,
1167
len(self.interfaces),
1169
interface_directory_offset,
1171
# write an empty annotation
1172
fd.write(struct.pack(">B", 0x80))
1173
# now write the interface directory
1174
#XXX: bug-compatible with existing xpt lib, put it one byte
1175
# ahead of where it's supposed to be.
1176
fd.seek(interface_directory_offset - 1)
1177
for i in self.interfaces:
1178
i.write_directory_entry(fd)
1180
def write(self, output_file):
1182
Write the contents of this typelib to |output_file|,
1183
which can be either a filename or a file-like object.
1187
if isinstance(output_file, basestring):
1188
with open(output_file, "wb") as f:
1191
self.writefd(output_file)
1193
def dump(self, out):
1195
Print a human-readable listing of the contents of this typelib
1196
to |out|, in the format of xpt_dump.
1199
out.write("""Header:
1202
Number of interfaces: %d
1203
Annotations:\n""" % (self.version[0], self.version[1], len(self.interfaces)))
1204
for i, a in enumerate(self.annotations):
1206
out.write(" Annotation #%d is empty.\n" % i)
1207
out.write("\nInterface Directory:\n")
1208
for i in self.interfaces:
1209
out.write(" - %s::%s (%s):\n" % (i.namespace, i.name, i.iid))
1211
out.write(" [Unresolved]\n")
1214
out.write(" Parent: %s::%s\n" % (i.parent.namespace,
1216
out.write(""" Flags:
1219
Function: %s\n""" % (i.scriptable and "TRUE" or "FALSE",
1220
i.builtinclass and "TRUE" or "FALSE",
1221
i.function and "TRUE" or "FALSE"))
1222
out.write(" Methods:\n")
1223
if len(i.methods) == 0:
1224
out.write(" No Methods\n")
1227
out.write(" %s%s%s%s%s%s%s %s %s(%s);\n" % (
1228
m.getter and "G" or " ",
1229
m.setter and "S" or " ",
1230
m.hidden and "H" or " ",
1231
m.notxpcom and "N" or " ",
1232
m.constructor and "C" or " ",
1233
m.optargc and "O" or " ",
1234
m.implicit_jscontext and "J" or " ",
1237
m.params and ", ".join(str(p) for p in m.params) or ""
1239
out.write(" Constants:\n")
1240
if len(i.constants) == 0:
1241
out.write(" No Constants\n")
1243
for c in i.constants:
1244
out.write(" %s %s = %d;\n" % (c.type, c.name, c.value))
1248
Dump the contents of |file| to stdout in the format of xpt_dump.
1251
t = Typelib.read(file)
1254
def xpt_link(inputs):
1256
Link all of the xpt files in |inputs| together and return the result
1257
as a Typelib object. All entries in inputs may be filenames or
1262
if isinstance(i, Typelib):
1264
return Typelib.read(i)
1267
print >>sys.stderr, "Usage: xpt_link <destination file> <input files>"
1269
# This is the aggregate list of interfaces.
1271
# This will be a dict of replaced interface -> replaced with
1272
# containing interfaces that were replaced with interfaces from
1273
# another typelib, and the interface that replaced them.
1274
merged_interfaces = {}
1277
interfaces.extend(t.interfaces)
1278
# Sort interfaces by name so we can merge adjacent duplicates
1279
interfaces.sort(key=operator.attrgetter('name'))
1281
Result = enum('Equal', # Interfaces the same, doesn't matter
1282
'NotEqual', # Interfaces differ, keep both
1283
'KeepFirst', # Replace second interface with first
1284
'KeepSecond')# Replace first interface with second
1288
Compare two interfaces, determine if they're equal or
1289
completely different, or should be merged (and indicate which
1290
one to keep in that case).
1294
# Arbitrary, just pick one
1296
if i.name != j.name:
1297
if i.iid == j.iid and i.iid != Interface.UNRESOLVED_IID:
1298
# Same IID but different names: raise an exception.
1300
"Typelibs contain definitions of interface %s" \
1301
" with different names (%s (%s) vs %s (%s))!" % \
1302
(i.iid, i.name, i.xpt_filename, j.name, j.xpt_filename)
1303
# Otherwise just different interfaces.
1304
return Result.NotEqual
1305
# Interfaces have the same name, so either they need to be merged
1306
# or there's a data error. Sort out which one to keep
1307
if i.resolved != j.resolved:
1308
# prefer resolved interfaces over unresolved
1310
assert i.iid == j.iid or i.iid == Interface.UNRESOLVED_IID
1312
return Result.KeepSecond
1314
assert i.iid == j.iid or j.iid == Interface.UNRESOLVED_IID
1316
return Result.KeepFirst
1317
elif i.iid != j.iid:
1318
# Prefer unresolved interfaces with valid IIDs
1319
if j.iid == Interface.UNRESOLVED_IID:
1321
assert not j.resolved
1322
return Result.KeepFirst
1323
elif i.iid == Interface.UNRESOLVED_IID:
1325
assert not i.resolved
1326
return Result.KeepSecond
1328
# Same name but different IIDs: raise an exception.
1330
"Typelibs contain definitions of interface %s" \
1331
" with different IIDs (%s (%s) vs %s (%s))!" % \
1332
(i.name, i.iid, i.xpt_filename, \
1333
j.iid, j.xpt_filename)
1334
raise DataError, "No idea what happened here: %s:%s (%s), %s:%s (%s)" % \
1335
(i.name, i.iid, i.xpt_filename, j.name, j.iid, j.xpt_filename)
1337
# Compare interfaces pairwise to find duplicates that should be merged.
1339
while i < len(interfaces):
1340
res = compare(interfaces[i-1], interfaces[i])
1341
if res == Result.NotEqual:
1343
elif res == Result.Equal:
1344
# Need to drop one but it doesn't matter which
1346
elif res == Result.KeepFirst:
1347
merged_interfaces[interfaces[i]] = interfaces[i-1]
1349
elif res == Result.KeepSecond:
1350
merged_interfaces[interfaces[i-1]] = interfaces[i]
1353
# Now fixup any merged interfaces
1355
if isinstance(t, InterfaceType) and t.iface in merged_interfaces:
1356
t.iface = merged_interfaces[t.iface]
1357
elif isinstance(t, ArrayType) and \
1358
isinstance(t.element_type, InterfaceType) and \
1359
t.element_type.iface in merged_interfaces:
1360
t.element_type.iface = merged_interfaces[t.element_type.iface]
1362
for i in interfaces:
1363
# Replace parent references
1364
if i.parent in merged_interfaces:
1365
i.parent = merged_interfaces[i.parent]
1367
# Replace InterfaceType params and return values
1368
checkType(m.result.type)
1371
# Re-sort interfaces (by IID)
1373
return Typelib(interfaces=interfaces)
1375
if __name__ == '__main__':
1376
if len(sys.argv) < 3:
1377
print >>sys.stderr, "xpt <dump|link> <files>"
1379
if sys.argv[1] == 'dump':
1380
xpt_dump(sys.argv[2])
1381
elif sys.argv[1] == 'link':
1382
xpt_link(sys.argv[3:]).write(sys.argv[2])