diff options
Diffstat (limited to 'xpcom/typelib/xpt/tools/xpt.py')
-rwxr-xr-x | xpcom/typelib/xpt/tools/xpt.py | 1540 |
1 files changed, 1540 insertions, 0 deletions
diff --git a/xpcom/typelib/xpt/tools/xpt.py b/xpcom/typelib/xpt/tools/xpt.py new file mode 100755 index 000000000..67d40f7b2 --- /dev/null +++ b/xpcom/typelib/xpt/tools/xpt.py @@ -0,0 +1,1540 @@ +#!/usr/bin/env python +# Copyright 2010,2011 Mozilla Foundation. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE MOZILLA FOUNDATION ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE MOZILLA FOUNDATION OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# The views and conclusions contained in the software and documentation +# are those of the authors and should not be interpreted as representing +# official policies, either expressed or implied, of the Mozilla +# Foundation. + +""" +A module for working with XPCOM Type Libraries. + +The XPCOM Type Library File Format is described at: +http://www.mozilla.org/scriptable/typelib_file.html . It is used +to provide type information for calling methods on XPCOM objects +from scripting languages such as JavaScript. + +This module provides a set of classes representing the parts of +a typelib in a high-level manner, as well as methods for reading +and writing them from files. + +The usable public interfaces are currently: +Typelib.read(input_file) - read a typelib from a file on disk or file-like + object, return a Typelib object. + +xpt_dump(filename) - read a typelib from a file on disk, dump + the contents to stdout in a human-readable + format. + +Typelib() - construct a new Typelib object +Interface() - construct a new Interface object +Method() - construct a new object representing a method + defined on an Interface +Constant() - construct a new object representing a constant + defined on an Interface +Param() - construct a new object representing a parameter + to a method +SimpleType() - construct a new object representing a simple + data type +InterfaceType() - construct a new object representing a type that + is an IDL-defined interface + +""" + +from __future__ import with_statement +import os +import sys +import struct +import operator + +# header magic +XPT_MAGIC = "XPCOM\nTypeLib\r\n\x1a" +TYPELIB_VERSION = (1, 2) + + +class FileFormatError(Exception): + pass + + +class DataError(Exception): + pass + + +# Magic for creating enums +def M_add_class_attribs(attribs): + def foo(name, bases, dict_): + for v, k in attribs: + dict_[k] = v + return type(name, bases, dict_) + return foo + + +def enum(*names): + class Foo(object): + __metaclass__ = M_add_class_attribs(enumerate(names)) + + def __setattr__(self, name, value): # this makes it read-only + raise NotImplementedError + return Foo() + + +# Descriptor types as described in the spec +class Type(object): + """ + Data type of a method parameter or return value. Do not instantiate + this class directly. Rather, use one of its subclasses. + + """ + _prefixdescriptor = struct.Struct(">B") + Tags = enum( + # The first 18 entries are SimpleTypeDescriptor + 'int8', + 'int16', + 'int32', + 'int64', + 'uint8', + 'uint16', + 'uint32', + 'uint64', + 'float', + 'double', + 'boolean', + 'char', + 'wchar_t', + 'void', + # the following four values are only valid as pointers + 'nsIID', + 'DOMString', + 'char_ptr', + 'wchar_t_ptr', + # InterfaceTypeDescriptor + 'Interface', + # InterfaceIsTypeDescriptor + 'InterfaceIs', + # ArrayTypeDescriptor + 'Array', + # StringWithSizeTypeDescriptor + 'StringWithSize', + # WideStringWithSizeTypeDescriptor + 'WideStringWithSize', + # XXX: These are also SimpleTypes (but not in the spec) + # http://hg.mozilla.org/mozilla-central/annotate/0e0e2516f04e/xpcom/typelib/xpt/tools/xpt_dump.c#l69 + 'UTF8String', + 'CString', + 'AString', + 'jsval', + ) + + def __init__(self, pointer=False, reference=False): + self.pointer = pointer + self.reference = reference + if reference and not pointer: + raise Exception("If reference is True pointer must be True too") + + def __cmp__(self, other): + return ( + # First make sure we have two Types of the same type (no pun intended!) + cmp(type(self), type(other)) or + cmp(self.pointer, other.pointer) or + cmp(self.reference, other.reference) + ) + + @staticmethod + def decodeflags(byte): + """ + Given |byte|, an unsigned uint8 containing flag bits, + decode the flag bits as described in + http://www.mozilla.org/scriptable/typelib_file.html#TypeDescriptor + and return a dict of flagname: (True|False) suitable + for passing to Type.__init__ as **kwargs. + + """ + return {'pointer': bool(byte & 0x80), + 'reference': bool(byte & 0x20), + } + + def encodeflags(self): + """ + Encode the flag bits of this Type object. Returns a byte. + + """ + flags = 0 + if self.pointer: + flags |= 0x80 + if self.reference: + flags |= 0x20 + return flags + + @staticmethod + def read(typelib, map, data_pool, offset): + """ + Read a TypeDescriptor at |offset| from the mmaped file |map| with + data pool offset |data_pool|. Returns (Type, next offset), + where |next offset| is an offset suitable for reading the data + following this TypeDescriptor. + + """ + start = data_pool + offset - 1 + (data,) = Type._prefixdescriptor.unpack_from(map, start) + # first three bits are the flags + flags = data & 0xE0 + flags = Type.decodeflags(flags) + # last five bits is the tag + tag = data & 0x1F + offset += Type._prefixdescriptor.size + t = None + if tag <= Type.Tags.wchar_t_ptr or tag >= Type.Tags.UTF8String: + t = SimpleType.get(data, tag, flags) + elif tag == Type.Tags.Interface: + t, offset = InterfaceType.read(typelib, map, data_pool, offset, flags) + elif tag == Type.Tags.InterfaceIs: + t, offset = InterfaceIsType.read(typelib, map, data_pool, offset, flags) + elif tag == Type.Tags.Array: + t, offset = ArrayType.read(typelib, map, data_pool, offset, flags) + elif tag == Type.Tags.StringWithSize: + t, offset = StringWithSizeType.read(typelib, map, data_pool, offset, flags) + elif tag == Type.Tags.WideStringWithSize: + t, offset = WideStringWithSizeType.read(typelib, map, data_pool, offset, flags) + return t, offset + + def write(self, typelib, file): + """ + Write a TypeDescriptor to |file|, which is assumed + to be seeked to the proper position. For types other than + SimpleType, this is not sufficient for writing the TypeDescriptor, + and the subclass method must be called. + + """ + file.write(Type._prefixdescriptor.pack(self.encodeflags() | self.tag)) + + +class SimpleType(Type): + """ + A simple data type. (SimpleTypeDescriptor from the typelib specification.) + + """ + _cache = {} + + def __init__(self, tag, **kwargs): + Type.__init__(self, **kwargs) + self.tag = tag + + def __cmp__(self, other): + return ( + Type.__cmp__(self, other) or + cmp(self.tag, other.tag) + ) + + @staticmethod + def get(data, tag, flags): + """ + Get a SimpleType object representing |data| (a TypeDescriptorPrefix). + May return an already-created object. If no cached object is found, + construct one with |tag| and |flags|. + + """ + if data not in SimpleType._cache: + SimpleType._cache[data] = SimpleType(tag, **flags) + return SimpleType._cache[data] + + def __str__(self): + s = "unknown" + if self.tag == Type.Tags.char_ptr and self.pointer: + return "string" + if self.tag == Type.Tags.wchar_t_ptr and self.pointer: + return "wstring" + for t in dir(Type.Tags): + if self.tag == getattr(Type.Tags, t): + s = t + break + + if self.pointer: + if self.reference: + s += " &" + else: + s += " *" + return s + + +class InterfaceType(Type): + """ + A type representing a pointer to an IDL-defined interface. + (InterfaceTypeDescriptor from the typelib specification.) + + """ + _descriptor = struct.Struct(">H") + + def __init__(self, iface, pointer=True, **kwargs): + if not pointer: + raise DataError("InterfaceType is not valid with pointer=False") + Type.__init__(self, pointer=pointer, **kwargs) + self.iface = iface + self.tag = Type.Tags.Interface + + def __cmp__(self, other): + return ( + Type.__cmp__(self, other) or + # When comparing interface types, only look at the name. + cmp(self.iface.name, other.iface.name) or + cmp(self.tag, other.tag) + ) + + @staticmethod + def read(typelib, map, data_pool, offset, flags): + """ + Read an InterfaceTypeDescriptor at |offset| from the mmaped + file |map| with data pool offset |data_pool|. + Returns (InterfaceType, next offset), + where |next offset| is an offset suitable for reading the data + following this InterfaceTypeDescriptor. + + """ + if not flags['pointer']: + return None, offset + start = data_pool + offset - 1 + (iface_index,) = InterfaceType._descriptor.unpack_from(map, start) + offset += InterfaceType._descriptor.size + iface = None + # interface indices are 1-based + if iface_index > 0 and iface_index <= len(typelib.interfaces): + iface = typelib.interfaces[iface_index - 1] + return InterfaceType(iface, **flags), offset + + def write(self, typelib, file): + """ + Write an InterfaceTypeDescriptor to |file|, which is assumed + to be seeked to the proper position. + + """ + Type.write(self, typelib, file) + # write out the interface index (1-based) + file.write(InterfaceType._descriptor.pack(typelib.interfaces.index(self.iface) + 1)) + + def __str__(self): + if self.iface: + return self.iface.name + return "unknown interface" + + +class InterfaceIsType(Type): + """ + A type representing an interface described by one of the other + arguments to the method. (InterfaceIsTypeDescriptor from the + typelib specification.) + + """ + _descriptor = struct.Struct(">B") + _cache = {} + + def __init__(self, param_index, pointer=True, **kwargs): + if not pointer: + raise DataError("InterfaceIsType is not valid with pointer=False") + Type.__init__(self, pointer=pointer, **kwargs) + self.param_index = param_index + self.tag = Type.Tags.InterfaceIs + + def __cmp__(self, other): + return ( + Type.__cmp__(self, other) or + cmp(self.param_index, other.param_index) or + cmp(self.tag, other.tag) + ) + + @staticmethod + def read(typelib, map, data_pool, offset, flags): + """ + Read an InterfaceIsTypeDescriptor at |offset| from the mmaped + file |map| with data pool offset |data_pool|. + Returns (InterfaceIsType, next offset), + where |next offset| is an offset suitable for reading the data + following this InterfaceIsTypeDescriptor. + May return a cached value. + + """ + if not flags['pointer']: + return None, offset + start = data_pool + offset - 1 + (param_index,) = InterfaceIsType._descriptor.unpack_from(map, start) + offset += InterfaceIsType._descriptor.size + if param_index not in InterfaceIsType._cache: + InterfaceIsType._cache[param_index] = InterfaceIsType(param_index, **flags) + return InterfaceIsType._cache[param_index], offset + + def write(self, typelib, file): + """ + Write an InterfaceIsTypeDescriptor to |file|, which is assumed + to be seeked to the proper position. + + """ + Type.write(self, typelib, file) + file.write(InterfaceIsType._descriptor.pack(self.param_index)) + + def __str__(self): + return "InterfaceIs *" + + +class ArrayType(Type): + """ + A type representing an Array of elements of another type, whose + size and length are passed as separate parameters to a method. + (ArrayTypeDescriptor from the typelib specification.) + + """ + _descriptor = struct.Struct(">BB") + + def __init__(self, element_type, size_is_arg_num, length_is_arg_num, + pointer=True, **kwargs): + if not pointer: + raise DataError("ArrayType is not valid with pointer=False") + Type.__init__(self, pointer=pointer, **kwargs) + self.element_type = element_type + self.size_is_arg_num = size_is_arg_num + self.length_is_arg_num = length_is_arg_num + self.tag = Type.Tags.Array + + def __cmp__(self, other): + return ( + Type.__cmp__(self, other) or + cmp(self.element_type, other.element_type) or + cmp(self.size_is_arg_num, other.size_is_arg_num) or + cmp(self.length_is_arg_num, other.length_is_arg_num) or + cmp(self.tag, other.tag) + ) + + @staticmethod + def read(typelib, map, data_pool, offset, flags): + """ + Read an ArrayTypeDescriptor at |offset| from the mmaped + file |map| with data pool offset |data_pool|. + Returns (ArrayType, next offset), + where |next offset| is an offset suitable for reading the data + following this ArrayTypeDescriptor. + """ + if not flags['pointer']: + return None, offset + start = data_pool + offset - 1 + (size_is_arg_num, length_is_arg_num) = ArrayType._descriptor.unpack_from(map, start) + offset += ArrayType._descriptor.size + t, offset = Type.read(typelib, map, data_pool, offset) + return ArrayType(t, size_is_arg_num, length_is_arg_num, **flags), offset + + def write(self, typelib, file): + """ + Write an ArrayTypeDescriptor to |file|, which is assumed + to be seeked to the proper position. + + """ + Type.write(self, typelib, file) + file.write(ArrayType._descriptor.pack(self.size_is_arg_num, + self.length_is_arg_num)) + self.element_type.write(typelib, file) + + def __str__(self): + return "%s []" % str(self.element_type) + + +class StringWithSizeType(Type): + """ + A type representing a UTF-8 encoded string whose size and length + are passed as separate arguments to a method. (StringWithSizeTypeDescriptor + from the typelib specification.) + + """ + _descriptor = struct.Struct(">BB") + + def __init__(self, size_is_arg_num, length_is_arg_num, + pointer=True, **kwargs): + if not pointer: + raise DataError("StringWithSizeType is not valid with pointer=False") + Type.__init__(self, pointer=pointer, **kwargs) + self.size_is_arg_num = size_is_arg_num + self.length_is_arg_num = length_is_arg_num + self.tag = Type.Tags.StringWithSize + + def __cmp__(self, other): + return ( + Type.__cmp__(self, other) or + cmp(self.size_is_arg_num, other.size_is_arg_num) or + cmp(self.length_is_arg_num, other.length_is_arg_num) or + cmp(self.tag, other.tag) + ) + + @staticmethod + def read(typelib, map, data_pool, offset, flags): + """ + Read an StringWithSizeTypeDescriptor at |offset| from the mmaped + file |map| with data pool offset |data_pool|. + Returns (StringWithSizeType, next offset), + where |next offset| is an offset suitable for reading the data + following this StringWithSizeTypeDescriptor. + """ + if not flags['pointer']: + return None, offset + start = data_pool + offset - 1 + (size_is_arg_num, length_is_arg_num) = StringWithSizeType._descriptor.unpack_from(map, start) + offset += StringWithSizeType._descriptor.size + return StringWithSizeType(size_is_arg_num, length_is_arg_num, **flags), offset + + def write(self, typelib, file): + """ + Write a StringWithSizeTypeDescriptor to |file|, which is assumed + to be seeked to the proper position. + + """ + Type.write(self, typelib, file) + file.write(StringWithSizeType._descriptor.pack(self.size_is_arg_num, + self.length_is_arg_num)) + + def __str__(self): + return "string_s" + + +class WideStringWithSizeType(Type): + """ + A type representing a UTF-16 encoded string whose size and length + are passed as separate arguments to a method. + (WideStringWithSizeTypeDescriptor from the typelib specification.) + + """ + _descriptor = struct.Struct(">BB") + + def __init__(self, size_is_arg_num, length_is_arg_num, + pointer=True, **kwargs): + if not pointer: + raise DataError("WideStringWithSizeType is not valid with pointer=False") + Type.__init__(self, pointer=pointer, **kwargs) + self.size_is_arg_num = size_is_arg_num + self.length_is_arg_num = length_is_arg_num + self.tag = Type.Tags.WideStringWithSize + + def __cmp__(self, other): + return ( + Type.__cmp__(self, other) or + cmp(self.size_is_arg_num, other.size_is_arg_num) or + cmp(self.length_is_arg_num, other.length_is_arg_num) or + cmp(self.tag, other.tag) + ) + + @staticmethod + def read(typelib, map, data_pool, offset, flags): + """ + Read an WideStringWithSizeTypeDescriptor at |offset| from the mmaped + file |map| with data pool offset |data_pool|. + Returns (WideStringWithSizeType, next offset), + where |next offset| is an offset suitable for reading the data + following this WideStringWithSizeTypeDescriptor. + """ + if not flags['pointer']: + return None, offset + start = data_pool + offset - 1 + (size_is_arg_num, length_is_arg_num) = WideStringWithSizeType._descriptor.unpack_from(map, start) + offset += WideStringWithSizeType._descriptor.size + return WideStringWithSizeType(size_is_arg_num, length_is_arg_num, **flags), offset + + def write(self, typelib, file): + """ + Write a WideStringWithSizeTypeDescriptor to |file|, which is assumed + to be seeked to the proper position. + + """ + Type.write(self, typelib, file) + file.write(WideStringWithSizeType._descriptor.pack(self.size_is_arg_num, + self.length_is_arg_num)) + + def __str__(self): + return "wstring_s" + + +class Param(object): + """ + A parameter to a method, or the return value of a method. + (ParamDescriptor from the typelib specification.) + + """ + _descriptorstart = struct.Struct(">B") + + def __init__(self, type, in_=True, out=False, retval=False, + shared=False, dipper=False, optional=False): + """ + Construct a Param object with the specified |type| and + flags. Params default to "in". + + """ + + self.type = type + self.in_ = in_ + self.out = out + self.retval = retval + self.shared = shared + self.dipper = dipper + self.optional = optional + + def __cmp__(self, other): + return ( + cmp(self.type, other.type) or + cmp(self.in_, other.in_) or + cmp(self.out, other.out) or + cmp(self.retval, other.retval) or + cmp(self.shared, other.shared) or + cmp(self.dipper, other.dipper) or + cmp(self.optional, other.optional) + ) + + @staticmethod + def decodeflags(byte): + """ + Given |byte|, an unsigned uint8 containing flag bits, + decode the flag bits as described in + http://www.mozilla.org/scriptable/typelib_file.html#ParamDescriptor + and return a dict of flagname: (True|False) suitable + for passing to Param.__init__ as **kwargs + """ + return {'in_': bool(byte & 0x80), + 'out': bool(byte & 0x40), + 'retval': bool(byte & 0x20), + 'shared': bool(byte & 0x10), + 'dipper': bool(byte & 0x08), + # XXX: Not in the spec, see: + # http://hg.mozilla.org/mozilla-central/annotate/0e0e2516f04e/xpcom/typelib/xpt/public/xpt_struct.h#l456 + 'optional': bool(byte & 0x04), + } + + def encodeflags(self): + """ + Encode the flags of this Param. Return a byte suitable for + writing to a typelib file. + + """ + flags = 0 + if self.in_: + flags |= 0x80 + if self.out: + flags |= 0x40 + if self.retval: + flags |= 0x20 + if self.shared: + flags |= 0x10 + if self.dipper: + flags |= 0x08 + if self.optional: + flags |= 0x04 + return flags + + @staticmethod + def read(typelib, map, data_pool, offset): + """ + Read a ParamDescriptor at |offset| from the mmaped file |map| with + data pool offset |data_pool|. Returns (Param, next offset), + where |next offset| is an offset suitable for reading the data + following this ParamDescriptor. + """ + start = data_pool + offset - 1 + (flags,) = Param._descriptorstart.unpack_from(map, start) + # only the first five bits are flags + flags &= 0xFC + flags = Param.decodeflags(flags) + offset += Param._descriptorstart.size + t, offset = Type.read(typelib, map, data_pool, offset) + p = Param(t, **flags) + return p, offset + + def write(self, typelib, file): + """ + Write a ParamDescriptor to |file|, which is assumed to be seeked + to the correct position. + + """ + file.write(Param._descriptorstart.pack(self.encodeflags())) + self.type.write(typelib, file) + + def prefix(self): + """ + Return a human-readable string representing the flags set + on this Param. + + """ + s = "" + if self.out: + if self.in_: + s = "inout " + else: + s = "out " + else: + s = "in " + if self.dipper: + s += "dipper " + if self.retval: + s += "retval " + if self.shared: + s += "shared " + if self.optional: + s += "optional " + return s + + def __str__(self): + return self.prefix() + str(self.type) + + +class Method(object): + """ + A method of an interface, defining its associated parameters + and return value. + (MethodDescriptor from the typelib specification.) + + """ + _descriptorstart = struct.Struct(">BIB") + + def __init__(self, name, result, + params=[], getter=False, setter=False, notxpcom=False, + constructor=False, hidden=False, optargc=False, + implicit_jscontext=False): + self.name = name + self._name_offset = 0 + self.getter = getter + self.setter = setter + self.notxpcom = notxpcom + self.constructor = constructor + self.hidden = hidden + self.optargc = optargc + self.implicit_jscontext = implicit_jscontext + self.params = list(params) + if result and not isinstance(result, Param): + raise Exception("result must be a Param!") + self.result = result + + def __cmp__(self, other): + return ( + cmp(self.name, other.name) or + cmp(self.getter, other.getter) or + cmp(self.setter, other.setter) or + cmp(self.notxpcom, other.notxpcom) or + cmp(self.constructor, other.constructor) or + cmp(self.hidden, other.hidden) or + cmp(self.optargc, other.optargc) or + cmp(self.implicit_jscontext, other.implicit_jscontext) or + cmp(self.params, other.params) or + cmp(self.result, other.result) + ) + + def read_params(self, typelib, map, data_pool, offset, num_args): + """ + Read |num_args| ParamDescriptors representing this Method's arguments + from the mmaped file |map| with data pool at the offset |data_pool|, + starting at |offset| into self.params. Returns the offset + suitable for reading the data following the ParamDescriptor array. + + """ + for i in range(num_args): + p, offset = Param.read(typelib, map, data_pool, offset) + self.params.append(p) + return offset + + def read_result(self, typelib, map, data_pool, offset): + """ + Read a ParamDescriptor representing this Method's return type + from the mmaped file |map| with data pool at the offset |data_pool|, + starting at |offset| into self.result. Returns the offset + suitable for reading the data following the ParamDescriptor. + + """ + self.result, offset = Param.read(typelib, map, data_pool, offset) + return offset + + @staticmethod + def decodeflags(byte): + """ + Given |byte|, an unsigned uint8 containing flag bits, + decode the flag bits as described in + http://www.mozilla.org/scriptable/typelib_file.html#MethodDescriptor + and return a dict of flagname: (True|False) suitable + for passing to Method.__init__ as **kwargs + + """ + return {'getter': bool(byte & 0x80), + 'setter': bool(byte & 0x40), + 'notxpcom': bool(byte & 0x20), + 'constructor': bool(byte & 0x10), + 'hidden': bool(byte & 0x08), + # Not in the spec, see + # http://hg.mozilla.org/mozilla-central/annotate/0e0e2516f04e/xpcom/typelib/xpt/public/xpt_struct.h#l489 + 'optargc': bool(byte & 0x04), + 'implicit_jscontext': bool(byte & 0x02), + } + + def encodeflags(self): + """ + Encode the flags of this Method object, return a byte suitable + for writing to a typelib file. + + """ + flags = 0 + if self.getter: + flags |= 0x80 + if self.setter: + flags |= 0x40 + if self.notxpcom: + flags |= 0x20 + if self.constructor: + flags |= 0x10 + if self.hidden: + flags |= 0x08 + if self.optargc: + flags |= 0x04 + if self.implicit_jscontext: + flags |= 0x02 + return flags + + @staticmethod + def read(typelib, map, data_pool, offset): + """ + Read a MethodDescriptor at |offset| from the mmaped file |map| with + data pool offset |data_pool|. Returns (Method, next offset), + where |next offset| is an offset suitable for reading the data + following this MethodDescriptor. + + """ + start = data_pool + offset - 1 + flags, name_offset, num_args = Method._descriptorstart.unpack_from(map, start) + # only the first seven bits are flags + flags &= 0xFE + flags = Method.decodeflags(flags) + name = Typelib.read_string(map, data_pool, name_offset) + m = Method(name, None, **flags) + offset += Method._descriptorstart.size + offset = m.read_params(typelib, map, data_pool, offset, num_args) + offset = m.read_result(typelib, map, data_pool, offset) + return m, offset + + def write(self, typelib, file): + """ + Write a MethodDescriptor to |file|, which is assumed to be + seeked to the right position. + + """ + file.write(Method._descriptorstart.pack(self.encodeflags(), + self._name_offset, + len(self.params))) + for p in self.params: + p.write(typelib, file) + self.result.write(typelib, file) + + def write_name(self, file, data_pool_offset): + """ + Write this method's name to |file|. + Assumes that |file| is currently seeked to an unused portion + of the data pool. + + """ + if self.name: + self._name_offset = file.tell() - data_pool_offset + 1 + file.write(self.name + "\x00") + else: + self._name_offset = 0 + + +class Constant(object): + """ + A constant value of a specific type defined on an interface. + (ConstantDesciptor from the typelib specification.) + + """ + _descriptorstart = struct.Struct(">I") + # Actual value is restricted to this set of types + # XXX: the spec lies, the source allows a bunch more + # http://hg.mozilla.org/mozilla-central/annotate/9c85f9aaec8c/xpcom/typelib/xpt/src/xpt_struct.c#l689 + typemap = {Type.Tags.int16: '>h', + Type.Tags.uint16: '>H', + Type.Tags.int32: '>i', + Type.Tags.uint32: '>I'} + + def __init__(self, name, type, value): + self.name = name + self._name_offset = 0 + self.type = type + self.value = value + + def __cmp__(self, other): + return ( + cmp(self.name, other.name) or + cmp(self.type, other.type) or + cmp(self.value, other.value) + ) + + @staticmethod + def read(typelib, map, data_pool, offset): + """ + Read a ConstDescriptor at |offset| from the mmaped file |map| with + data pool offset |data_pool|. Returns (Constant, next offset), + where |next offset| is an offset suitable for reading the data + following this ConstDescriptor. + + """ + start = data_pool + offset - 1 + (name_offset,) = Constant._descriptorstart.unpack_from(map, start) + name = Typelib.read_string(map, data_pool, name_offset) + offset += Constant._descriptorstart.size + # Read TypeDescriptor + t, offset = Type.read(typelib, map, data_pool, offset) + c = None + if isinstance(t, SimpleType) and t.tag in Constant.typemap: + tt = Constant.typemap[t.tag] + start = data_pool + offset - 1 + (val,) = struct.unpack_from(tt, map, start) + offset += struct.calcsize(tt) + c = Constant(name, t, val) + return c, offset + + def write(self, typelib, file): + """ + Write a ConstDescriptor to |file|, which is assumed + to be seeked to the proper position. + + """ + file.write(Constant._descriptorstart.pack(self._name_offset)) + self.type.write(typelib, file) + tt = Constant.typemap[self.type.tag] + file.write(struct.pack(tt, self.value)) + + def write_name(self, file, data_pool_offset): + """ + Write this constants's name to |file|. + Assumes that |file| is currently seeked to an unused portion + of the data pool. + + """ + if self.name: + self._name_offset = file.tell() - data_pool_offset + 1 + file.write(self.name + "\x00") + else: + self._name_offset = 0 + + def __repr__(self): + return "Constant(%s, %s, %d)" % (self.name, str(self.type), self.value) + + +class Interface(object): + """ + An Interface represents an object, with its associated methods + and constant values. + (InterfaceDescriptor from the typelib specification.) + + """ + _direntry = struct.Struct(">16sIII") + _descriptorstart = struct.Struct(">HH") + + UNRESOLVED_IID = "00000000-0000-0000-0000-000000000000" + + def __init__(self, name, iid=UNRESOLVED_IID, namespace="", + resolved=False, parent=None, methods=[], constants=[], + scriptable=False, function=False, builtinclass=False, + main_process_scriptable_only=False): + self.resolved = resolved + # TODO: should validate IIDs! + self.iid = iid + self.name = name + self.namespace = namespace + # if unresolved, all the members following this are unusable + self.parent = parent + self.methods = list(methods) + self.constants = list(constants) + self.scriptable = scriptable + self.function = function + self.builtinclass = builtinclass + self.main_process_scriptable_only = main_process_scriptable_only + # For sanity, if someone constructs an Interface and passes + # in methods or constants, then it's resolved. + if self.methods or self.constants: + # make sure it has a valid IID + if self.iid == Interface.UNRESOLVED_IID: + raise DataError("Cannot instantiate Interface %s containing methods or constants with an unresolved IID" % self.name) + self.resolved = True + # These are only used for writing out the interface + self._descriptor_offset = 0 + self._name_offset = 0 + self._namespace_offset = 0 + self.xpt_filename = None + + def __repr__(self): + return "Interface('%s', '%s', '%s', methods=%s)" % (self.name, self.iid, self.namespace, self.methods) + + def __str__(self): + return "Interface(name='%s', iid='%s')" % (self.name, self.iid) + + def __hash__(self): + return hash((self.name, self.iid)) + + def __cmp__(self, other): + c = cmp(self.iid, other.iid) + if c != 0: + return c + c = cmp(self.name, other.name) + if c != 0: + return c + c = cmp(self.namespace, other.namespace) + if c != 0: + return c + # names and IIDs are the same, check resolved + if self.resolved != other.resolved: + if self.resolved: + return -1 + else: + return 1 + else: + if not self.resolved: + # both unresolved, but names and IIDs are the same, so equal + return 0 + # When comparing parents, only look at the name. + if (self.parent is None) != (other.parent is None): + if self.parent is None: + return -1 + else: + return 1 + elif self.parent is not None: + c = cmp(self.parent.name, other.parent.name) + if c != 0: + return c + return ( + cmp(self.methods, other.methods) or + cmp(self.constants, other.constants) or + cmp(self.scriptable, other.scriptable) or + cmp(self.function, other.function) or + cmp(self.builtinclass, other.builtinclass) or + cmp(self.main_process_scriptable_only, other.main_process_scriptable_only) + ) + + def read_descriptor(self, typelib, map, data_pool): + offset = self._descriptor_offset + if offset == 0: + return + start = data_pool + offset - 1 + parent, num_methods = Interface._descriptorstart.unpack_from(map, start) + if parent > 0 and parent <= len(typelib.interfaces): + self.parent = typelib.interfaces[parent - 1] + # Read methods + offset += Interface._descriptorstart.size + for i in range(num_methods): + m, offset = Method.read(typelib, map, data_pool, offset) + self.methods.append(m) + # Read constants + start = data_pool + offset - 1 + (num_constants, ) = struct.unpack_from(">H", map, start) + offset = offset + struct.calcsize(">H") + for i in range(num_constants): + c, offset = Constant.read(typelib, map, data_pool, offset) + self.constants.append(c) + # Read flags + start = data_pool + offset - 1 + (flags, ) = struct.unpack_from(">B", map, start) + offset = offset + struct.calcsize(">B") + # only the first two bits are flags + flags &= 0xf0 + if flags & 0x80: + self.scriptable = True + if flags & 0x40: + self.function = True + if flags & 0x20: + self.builtinclass = True + if flags & 0x10: + self.main_process_scriptable_only = True + self.resolved = True + + def write_directory_entry(self, file): + """ + Write an InterfaceDirectoryEntry for this interface + to |file|, which is assumed to be seeked to the correct offset. + + """ + file.write(Interface._direntry.pack(Typelib.string_to_iid(self.iid), + self._name_offset, + self._namespace_offset, + self._descriptor_offset)) + + def write(self, typelib, file, data_pool_offset): + """ + Write an InterfaceDescriptor to |file|, which is assumed + to be seeked to the proper position. If this interface + is not resolved, do not write any data. + + """ + if not self.resolved: + self._descriptor_offset = 0 + return + self._descriptor_offset = file.tell() - data_pool_offset + 1 + parent_idx = 0 + if self.parent: + parent_idx = typelib.interfaces.index(self.parent) + 1 + file.write(Interface._descriptorstart.pack(parent_idx, len(self.methods))) + for m in self.methods: + m.write(typelib, file) + file.write(struct.pack(">H", len(self.constants))) + for c in self.constants: + c.write(typelib, file) + flags = 0 + if self.scriptable: + flags |= 0x80 + if self.function: + flags |= 0x40 + if self.builtinclass: + flags |= 0x20 + if self.main_process_scriptable_only: + flags |= 0x10 + file.write(struct.pack(">B", flags)) + + def write_names(self, file, data_pool_offset): + """ + Write this interface's name and namespace to |file|, + as well as the names of all of its methods and constants. + Assumes that |file| is currently seeked to an unused portion + of the data pool. + + """ + if self.name: + self._name_offset = file.tell() - data_pool_offset + 1 + file.write(self.name + "\x00") + else: + self._name_offset = 0 + if self.namespace: + self._namespace_offset = file.tell() - data_pool_offset + 1 + file.write(self.namespace + "\x00") + else: + self._namespace_offset = 0 + for m in self.methods: + m.write_name(file, data_pool_offset) + for c in self.constants: + c.write_name(file, data_pool_offset) + + +class Typelib(object): + """ + A typelib represents one entire typelib file and all the interfaces + referenced within, whether defined entirely within the typelib or + merely referenced by name or IID. + + Typelib objects may be instantiated directly and populated with data, + or the static Typelib.read method may be called to read one from a file. + + """ + _header = struct.Struct(">16sBBHIII") + + def __init__(self, version=TYPELIB_VERSION, interfaces=[], annotations=[]): + """ + Instantiate a new Typelib. + + """ + self.version = version + self.interfaces = list(interfaces) + self.annotations = list(annotations) + self.filename = None + + @staticmethod + def iid_to_string(iid): + """ + Convert a 16-byte IID into a UUID string. + + """ + def hexify(s): + return ''.join(["%02x" % ord(x) for x in s]) + + return "%s-%s-%s-%s-%s" % (hexify(iid[:4]), hexify(iid[4:6]), + hexify(iid[6:8]), hexify(iid[8:10]), + hexify(iid[10:])) + + @staticmethod + def string_to_iid(iid_str): + """ + Convert a UUID string into a 16-byte IID. + + """ + s = iid_str.replace('-', '') + return ''.join([chr(int(s[i:i+2], 16)) for i in range(0, len(s), 2)]) + + @staticmethod + def read_string(map, data_pool, offset): + if offset == 0: + return "" + sz = map.find('\x00', data_pool + offset - 1) + if sz == -1: + return "" + return map[data_pool + offset - 1:sz] + + @staticmethod + def read(input_file): + """ + Read a typelib from |input_file| and return + the constructed Typelib object. |input_file| can be a filename + or a file-like object. + + """ + filename = "" + data = None + expected_size = None + if isinstance(input_file, basestring): + filename = input_file + with open(input_file, "rb") as f: + st = os.fstat(f.fileno()) + data = f.read(st.st_size) + expected_size = st.st_size + else: + data = input_file.read() + + (magic, + major_ver, + minor_ver, + num_interfaces, + file_length, + interface_directory_offset, + data_pool_offset) = Typelib._header.unpack_from(data) + if magic != XPT_MAGIC: + raise FileFormatError("Bad magic: %s" % magic) + xpt = Typelib((major_ver, minor_ver)) + xpt.filename = filename + if expected_size and file_length != expected_size: + raise FileFormatError("File is of wrong length, got %d bytes, expected %d" % (expected_size, file_length)) + # XXX: by spec this is a zero-based file offset. however, + # the xpt_xdr code always subtracts 1 from data offsets + # (because that's what you do in the data pool) so it + # winds up accidentally treating this as 1-based. + # Filed as: https://bugzilla.mozilla.org/show_bug.cgi?id=575343 + interface_directory_offset -= 1 + # make a half-hearted attempt to read Annotations, + # since XPIDL doesn't produce any anyway. + start = Typelib._header.size + (anno, ) = struct.unpack_from(">B", data, start) + tag = anno & 0x7F + if tag == 0: # EmptyAnnotation + xpt.annotations.append(None) + # We don't bother handling PrivateAnnotations or anything + + for i in range(num_interfaces): + # iid, name, namespace, interface_descriptor + start = interface_directory_offset + i * Interface._direntry.size + ide = Interface._direntry.unpack_from(data, start) + iid = Typelib.iid_to_string(ide[0]) + name = Typelib.read_string(data, data_pool_offset, ide[1]) + namespace = Typelib.read_string(data, data_pool_offset, ide[2]) + iface = Interface(name, iid, namespace) + iface._descriptor_offset = ide[3] + iface.xpt_filename = xpt.filename + xpt.interfaces.append(iface) + for iface in xpt.interfaces: + iface.read_descriptor(xpt, data, data_pool_offset) + return xpt + + def __repr__(self): + return "<Typelib with %d interfaces>" % len(self.interfaces) + + def _sanityCheck(self): + """ + Check certain assumptions about data contained in this typelib. + Sort the interfaces array by IID, check that all interfaces + referenced by methods exist in the array. + + """ + self.interfaces.sort() + for i in self.interfaces: + if i.parent and i.parent not in self.interfaces: + raise DataError("Interface %s has parent %s not present in typelib!" % (i.name, i.parent.name)) + for m in i.methods: + for n, p in enumerate(m.params): + if isinstance(p, InterfaceType) and \ + p.iface not in self.interfaces: + raise DataError("Interface method %s::%s, parameter %d references interface %s not present in typelib!" % (i.name, m.name, n, p.iface.name)) + if isinstance(m.result, InterfaceType) and m.result.iface not in self.interfaces: + raise DataError("Interface method %s::%s, result references interface %s not present in typelib!" % (i.name, m.name, m.result.iface.name)) + + def writefd(self, fd): + # write out space for a header + one empty annotation, + # padded to 4-byte alignment. + headersize = (Typelib._header.size + 1) + if headersize % 4: + headersize += 4 - headersize % 4 + fd.write("\x00" * headersize) + # save this offset, it's the interface directory offset. + interface_directory_offset = fd.tell() + # write out space for an interface directory + fd.write("\x00" * Interface._direntry.size * len(self.interfaces)) + # save this offset, it's the data pool offset. + data_pool_offset = fd.tell() + # write out all the interface descriptors to the data pool + for i in self.interfaces: + i.write_names(fd, data_pool_offset) + i.write(self, fd, data_pool_offset) + # now, seek back and write the header + file_len = fd.tell() + fd.seek(0) + fd.write(Typelib._header.pack(XPT_MAGIC, + TYPELIB_VERSION[0], + TYPELIB_VERSION[1], + len(self.interfaces), + file_len, + interface_directory_offset, + data_pool_offset)) + # write an empty annotation + fd.write(struct.pack(">B", 0x80)) + # now write the interface directory + # XXX: bug-compatible with existing xpt lib, put it one byte + # ahead of where it's supposed to be. + fd.seek(interface_directory_offset - 1) + for i in self.interfaces: + i.write_directory_entry(fd) + + def write(self, output_file): + """ + Write the contents of this typelib to |output_file|, + which can be either a filename or a file-like object. + + """ + self._sanityCheck() + if isinstance(output_file, basestring): + with open(output_file, "wb") as f: + self.writefd(f) + else: + self.writefd(output_file) + + def dump(self, out): + """ + Print a human-readable listing of the contents of this typelib + to |out|, in the format of xpt_dump. + + """ + out.write("""Header: + Major version: %d + Minor version: %d + Number of interfaces: %d + Annotations:\n""" % (self.version[0], self.version[1], len(self.interfaces))) + for i, a in enumerate(self.annotations): + if a is None: + out.write(" Annotation #%d is empty.\n" % i) + out.write("\nInterface Directory:\n") + for i in self.interfaces: + out.write(" - %s::%s (%s):\n" % (i.namespace, i.name, i.iid)) + if not i.resolved: + out.write(" [Unresolved]\n") + else: + if i.parent: + out.write(" Parent: %s::%s\n" % (i.parent.namespace, + i.parent.name)) + out.write(""" Flags: + Scriptable: %s + BuiltinClass: %s + Function: %s\n""" % (i.scriptable and "TRUE" or "FALSE", + i.builtinclass and "TRUE" or "FALSE", + i.function and "TRUE" or "FALSE")) + out.write(" Methods:\n") + if len(i.methods) == 0: + out.write(" No Methods\n") + else: + for m in i.methods: + out.write(" %s%s%s%s%s%s%s %s %s(%s);\n" % ( + m.getter and "G" or " ", + m.setter and "S" or " ", + m.hidden and "H" or " ", + m.notxpcom and "N" or " ", + m.constructor and "C" or " ", + m.optargc and "O" or " ", + m.implicit_jscontext and "J" or " ", + str(m.result.type), + m.name, + m.params and ", ".join(str(p) for p in m.params) or "" + )) + out.write(" Constants:\n") + if len(i.constants) == 0: + out.write(" No Constants\n") + else: + for c in i.constants: + out.write(" %s %s = %d;\n" % (c.type, c.name, c.value)) + + +def xpt_dump(file): + """ + Dump the contents of |file| to stdout in the format of xpt_dump. + + """ + t = Typelib.read(file) + t.dump(sys.stdout) + + +def xpt_link(inputs): + """ + Link all of the xpt files in |inputs| together and return the result + as a Typelib object. All entries in inputs may be filenames or + file-like objects. Non-scriptable interfaces that are unreferenced + from scriptable interfaces will be removed during linking. + + """ + def read_input(i): + if isinstance(i, Typelib): + return i + return Typelib.read(i) + + if not inputs: + print >>sys.stderr, "Usage: xpt_link <destination file> <input files>" + return None + # This is the aggregate list of interfaces. + interfaces = [] + # This will be a dict of replaced interface -> replaced with + # containing interfaces that were replaced with interfaces from + # another typelib, and the interface that replaced them. + merged_interfaces = {} + for f in inputs: + t = read_input(f) + interfaces.extend(t.interfaces) + # Sort interfaces by name so we can merge adjacent duplicates + interfaces.sort(key=operator.attrgetter('name')) + + Result = enum('Equal', # Interfaces the same, doesn't matter + 'NotEqual', # Interfaces differ, keep both + 'KeepFirst', # Replace second interface with first + 'KeepSecond') # Replace first interface with second + + def compare(i, j): + """ + Compare two interfaces, determine if they're equal or + completely different, or should be merged (and indicate which + one to keep in that case). + + """ + if i == j: + # Arbitrary, just pick one + return Result.Equal + if i.name != j.name: + if i.iid == j.iid and i.iid != Interface.UNRESOLVED_IID: + # Same IID but different names: raise an exception. + raise DataError( + "Typelibs contain definitions of interface %s" + " with different names (%s (%s) vs %s (%s))!" % + (i.iid, i.name, i.xpt_filename, j.name, j.xpt_filename)) + # Otherwise just different interfaces. + return Result.NotEqual + # Interfaces have the same name, so either they need to be merged + # or there's a data error. Sort out which one to keep + if i.resolved != j.resolved: + # prefer resolved interfaces over unresolved + if j.resolved: + assert i.iid == j.iid or i.iid == Interface.UNRESOLVED_IID + # keep j + return Result.KeepSecond + else: + assert i.iid == j.iid or j.iid == Interface.UNRESOLVED_IID + # replace j with i + return Result.KeepFirst + elif i.iid != j.iid: + # Prefer unresolved interfaces with valid IIDs + if j.iid == Interface.UNRESOLVED_IID: + # replace j with i + assert not j.resolved + return Result.KeepFirst + elif i.iid == Interface.UNRESOLVED_IID: + # keep j + assert not i.resolved + return Result.KeepSecond + else: + # Same name but different IIDs: raise an exception. + raise DataError( + "Typelibs contain definitions of interface %s" + " with different IIDs (%s (%s) vs %s (%s))!" % + (i.name, i.iid, i.xpt_filename, + j.iid, j.xpt_filename)) + raise DataError("No idea what happened here: %s:%s (%s), %s:%s (%s)" % + (i.name, i.iid, i.xpt_filename, j.name, j.iid, j.xpt_filename)) + + # Compare interfaces pairwise to find duplicates that should be merged. + i = 1 + while i < len(interfaces): + res = compare(interfaces[i-1], interfaces[i]) + if res == Result.NotEqual: + i += 1 + elif res == Result.Equal: + # Need to drop one but it doesn't matter which + del interfaces[i] + elif res == Result.KeepFirst: + merged_interfaces[interfaces[i]] = interfaces[i-1] + del interfaces[i] + elif res == Result.KeepSecond: + merged_interfaces[interfaces[i-1]] = interfaces[i] + del interfaces[i-1] + + # Now fixup any merged interfaces + def checkType(t): + if isinstance(t, InterfaceType) and t.iface in merged_interfaces: + t.iface = merged_interfaces[t.iface] + elif isinstance(t, ArrayType) and \ + isinstance(t.element_type, InterfaceType) and \ + t.element_type.iface in merged_interfaces: + t.element_type.iface = merged_interfaces[t.element_type.iface] + + for i in interfaces: + # Replace parent references + if i.parent in merged_interfaces: + i.parent = merged_interfaces[i.parent] + for m in i.methods: + # Replace InterfaceType params and return values + checkType(m.result.type) + for p in m.params: + checkType(p.type) + + # There's no need to have non-scriptable interfaces in a typelib, and + # removing them saves memory when typelibs are loaded. But we can't + # just blindly remove all non-scriptable interfaces, since we still + # need to know about non-scriptable interfaces referenced from + # scriptable interfaces. + worklist = set(i for i in interfaces if i.scriptable) + required_interfaces = set() + + def maybe_add_to_worklist(iface): + if iface in required_interfaces or iface in worklist: + return + worklist.add(iface) + + while worklist: + i = worklist.pop() + required_interfaces.add(i) + if i.parent: + maybe_add_to_worklist(i.parent) + for m in i.methods: + if isinstance(m.result.type, InterfaceType): + maybe_add_to_worklist(m.result.type.iface) + for p in m.params: + if isinstance(p.type, InterfaceType): + maybe_add_to_worklist(p.type.iface) + elif isinstance(p.type, ArrayType) and isinstance(p.type.element_type, InterfaceType): + maybe_add_to_worklist(p.type.element_type.iface) + + interfaces = list(required_interfaces) + + # Re-sort interfaces (by IID) + interfaces.sort() + return Typelib(interfaces=interfaces) + +if __name__ == '__main__': + if len(sys.argv) < 3: + print >>sys.stderr, "xpt <dump|link> <files>" + sys.exit(1) + if sys.argv[1] == 'dump': + xpt_dump(sys.argv[2]) + elif sys.argv[1] == 'link': + xpt_link(sys.argv[3:]).write(sys.argv[2]) |