~ubuntu-branches/ubuntu/quantal/enigmail/quantal-security

« back to all changes in this revision

Viewing changes to mozilla/xpcom/typelib/xpt/tools/xpt.py

  • Committer: Package Import Robot
  • Author(s): Chris Coulson
  • Date: 2013-09-13 16:02:15 UTC
  • mfrom: (0.12.16)
  • Revision ID: package-import@ubuntu.com-20130913160215-u3g8nmwa0pdwagwc
Tags: 2:1.5.2-0ubuntu0.12.10.1
* New upstream release v1.5.2 for Thunderbird 24

* Build enigmail using a stripped down Thunderbird 17 build system, as it's
  now quite difficult to build the way we were doing previously, with the
  latest Firefox build system
* Add debian/patches/no_libxpcom.patch - Don't link against libxpcom, as it
  doesn't exist anymore (but exists in the build system)
* Add debian/patches/use_sdk.patch - Use the SDK version of xpt.py and
  friends
* Drop debian/patches/ipc-pipe_rename.diff (not needed anymore)
* Drop debian/patches/makefile_depth.diff (not needed anymore)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# Copyright 2010,2011 Mozilla Foundation. All rights reserved.
 
3
#
 
4
# Redistribution and use in source and binary forms, with or without
 
5
# modification, are permitted provided that the following conditions are
 
6
# met:
 
7
#
 
8
#   1. Redistributions of source code must retain the above copyright
 
9
#      notice, this list of conditions and the following disclaimer.
 
10
#
 
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
 
14
#      distribution.
 
15
#
 
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.
 
27
#
 
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
 
31
# Foundation.
 
32
 
 
33
"""
 
34
A module for working with XPCOM Type Libraries.
 
35
 
 
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.
 
40
 
 
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.
 
44
 
 
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.
 
48
 
 
49
xpt_dump(filename)     - read a typelib from a file on disk, dump
 
50
                         the contents to stdout in a human-readable
 
51
                         format.
 
52
 
 
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
 
60
                         to a method
 
61
SimpleType()           - construct a new object representing a simple
 
62
                         data type
 
63
InterfaceType()        - construct a new object representing a type that
 
64
                         is an IDL-defined interface
 
65
 
 
66
"""
 
67
 
 
68
from __future__ import with_statement
 
69
import os, sys
 
70
import struct
 
71
import operator
 
72
 
 
73
# header magic
 
74
XPT_MAGIC = "XPCOM\nTypeLib\r\n\x1a"
 
75
TYPELIB_VERSION = (1, 2)
 
76
 
 
77
class FileFormatError(Exception):
 
78
    pass
 
79
 
 
80
class DataError(Exception):
 
81
    pass
 
82
 
 
83
# Magic for creating enums
 
84
def M_add_class_attribs(attribs):
 
85
    def foo(name, bases, dict_):
 
86
        for v, k in attribs:
 
87
            dict_[k] = v
 
88
        return type(name, bases, dict_)
 
89
    return foo
 
90
 
 
91
def enum(*names):
 
92
    class Foo(object):
 
93
        __metaclass__ = M_add_class_attribs(enumerate(names))
 
94
        def __setattr__(self, name, value):  # this makes it read-only
 
95
            raise NotImplementedError
 
96
    return Foo()
 
97
 
 
98
# Descriptor types as described in the spec
 
99
class Type(object):
 
100
    """
 
101
    Data type of a method parameter or return value. Do not instantiate
 
102
    this class directly. Rather, use one of its subclasses.
 
103
    
 
104
    """
 
105
    _prefixdescriptor = struct.Struct(">B")
 
106
    Tags = enum(
 
107
        # The first 18 entries are SimpleTypeDescriptor
 
108
        'int8',
 
109
        'int16',
 
110
        'int32',
 
111
        'int64',
 
112
        'uint8',
 
113
        'uint16',
 
114
        'uint32',
 
115
        'uint64',
 
116
        'float',
 
117
        'double',
 
118
        'boolean',
 
119
        'char',
 
120
        'wchar_t',
 
121
        'void',
 
122
        # the following four values are only valid as pointers
 
123
        'nsIID',
 
124
        'DOMString',
 
125
        'char_ptr',
 
126
        'wchar_t_ptr',
 
127
        # InterfaceTypeDescriptor
 
128
        'Interface',
 
129
        # InterfaceIsTypeDescriptor
 
130
        'InterfaceIs',
 
131
        # ArrayTypeDescriptor
 
132
        'Array',
 
133
        # StringWithSizeTypeDescriptor
 
134
        'StringWithSize',
 
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
 
139
        'UTF8String',
 
140
        'CString',
 
141
        'AString',
 
142
        'jsval',
 
143
        )
 
144
 
 
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")
 
150
 
 
151
    @staticmethod
 
152
    def decodeflags(byte):
 
153
        """
 
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.
 
159
        
 
160
        """
 
161
        return {'pointer': bool(byte & 0x80),
 
162
                'reference': bool(byte & 0x20),
 
163
                }
 
164
 
 
165
    def encodeflags(self):
 
166
        """
 
167
        Encode the flag bits of this Type object. Returns a byte.
 
168
 
 
169
        """
 
170
        flags = 0
 
171
        if self.pointer:
 
172
            flags |= 0x80
 
173
        if self.reference:
 
174
            flags |= 0x20
 
175
        return flags
 
176
 
 
177
    @staticmethod
 
178
    def read(typelib, map, data_pool, offset):
 
179
        """
 
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.
 
184
        
 
185
        """
 
186
        start = data_pool + offset - 1
 
187
        (data,) = Type._prefixdescriptor.unpack(map[start:start + Type._prefixdescriptor.size])
 
188
        # first three bits are the flags
 
189
        flags = data & 0xE0
 
190
        flags = Type.decodeflags(flags)
 
191
        # last five bits is the tag
 
192
        tag = data & 0x1F
 
193
        offset += Type._prefixdescriptor.size
 
194
        t = None
 
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)
 
207
        return t, offset
 
208
 
 
209
    def write(self, typelib, file):
 
210
        """
 
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.
 
215
 
 
216
        """
 
217
        file.write(Type._prefixdescriptor.pack(self.encodeflags() | self.tag))
 
218
 
 
219
class SimpleType(Type):
 
220
    """
 
221
    A simple data type. (SimpleTypeDescriptor from the typelib specification.)
 
222
 
 
223
    """
 
224
    _cache = {}
 
225
 
 
226
    def __init__(self, tag, **kwargs):
 
227
        Type.__init__(self, **kwargs)
 
228
        self.tag = tag
 
229
 
 
230
    @staticmethod
 
231
    def get(data, tag, flags):
 
232
        """
 
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|.
 
236
        
 
237
        """
 
238
        if data not in SimpleType._cache:
 
239
            SimpleType._cache[data] = SimpleType(tag, **flags)
 
240
        return SimpleType._cache[data]
 
241
 
 
242
    def __str__(self):
 
243
        s = "unknown"
 
244
        if self.tag == Type.Tags.char_ptr and self.pointer:
 
245
            return "string"
 
246
        if self.tag == Type.Tags.wchar_t_ptr and self.pointer:
 
247
            return "wstring"
 
248
        for t in dir(Type.Tags):
 
249
            if self.tag == getattr(Type.Tags, t):
 
250
                s = t
 
251
                break
 
252
 
 
253
        if self.pointer:
 
254
            if self.reference:
 
255
                s += " &"
 
256
            else:
 
257
                s += " *"
 
258
        return s
 
259
 
 
260
class InterfaceType(Type):
 
261
    """
 
262
    A type representing a pointer to an IDL-defined interface.
 
263
    (InterfaceTypeDescriptor from the typelib specification.)
 
264
 
 
265
    """
 
266
    _descriptor = struct.Struct(">H")
 
267
 
 
268
    def __init__(self, iface, pointer=True, **kwargs):
 
269
        if not pointer:
 
270
            raise DataError, "InterfaceType is not valid with pointer=False"
 
271
        Type.__init__(self, pointer=pointer, **kwargs)
 
272
        self.iface = iface
 
273
        self.tag = Type.Tags.Interface
 
274
 
 
275
    @staticmethod
 
276
    def read(typelib, map, data_pool, offset, flags):
 
277
        """
 
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.
 
283
        
 
284
        """
 
285
        if not flags['pointer']:
 
286
            return None, offset
 
287
        start = data_pool + offset - 1
 
288
        (iface_index,) = InterfaceType._descriptor.unpack(map[start:start + InterfaceType._descriptor.size])
 
289
        offset += InterfaceType._descriptor.size
 
290
        iface = None
 
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
 
295
 
 
296
    def write(self, typelib, file):
 
297
        """
 
298
        Write an InterfaceTypeDescriptor to |file|, which is assumed
 
299
        to be seeked to the proper position.
 
300
 
 
301
        """
 
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))
 
305
 
 
306
    def __str__(self):
 
307
        if self.iface:
 
308
            return self.iface.name
 
309
        return "unknown interface"
 
310
 
 
311
class InterfaceIsType(Type):
 
312
    """
 
313
    A type representing an interface described by one of the other
 
314
    arguments to the method. (InterfaceIsTypeDescriptor from the
 
315
    typelib specification.)
 
316
    
 
317
    """
 
318
    _descriptor = struct.Struct(">B")
 
319
    _cache = {}
 
320
 
 
321
    def __init__(self, param_index, pointer=True, **kwargs):
 
322
        if not pointer:
 
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
 
327
 
 
328
    @staticmethod
 
329
    def read(typelib, map, data_pool, offset, flags):
 
330
        """
 
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.
 
337
        
 
338
        """
 
339
        if not flags['pointer']:
 
340
            return None, offset
 
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
 
347
 
 
348
    def write(self, typelib, file):
 
349
        """
 
350
        Write an InterfaceIsTypeDescriptor to |file|, which is assumed
 
351
        to be seeked to the proper position.
 
352
 
 
353
        """
 
354
        Type.write(self, typelib, file)
 
355
        file.write(InterfaceIsType._descriptor.pack(self.param_index))
 
356
 
 
357
    def __str__(self):
 
358
        return "InterfaceIs *"
 
359
 
 
360
class ArrayType(Type):
 
361
    """
 
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.)
 
365
    
 
366
    """
 
367
    _descriptor = struct.Struct(">BB")
 
368
 
 
369
    def __init__(self, element_type, size_is_arg_num, length_is_arg_num,
 
370
                 pointer=True, **kwargs):
 
371
        if not pointer:
 
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
 
378
 
 
379
    @staticmethod
 
380
    def read(typelib, map, data_pool, offset, flags):
 
381
        """
 
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.
 
387
        """
 
388
        if not flags['pointer']:
 
389
            return None, offset
 
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
 
395
 
 
396
    def write(self, typelib, file):
 
397
        """
 
398
        Write an ArrayTypeDescriptor to |file|, which is assumed
 
399
        to be seeked to the proper position.
 
400
 
 
401
        """
 
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)
 
406
 
 
407
    def __str__(self):
 
408
        return "%s []" % str(self.element_type)
 
409
 
 
410
class StringWithSizeType(Type):
 
411
    """
 
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.)
 
415
 
 
416
    """
 
417
    _descriptor = struct.Struct(">BB")
 
418
 
 
419
    def __init__(self, size_is_arg_num, length_is_arg_num,
 
420
                 pointer=True, **kwargs):
 
421
        if not pointer:
 
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
 
427
 
 
428
    @staticmethod
 
429
    def read(typelib, map, data_pool, offset, flags):
 
430
        """
 
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.
 
436
        """
 
437
        if not flags['pointer']:
 
438
            return None, offset
 
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
 
443
 
 
444
    def write(self, typelib, file):
 
445
        """
 
446
        Write a StringWithSizeTypeDescriptor to |file|, which is assumed
 
447
        to be seeked to the proper position.
 
448
 
 
449
        """
 
450
        Type.write(self, typelib, file)
 
451
        file.write(StringWithSizeType._descriptor.pack(self.size_is_arg_num,
 
452
                                                       self.length_is_arg_num))
 
453
 
 
454
    def __str__(self):
 
455
        return "string_s"
 
456
 
 
457
class WideStringWithSizeType(Type):
 
458
    """
 
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.)
 
462
 
 
463
    """
 
464
    _descriptor = struct.Struct(">BB")
 
465
 
 
466
    def __init__(self, size_is_arg_num, length_is_arg_num,
 
467
                 pointer=True, **kwargs):
 
468
        if not pointer:
 
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
 
474
 
 
475
    @staticmethod
 
476
    def read(typelib, map, data_pool, offset, flags):
 
477
        """
 
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.
 
483
        """
 
484
        if not flags['pointer']:
 
485
            return None, offset
 
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
 
490
 
 
491
    def write(self, typelib, file):
 
492
        """
 
493
        Write a WideStringWithSizeTypeDescriptor to |file|, which is assumed
 
494
        to be seeked to the proper position.
 
495
 
 
496
        """
 
497
        Type.write(self, typelib, file)
 
498
        file.write(WideStringWithSizeType._descriptor.pack(self.size_is_arg_num,
 
499
                                                           self.length_is_arg_num))
 
500
 
 
501
    def __str__(self):
 
502
        return "wstring_s"
 
503
 
 
504
class Param(object):
 
505
    """
 
506
    A parameter to a method, or the return value of a method.
 
507
    (ParamDescriptor from the typelib specification.)
 
508
 
 
509
    """
 
510
    _descriptorstart = struct.Struct(">B")
 
511
 
 
512
    def __init__(self, type, in_=True, out=False, retval=False,
 
513
                 shared=False, dipper=False, optional=False):
 
514
        """
 
515
        Construct a Param object with the specified |type| and
 
516
        flags. Params default to "in".
 
517
 
 
518
        """
 
519
 
 
520
        self.type = type
 
521
        self.in_ = in_
 
522
        self.out = out
 
523
        self.retval = retval
 
524
        self.shared = shared
 
525
        self.dipper = dipper
 
526
        self.optional = optional
 
527
 
 
528
    @staticmethod
 
529
    def decodeflags(byte):
 
530
        """
 
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
 
536
        """
 
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),
 
545
                }
 
546
 
 
547
    def encodeflags(self):
 
548
        """
 
549
        Encode the flags of this Param. Return a byte suitable for
 
550
        writing to a typelib file.
 
551
 
 
552
        """
 
553
        flags = 0
 
554
        if self.in_:
 
555
            flags |= 0x80
 
556
        if self.out:
 
557
            flags |= 0x40
 
558
        if self.retval:
 
559
            flags |= 0x20
 
560
        if self.shared:
 
561
            flags |= 0x10
 
562
        if self.dipper:
 
563
            flags |= 0x08
 
564
        if self.optional:
 
565
            flags |= 0x04
 
566
        return flags
 
567
 
 
568
    @staticmethod
 
569
    def read(typelib, map, data_pool, offset):
 
570
        """
 
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.
 
575
        """
 
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
 
579
        flags &= 0xFC
 
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)
 
584
        return p, offset
 
585
 
 
586
    def write(self, typelib, file):
 
587
        """
 
588
        Write a ParamDescriptor to |file|, which is assumed to be seeked
 
589
        to the correct position.
 
590
 
 
591
        """
 
592
        file.write(Param._descriptorstart.pack(self.encodeflags()))
 
593
        self.type.write(typelib, file)
 
594
 
 
595
    def prefix(self):
 
596
        """
 
597
        Return a human-readable string representing the flags set
 
598
        on this Param.
 
599
 
 
600
        """
 
601
        s = ""
 
602
        if self.out:
 
603
            if self.in_:
 
604
                s = "inout "
 
605
            else:
 
606
                s = "out "
 
607
        else:
 
608
            s = "in "
 
609
        if self.dipper:
 
610
            s += "dipper "
 
611
        if self.retval:
 
612
            s += "retval "
 
613
        if self.shared:
 
614
            s += "shared "
 
615
        if self.optional:
 
616
            s += "optional "
 
617
        return s
 
618
 
 
619
    def __str__(self):
 
620
        return self.prefix() + str(self.type)
 
621
 
 
622
class Method(object):
 
623
    """
 
624
    A method of an interface, defining its associated parameters
 
625
    and return value.
 
626
    (MethodDescriptor from the typelib specification.)
 
627
    
 
628
    """
 
629
    _descriptorstart = struct.Struct(">BIB")
 
630
 
 
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):
 
635
        self.name = name
 
636
        self._name_offset = 0
 
637
        self.getter = getter
 
638
        self.setter = setter
 
639
        self.notxpcom = notxpcom
 
640
        self.constructor = constructor
 
641
        self.hidden = hidden
 
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!")
 
647
        self.result = result
 
648
 
 
649
    def read_params(self, typelib, map, data_pool, offset, num_args):
 
650
        """
 
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.
 
655
        
 
656
        """
 
657
        for i in range(num_args):
 
658
            p, offset = Param.read(typelib, map, data_pool, offset)
 
659
            self.params.append(p)
 
660
        return offset
 
661
 
 
662
    def read_result(self, typelib, map, data_pool, offset):
 
663
        """
 
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.
 
668
        
 
669
        """
 
670
        self.result, offset = Param.read(typelib, map, data_pool, offset)
 
671
        return offset
 
672
 
 
673
    @staticmethod
 
674
    def decodeflags(byte):
 
675
        """
 
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
 
681
        
 
682
        """
 
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),
 
692
                }
 
693
 
 
694
    def encodeflags(self):
 
695
        """
 
696
        Encode the flags of this Method object, return a byte suitable
 
697
        for writing to a typelib file.
 
698
 
 
699
        """
 
700
        flags = 0
 
701
        if self.getter:
 
702
            flags |= 0x80
 
703
        if self.setter:
 
704
            flags |= 0x40
 
705
        if self.notxpcom:
 
706
            flags |= 0x20
 
707
        if self.constructor:
 
708
            flags |= 0x10
 
709
        if self.hidden:
 
710
            flags |= 0x08
 
711
        if self.optargc:
 
712
            flags |= 0x04
 
713
        if self.implicit_jscontext:
 
714
            flags |= 0x02
 
715
        return flags
 
716
 
 
717
    @staticmethod
 
718
    def read(typelib, map, data_pool, offset):
 
719
        """
 
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.
 
724
        
 
725
        """
 
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
 
729
        flags &= 0xFE
 
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)
 
736
        return m, offset
 
737
 
 
738
    def write(self, typelib, file):
 
739
        """
 
740
        Write a MethodDescriptor to |file|, which is assumed to be
 
741
        seeked to the right position.
 
742
 
 
743
        """
 
744
        file.write(Method._descriptorstart.pack(self.encodeflags(),
 
745
                                                self._name_offset,
 
746
                                                len(self.params)))
 
747
        for p in self.params:
 
748
            p.write(typelib, file)
 
749
        self.result.write(typelib, file)
 
750
 
 
751
    def write_name(self, file, data_pool_offset):
 
752
        """
 
753
        Write this method's name to |file|.
 
754
        Assumes that |file| is currently seeked to an unused portion
 
755
        of the data pool.
 
756
 
 
757
        """
 
758
        if self.name:
 
759
            self._name_offset = file.tell() - data_pool_offset + 1
 
760
            file.write(self.name + "\x00")
 
761
        else:
 
762
            self._name_offset = 0
 
763
 
 
764
class Constant(object):
 
765
    """
 
766
    A constant value of a specific type defined on an interface.
 
767
    (ConstantDesciptor from the typelib specification.)
 
768
 
 
769
    """
 
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'}
 
778
 
 
779
    def __init__(self, name, type, value):
 
780
        self.name = name
 
781
        self._name_offset = 0
 
782
        self.type = type
 
783
        self.value = value
 
784
 
 
785
    @staticmethod
 
786
    def read(typelib, map, data_pool, offset):
 
787
        """
 
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.
 
792
        
 
793
        """
 
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)
 
800
        c = None
 
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)
 
807
        return c, offset
 
808
 
 
809
    def write(self, typelib, file):
 
810
        """
 
811
        Write a ConstDescriptor to |file|, which is assumed
 
812
        to be seeked to the proper position.
 
813
 
 
814
        """
 
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))
 
819
 
 
820
    def write_name(self, file, data_pool_offset):
 
821
        """
 
822
        Write this constants's name to |file|.
 
823
        Assumes that |file| is currently seeked to an unused portion
 
824
        of the data pool.
 
825
 
 
826
        """
 
827
        if self.name:
 
828
            self._name_offset = file.tell() - data_pool_offset + 1
 
829
            file.write(self.name + "\x00")
 
830
        else:
 
831
            self._name_offset = 0
 
832
 
 
833
    def __repr__(self):
 
834
        return "Constant(%s, %s, %d)" % (self.name, str(self.type), self.value)
 
835
 
 
836
class Interface(object):
 
837
    """
 
838
    An Interface represents an object, with its associated methods
 
839
    and constant values.
 
840
    (InterfaceDescriptor from the typelib specification.)
 
841
    
 
842
    """
 
843
    _direntry = struct.Struct(">16sIII")
 
844
    _descriptorstart = struct.Struct(">HH")
 
845
 
 
846
    UNRESOLVED_IID = "00000000-0000-0000-0000-000000000000"
 
847
 
 
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!
 
853
        self.iid = iid
 
854
        self.name = name
 
855
        self.namespace = namespace
 
856
        # if unresolved, all the members following this are unusable
 
857
        self.parent = parent
 
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
 
869
            self.resolved = True
 
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
 
875
 
 
876
    def __repr__(self):
 
877
        return "Interface('%s', '%s', '%s', methods=%s)" % (self.name, self.iid, self.namespace, self.methods)
 
878
 
 
879
    def __str__(self):
 
880
        return "Interface(name='%s', iid='%s')" % (self.name, self.iid)
 
881
 
 
882
    def __hash__(self):
 
883
        return hash((self.name, self.iid))
 
884
 
 
885
    def __cmp__(self, other):
 
886
        c = cmp(self.iid, other.iid)
 
887
        if c != 0:
 
888
            return c
 
889
        c = cmp(self.name, other.name)
 
890
        if c != 0:
 
891
            return c
 
892
        c = cmp(self.namespace, other.namespace)
 
893
        if c != 0:
 
894
            return c
 
895
        # names and IIDs are the same, check resolved
 
896
        if self.resolved != other.resolved:
 
897
            if self.resolved:
 
898
                return -1
 
899
            else:
 
900
                return 1
 
901
        else:
 
902
            # both unresolved, but names and IIDs are the same, so equal
 
903
            return 0
 
904
        #TODO: actually compare methods etc
 
905
        return 0
 
906
 
 
907
    def read_descriptor(self, typelib, map, data_pool):
 
908
        offset = self._descriptor_offset
 
909
        if offset == 0:
 
910
            return
 
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]
 
915
        # Read methods
 
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)
 
920
        # Read constants
 
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)
 
927
        # Read flags
 
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
 
932
        flags &= 0xE0
 
933
        if flags & 0x80:
 
934
            self.scriptable = True
 
935
        if flags & 0x40:
 
936
            self.function = True
 
937
        if flags & 0x20:
 
938
            self.builtinclass = True
 
939
        self.resolved = True
 
940
 
 
941
    def write_directory_entry(self, file):
 
942
        """
 
943
        Write an InterfaceDirectoryEntry for this interface
 
944
        to |file|, which is assumed to be seeked to the correct offset.
 
945
 
 
946
        """
 
947
        file.write(Interface._direntry.pack(Typelib.string_to_iid(self.iid),
 
948
                                            self._name_offset,
 
949
                                            self._namespace_offset,
 
950
                                            self._descriptor_offset))
 
951
 
 
952
    def write(self, typelib, file, data_pool_offset):
 
953
        """
 
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.
 
957
 
 
958
        """
 
959
        if not self.resolved:
 
960
            self._descriptor_offset = 0
 
961
            return
 
962
        self._descriptor_offset = file.tell() - data_pool_offset + 1
 
963
        parent_idx = 0
 
964
        if self.parent:
 
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)
 
972
        flags = 0
 
973
        if self.scriptable:
 
974
            flags |= 0x80
 
975
        if self.function:
 
976
            flags |= 0x40
 
977
        if self.builtinclass:
 
978
            flags |= 0x20
 
979
        file.write(struct.pack(">B", flags))
 
980
 
 
981
    def write_names(self, file, data_pool_offset):
 
982
        """
 
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
 
986
        of the data pool.
 
987
 
 
988
        """
 
989
        if self.name:
 
990
            self._name_offset = file.tell() - data_pool_offset + 1
 
991
            file.write(self.name + "\x00")
 
992
        else:
 
993
            self._name_offset = 0
 
994
        if self.namespace:
 
995
            self._namespace_offset = file.tell() - data_pool_offset + 1
 
996
            file.write(self.namespace + "\x00")
 
997
        else:
 
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)
 
1003
 
 
1004
class Typelib(object):
 
1005
    """
 
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.
 
1009
 
 
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.
 
1012
 
 
1013
    """
 
1014
    _header = struct.Struct(">16sBBHIII")
 
1015
 
 
1016
    def __init__(self, version=TYPELIB_VERSION, interfaces=[], annotations=[]):
 
1017
        """
 
1018
        Instantiate a new Typelib.
 
1019
 
 
1020
        """
 
1021
        self.version = version
 
1022
        self.interfaces = list(interfaces)
 
1023
        self.annotations = list(annotations)
 
1024
        self.filename = None
 
1025
 
 
1026
    @staticmethod
 
1027
    def iid_to_string(iid):
 
1028
        """
 
1029
        Convert a 16-byte IID into a UUID string.
 
1030
 
 
1031
        """
 
1032
        def hexify(s):
 
1033
            return ''.join(["%02x" % ord(x) for x in s])
 
1034
 
 
1035
        return "%s-%s-%s-%s-%s" % (hexify(iid[:4]), hexify(iid[4:6]),
 
1036
                                   hexify(iid[6:8]), hexify(iid[8:10]),
 
1037
                                   hexify(iid[10:]))
 
1038
 
 
1039
    @staticmethod
 
1040
    def string_to_iid(iid_str):
 
1041
        """
 
1042
        Convert a UUID string into a 16-byte IID.
 
1043
 
 
1044
        """
 
1045
        s = iid_str.replace('-','')
 
1046
        return ''.join([chr(int(s[i:i+2], 16)) for i in range(0, len(s), 2)])
 
1047
 
 
1048
    @staticmethod
 
1049
    def read_string(map, data_pool, offset):
 
1050
        if offset == 0:
 
1051
            return ""
 
1052
        sz = map.find('\x00', data_pool + offset - 1)
 
1053
        if sz == -1:
 
1054
            return ""
 
1055
        return map[data_pool + offset - 1:sz]
 
1056
 
 
1057
    @staticmethod
 
1058
    def read(input_file):
 
1059
        """
 
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.
 
1063
 
 
1064
        """
 
1065
        filename = ""
 
1066
        data = None
 
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
 
1074
        else:
 
1075
            data = input_file.read()
 
1076
 
 
1077
        (magic,
 
1078
         major_ver,
 
1079
         minor_ver,
 
1080
         num_interfaces,
 
1081
         file_length,
 
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
 
1101
        tag = anno & 0x7F
 
1102
        if tag == 0: # EmptyAnnotation
 
1103
            xpt.annotations.append(None)
 
1104
        # We don't bother handling PrivateAnnotations or anything
 
1105
 
 
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)
 
1120
        return xpt
 
1121
 
 
1122
    def __repr__(self):
 
1123
        return "<Typelib with %d interfaces>" % len(self.interfaces)
 
1124
 
 
1125
    def _sanityCheck(self):
 
1126
        """
 
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.
 
1130
 
 
1131
        """
 
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)
 
1136
            for m in i.methods:
 
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)
 
1143
 
 
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)
 
1148
        if headersize % 4:
 
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()
 
1163
        fd.seek(0)
 
1164
        fd.write(Typelib._header.pack(XPT_MAGIC,
 
1165
                                      TYPELIB_VERSION[0],
 
1166
                                      TYPELIB_VERSION[1],
 
1167
                                      len(self.interfaces),
 
1168
                                      file_len,
 
1169
                                      interface_directory_offset,
 
1170
                                      data_pool_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)
 
1179
 
 
1180
    def write(self, output_file):
 
1181
        """
 
1182
        Write the contents of this typelib to |output_file|,
 
1183
        which can be either a filename or a file-like object.
 
1184
 
 
1185
        """
 
1186
        self._sanityCheck()
 
1187
        if isinstance(output_file, basestring):
 
1188
            with open(output_file, "wb") as f:
 
1189
                self.writefd(f)
 
1190
        else:
 
1191
            self.writefd(output_file)
 
1192
 
 
1193
    def dump(self, out):
 
1194
        """
 
1195
        Print a human-readable listing of the contents of this typelib
 
1196
        to |out|, in the format of xpt_dump.
 
1197
        
 
1198
        """
 
1199
        out.write("""Header:
 
1200
   Major version:         %d
 
1201
   Minor version:         %d
 
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):
 
1205
            if a is None:
 
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))
 
1210
            if not i.resolved:
 
1211
                out.write("      [Unresolved]\n")
 
1212
            else:
 
1213
                if i.parent:
 
1214
                    out.write("      Parent: %s::%s\n" % (i.parent.namespace,
 
1215
                                                    i.parent.name))
 
1216
                out.write("""      Flags:
 
1217
         Scriptable: %s
 
1218
         BuiltinClass: %s
 
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")
 
1225
                else:
 
1226
                    for m in i.methods:
 
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 " ",
 
1235
                            str(m.result.type),
 
1236
                            m.name,
 
1237
                            m.params and ", ".join(str(p) for p in m.params) or ""
 
1238
                            ))
 
1239
                out.write("      Constants:\n")
 
1240
                if len(i.constants) == 0:
 
1241
                    out.write("         No Constants\n")
 
1242
                else:
 
1243
                    for c in i.constants:
 
1244
                        out.write("         %s %s = %d;\n" % (c.type, c.name, c.value))
 
1245
 
 
1246
def xpt_dump(file):
 
1247
    """
 
1248
    Dump the contents of |file| to stdout in the format of xpt_dump.
 
1249
 
 
1250
    """
 
1251
    t = Typelib.read(file)
 
1252
    t.dump(sys.stdout)
 
1253
 
 
1254
def xpt_link(inputs):
 
1255
    """
 
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
 
1258
    file-like objects.
 
1259
 
 
1260
    """
 
1261
    def read_input(i):
 
1262
        if isinstance(i, Typelib):
 
1263
            return i
 
1264
        return Typelib.read(i)
 
1265
 
 
1266
    if not inputs:
 
1267
        print >>sys.stderr, "Usage: xpt_link <destination file> <input files>"
 
1268
        return None
 
1269
    # This is the aggregate list of interfaces.
 
1270
    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 = {}
 
1275
    for f in inputs:
 
1276
        t = read_input(f)
 
1277
        interfaces.extend(t.interfaces)
 
1278
    # Sort interfaces by name so we can merge adjacent duplicates
 
1279
    interfaces.sort(key=operator.attrgetter('name'))
 
1280
 
 
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
 
1285
        
 
1286
    def compare(i, j):
 
1287
        """
 
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).
 
1291
 
 
1292
        """
 
1293
        if i == j:
 
1294
            # Arbitrary, just pick one
 
1295
            return Result.Equal
 
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.
 
1299
                raise DataError, \
 
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
 
1309
            if j.resolved:
 
1310
                assert i.iid == j.iid or i.iid == Interface.UNRESOLVED_IID
 
1311
                # keep j
 
1312
                return Result.KeepSecond
 
1313
            else:
 
1314
                assert i.iid == j.iid or j.iid == Interface.UNRESOLVED_IID
 
1315
                # replace j with i
 
1316
                return Result.KeepFirst
 
1317
        elif i.iid != j.iid:
 
1318
            # Prefer unresolved interfaces with valid IIDs
 
1319
            if j.iid == Interface.UNRESOLVED_IID:
 
1320
                # replace j with i
 
1321
                assert not j.resolved
 
1322
                return Result.KeepFirst
 
1323
            elif i.iid == Interface.UNRESOLVED_IID:
 
1324
                # keep j
 
1325
                assert not i.resolved
 
1326
                return Result.KeepSecond
 
1327
            else:
 
1328
                # Same name but different IIDs: raise an exception.
 
1329
                raise DataError, \
 
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)
 
1336
    
 
1337
    # Compare interfaces pairwise to find duplicates that should be merged.
 
1338
    i = 1
 
1339
    while i < len(interfaces):
 
1340
        res = compare(interfaces[i-1], interfaces[i])
 
1341
        if res == Result.NotEqual:
 
1342
            i += 1
 
1343
        elif res == Result.Equal:
 
1344
            # Need to drop one but it doesn't matter which
 
1345
            del interfaces[i]
 
1346
        elif res == Result.KeepFirst:
 
1347
            merged_interfaces[interfaces[i]] = interfaces[i-1]
 
1348
            del interfaces[i]
 
1349
        elif res == Result.KeepSecond:
 
1350
            merged_interfaces[interfaces[i-1]] = interfaces[i]
 
1351
            del interfaces[i-1]
 
1352
    
 
1353
    # Now fixup any merged interfaces
 
1354
    def checkType(t):
 
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]
 
1361
 
 
1362
    for i in interfaces:
 
1363
        # Replace parent references
 
1364
        if i.parent in merged_interfaces:
 
1365
            i.parent = merged_interfaces[i.parent]
 
1366
        for m in i.methods:
 
1367
            # Replace InterfaceType params and return values
 
1368
            checkType(m.result.type)
 
1369
            for p in m.params:
 
1370
                checkType(p.type)
 
1371
    # Re-sort interfaces (by IID)
 
1372
    interfaces.sort()
 
1373
    return Typelib(interfaces=interfaces)
 
1374
 
 
1375
if __name__ == '__main__':
 
1376
    if len(sys.argv) < 3:
 
1377
        print >>sys.stderr, "xpt <dump|link> <files>"
 
1378
        sys.exit(1)
 
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])
 
1383