diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /xpcom/typelib | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'xpcom/typelib')
-rw-r--r-- | xpcom/typelib/moz.build | 8 | ||||
-rw-r--r-- | xpcom/typelib/xpt/moz.build | 40 | ||||
-rw-r--r-- | xpcom/typelib/xpt/tools/moz.build | 13 | ||||
-rw-r--r-- | xpcom/typelib/xpt/tools/runtests.py | 770 | ||||
-rwxr-xr-x | xpcom/typelib/xpt/tools/xpt.py | 1540 | ||||
-rw-r--r-- | xpcom/typelib/xpt/xpt_arena.cpp | 196 | ||||
-rw-r--r-- | xpcom/typelib/xpt/xpt_arena.h | 70 | ||||
-rw-r--r-- | xpcom/typelib/xpt/xpt_struct.cpp | 432 | ||||
-rw-r--r-- | xpcom/typelib/xpt/xpt_struct.h | 366 | ||||
-rw-r--r-- | xpcom/typelib/xpt/xpt_xdr.cpp | 227 | ||||
-rw-r--r-- | xpcom/typelib/xpt/xpt_xdr.h | 86 |
11 files changed, 3748 insertions, 0 deletions
diff --git a/xpcom/typelib/moz.build b/xpcom/typelib/moz.build new file mode 100644 index 000000000..3d82a998d --- /dev/null +++ b/xpcom/typelib/moz.build @@ -0,0 +1,8 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += ['xpt'] + diff --git a/xpcom/typelib/xpt/moz.build b/xpcom/typelib/xpt/moz.build new file mode 100644 index 000000000..80b9160b0 --- /dev/null +++ b/xpcom/typelib/xpt/moz.build @@ -0,0 +1,40 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Library('xpt') + +DIRS += ['tools'] + +UNIFIED_SOURCES += [ + 'xpt_arena.cpp', + 'xpt_struct.cpp', + 'xpt_xdr.cpp', +] + +EXPORTS += [ + 'xpt_arena.h', + 'xpt_struct.h', + 'xpt_xdr.h', +] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '!/xpcom/base', + '/xpcom/base', +] + +if CONFIG['_MSC_VER']: + CFLAGS += ['-Zl'] + +# Code with FINAL_LIBRARY = 'xul' shouldn't do this, but the code +# here doesn't use malloc functions anyways, while not setting +# MOZ_NO_MOZALLOC makes the code include mozalloc.h, which includes +# inline operator new definitions that MSVC linker doesn't strip +# when linking the xpt tests. +DEFINES['MOZ_NO_MOZALLOC'] = True + +DIST_INSTALL = True diff --git a/xpcom/typelib/xpt/tools/moz.build b/xpcom/typelib/xpt/tools/moz.build new file mode 100644 index 000000000..bd4a2836b --- /dev/null +++ b/xpcom/typelib/xpt/tools/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +PYTHON_UNIT_TESTS += [ + 'runtests.py', +] + +SDK_FILES.bin += [ + 'xpt.py', +] diff --git a/xpcom/typelib/xpt/tools/runtests.py b/xpcom/typelib/xpt/tools/runtests.py new file mode 100644 index 000000000..a86e6625d --- /dev/null +++ b/xpcom/typelib/xpt/tools/runtests.py @@ -0,0 +1,770 @@ +#!/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. + +import difflib +import os +from StringIO import StringIO +import subprocess +import tempfile +import mozunit +import unittest +import xpt + + +def get_output(bin, file): + p = subprocess.Popen([bin, file], stdout=subprocess.PIPE) + stdout, _ = p.communicate() + return stdout + +if "MOZILLA_OBJDIR" in os.environ: + class CheckXPTDump(unittest.TestCase): + def test_xpt_dump_diffs(self): + MOZILLA_OBJDIR = os.environ["MOZILLA_OBJDIR"] + xptdump = os.path.abspath(os.path.join(MOZILLA_OBJDIR, + "dist", "bin", "xpt_dump")) + components = os.path.abspath(os.path.join(MOZILLA_OBJDIR, + "dist", "bin", "components")) + for f in os.listdir(components): + if not f.endswith(".xpt"): + continue + fullpath = os.path.join(components, f) + # read a Typelib and dump it to a string + t = xpt.Typelib.read(fullpath) + self.assert_(t is not None) + outf = StringIO() + t.dump(outf) + out = outf.getvalue() + # now run xpt_dump on it + out2 = get_output(xptdump, fullpath) + if out != out2: + print "diff %s" % f + for line in difflib.unified_diff(out2.split("\n"), out.split("\n"), lineterm=""): + print line + self.assert_(out == out2, "xpt_dump output should be identical for %s" % f) + + +class TestIIDString(unittest.TestCase): + def test_iid_str_roundtrip(self): + iid_str = "11223344-5566-7788-9900-aabbccddeeff" + iid = xpt.Typelib.string_to_iid(iid_str) + self.assertEqual(iid_str, xpt.Typelib.iid_to_string(iid)) + + def test_iid_roundtrip(self): + iid = "\x11\x22\x33\x44\x55\x66\x77\x88\x99\x00\xaa\xbb\xcc\xdd\xee\xff" + iid_str = xpt.Typelib.iid_to_string(iid) + self.assertEqual(iid, xpt.Typelib.string_to_iid(iid_str)) + + +class TypelibCompareMixin: + def assertEqualTypelibs(self, t1, t2): + self.assert_(t1 is not None, "Should not be None") + self.assert_(t2 is not None, "Should not be None") + self.assertEqual(t1.version, t2.version, "Versions should be equal") + self.assertEqual(len(t1.interfaces), len(t2.interfaces), + "Number of interfaces should be equal") + for i, j in zip(t1.interfaces, t2.interfaces): + self.assertEqualInterfaces(i, j) + + def assertEqualInterfaces(self, i1, i2): + self.assert_(i1 is not None, "Should not be None") + self.assert_(i2 is not None, "Should not be None") + self.assertEqual(i1.name, i2.name, "Names should be equal") + self.assertEqual(i1.iid, i2.iid, "IIDs should be equal") + self.assertEqual(i1.namespace, i2.namespace, + "Namespaces should be equal") + self.assertEqual(i1.resolved, i2.resolved, + "Resolved status should be equal") + if i1.resolved: + if i1.parent or i2.parent: + # Can't test exact equality, probably different objects + self.assertEqualInterfaces(i1.parent, i2.parent) + self.assertEqual(len(i1.methods), len(i2.methods)) + for m, n in zip(i1.methods, i2.methods): + self.assertEqualMethods(m, n) + self.assertEqual(len(i1.constants), len(i2.constants)) + for c, d in zip(i1.constants, i2.constants): + self.assertEqualConstants(c, d) + self.assertEqual(i1.scriptable, i2.scriptable, + "Scriptable status should be equal") + self.assertEqual(i1.function, i2.function, + "Function status should be equal") + + def assertEqualMethods(self, m1, m2): + self.assert_(m1 is not None, "Should not be None") + self.assert_(m2 is not None, "Should not be None") + self.assertEqual(m1.name, m2.name, "Names should be equal") + self.assertEqual(m1.getter, m2.getter, "Getter flag should be equal") + self.assertEqual(m1.setter, m2.setter, "Setter flag should be equal") + self.assertEqual(m1.notxpcom, m2.notxpcom, + "notxpcom flag should be equal") + self.assertEqual(m1.constructor, m2.constructor, + "constructor flag should be equal") + self.assertEqual(m1.hidden, m2.hidden, "hidden flag should be equal") + self.assertEqual(m1.optargc, m2.optargc, "optargc flag should be equal") + self.assertEqual(m1.implicit_jscontext, m2.implicit_jscontext, + "implicit_jscontext flag should be equal") + for p1, p2 in zip(m1.params, m2.params): + self.assertEqualParams(p1, p2) + self.assertEqualParams(m1.result, m2.result) + + def assertEqualConstants(self, c1, c2): + self.assert_(c1 is not None, "Should not be None") + self.assert_(c2 is not None, "Should not be None") + self.assertEqual(c1.name, c2.name) + self.assertEqual(c1.value, c2.value) + self.assertEqualTypes(c1.type, c2.type) + + def assertEqualParams(self, p1, p2): + self.assert_(p1 is not None, "Should not be None") + self.assert_(p2 is not None, "Should not be None") + self.assertEqualTypes(p1.type, p2.type) + self.assertEqual(p1.in_, p2.in_) + self.assertEqual(p1.out, p2.out) + self.assertEqual(p1.retval, p2.retval) + self.assertEqual(p1.shared, p2.shared) + self.assertEqual(p1.dipper, p2.dipper) + self.assertEqual(p1.optional, p2.optional) + + def assertEqualTypes(self, t1, t2): + self.assert_(t1 is not None, "Should not be None") + self.assert_(t2 is not None, "Should not be None") + self.assertEqual(type(t1), type(t2), "type types should be equal") + self.assertEqual(t1.pointer, t2.pointer, + "pointer flag should be equal for %s and %s" % (t1, t2)) + self.assertEqual(t1.reference, t2.reference) + if isinstance(t1, xpt.SimpleType): + self.assertEqual(t1.tag, t2.tag) + elif isinstance(t1, xpt.InterfaceType): + self.assertEqualInterfaces(t1.iface, t2.iface) + elif isinstance(t1, xpt.InterfaceIsType): + self.assertEqual(t1.param_index, t2.param_index) + elif isinstance(t1, xpt.ArrayType): + self.assertEqualTypes(t1.element_type, t2.element_type) + self.assertEqual(t1.size_is_arg_num, t2.size_is_arg_num) + self.assertEqual(t1.length_is_arg_num, t2.length_is_arg_num) + elif isinstance(t1, xpt.StringWithSizeType) or isinstance(t1, xpt.WideStringWithSizeType): + self.assertEqual(t1.size_is_arg_num, t2.size_is_arg_num) + self.assertEqual(t1.length_is_arg_num, t2.length_is_arg_num) + + +class TestTypelibReadWrite(unittest.TestCase, TypelibCompareMixin): + def test_read_file(self): + """ + Test that a Typelib can be read/written from/to a file. + """ + t = xpt.Typelib() + # add an unresolved interface + t.interfaces.append(xpt.Interface("IFoo")) + fd, f = tempfile.mkstemp() + os.close(fd) + t.write(f) + t2 = xpt.Typelib.read(f) + os.remove(f) + self.assert_(t2 is not None) + self.assertEqualTypelibs(t, t2) + + +# TODO: test flags in various combinations +class TestTypelibRoundtrip(unittest.TestCase, TypelibCompareMixin): + def checkRoundtrip(self, t): + s = StringIO() + t.write(s) + s.seek(0) + t2 = xpt.Typelib.read(s) + self.assert_(t2 is not None) + self.assertEqualTypelibs(t, t2) + + def test_simple(self): + t = xpt.Typelib() + # add an unresolved interface + t.interfaces.append(xpt.Interface("IFoo")) + self.checkRoundtrip(t) + + t = xpt.Typelib() + # add an unresolved interface with an IID + t.interfaces.append(xpt.Interface("IBar", "11223344-5566-7788-9900-aabbccddeeff")) + self.checkRoundtrip(t) + + def test_parent(self): + """ + Test that an interface's parent property is correctly serialized + and deserialized. + + """ + t = xpt.Typelib() + pi = xpt.Interface("IParent") + t.interfaces.append(pi) + t.interfaces.append(xpt.Interface("IChild", iid="11223344-5566-7788-9900-aabbccddeeff", + parent=pi, resolved=True)) + self.checkRoundtrip(t) + + def test_ifaceFlags(self): + """ + Test that an interface's flags are correctly serialized + and deserialized. + + """ + t = xpt.Typelib() + t.interfaces.append(xpt.Interface("IFlags", iid="11223344-5566-7788-9900-aabbccddeeff", + resolved=True, + scriptable=True, + function=True)) + self.checkRoundtrip(t) + + def test_constants(self): + c = xpt.Constant("X", xpt.SimpleType(xpt.Type.Tags.uint32), + 0xF000F000) + i = xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + constants=[c]) + t = xpt.Typelib(interfaces=[i]) + self.checkRoundtrip(t) + # tack on some more constants + i.constants.append(xpt.Constant("Y", + xpt.SimpleType(xpt.Type.Tags.int16), + -30000)) + i.constants.append(xpt.Constant("Z", + xpt.SimpleType(xpt.Type.Tags.uint16), + 0xB0B0)) + i.constants.append(xpt.Constant("A", + xpt.SimpleType(xpt.Type.Tags.int32), + -1000000)) + self.checkRoundtrip(t) + + def test_methods(self): + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + i = xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m]) + t = xpt.Typelib(interfaces=[i]) + self.checkRoundtrip(t) + # add some more methods + i.methods.append(xpt.Method("One", xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + params=[ + xpt.Param(xpt.SimpleType(xpt.Type.Tags.int64)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.float, pointer=True)) + ])) + self.checkRoundtrip(t) + # test some other types (should really be more thorough) + i.methods.append(xpt.Method("Two", xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + params=[ + xpt.Param(xpt.SimpleType(xpt.Type.Tags.UTF8String, pointer=True)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.wchar_t_ptr, pointer=True)) + ])) + self.checkRoundtrip(t) + # add a method with an InterfaceType argument + bar = xpt.Interface("IBar") + t.interfaces.append(bar) + i.methods.append(xpt.Method("IFaceMethod", xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + params=[ + xpt.Param(xpt.InterfaceType(bar)) + ])) + self.checkRoundtrip(t) + + # add a method with an InterfaceIsType argument + i.methods.append(xpt.Method("IFaceIsMethod", xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)), + params=[ + xpt.Param(xpt.InterfaceIsType(1)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.nsIID)) + ])) + self.checkRoundtrip(t) + + # add a method with an ArrayType argument + i.methods.append(xpt.Method("ArrayMethod", xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)), + params=[ + xpt.Param(xpt.ArrayType( + xpt.SimpleType(xpt.Type.Tags.int32), + 1, 2)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + ])) + self.checkRoundtrip(t) + + # add a method with a StringWithSize and WideStringWithSize arguments + i.methods.append(xpt.Method("StringWithSizeMethod", xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)), + params=[ + xpt.Param(xpt.StringWithSizeType( + 1, 2)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + xpt.Param(xpt.WideStringWithSizeType( + 4, 5)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)), + ])) + self.checkRoundtrip(t) + + +class TestInterfaceCmp(unittest.TestCase): + def test_unresolvedName(self): + """ + Test comparison function on xpt.Interface by name. + + """ + i1 = xpt.Interface("ABC") + i2 = xpt.Interface("DEF") + self.assert_(i1 < i2) + self.assert_(i1 != i2) + + def test_unresolvedEqual(self): + """ + Test comparison function on xpt.Interface with equal names and IIDs. + + """ + i1 = xpt.Interface("ABC") + i2 = xpt.Interface("ABC") + self.assert_(i1 == i2) + + def test_unresolvedIID(self): + """ + Test comparison function on xpt.Interface with different IIDs. + + """ + # IIDs sort before names + i1 = xpt.Interface("ABC", iid="22334411-5566-7788-9900-aabbccddeeff") + i2 = xpt.Interface("DEF", iid="11223344-5566-7788-9900-aabbccddeeff") + self.assert_(i2 < i1) + self.assert_(i2 != i1) + + def test_unresolvedResolved(self): + """ + Test comparison function on xpt.Interface with interfaces with + identical names and IIDs but different resolved status. + + """ + i1 = xpt.Interface("ABC", iid="11223344-5566-7788-9900-aabbccddeeff") + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + i2 = xpt.Interface("ABC", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m]) + self.assert_(i2 < i1) + self.assert_(i2 != i1) + + def test_resolvedIdentical(self): + """ + Test comparison function on xpt.Interface with interfaces with + identical names and IIDs, both of which are resolved. + + """ + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + i1 = xpt.Interface("ABC", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m]) + i2 = xpt.Interface("ABC", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m]) + self.assert_(i2 == i1) + + +class TestXPTLink(unittest.TestCase): + def test_mergeDifferent(self): + """ + Test that merging two typelibs with completely different interfaces + produces the correctly merged typelib. + + """ + t1 = xpt.Typelib() + # add an unresolved interface + t1.interfaces.append(xpt.Interface("IFoo", scriptable=True)) + t2 = xpt.Typelib() + # add an unresolved interface + t2.interfaces.append(xpt.Interface("IBar", scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + # Interfaces should wind up sorted + self.assertEqual("IBar", t3.interfaces[0].name) + self.assertEqual("IFoo", t3.interfaces[1].name) + + # Add some IID values + t1 = xpt.Typelib() + # add an unresolved interface + t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", scriptable=True)) + t2 = xpt.Typelib() + # add an unresolved interface + t2.interfaces.append(xpt.Interface("IBar", iid="44332211-6655-8877-0099-aabbccddeeff", scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + # Interfaces should wind up sorted + self.assertEqual("IFoo", t3.interfaces[0].name) + self.assertEqual("IBar", t3.interfaces[1].name) + + def test_mergeConflict(self): + """ + Test that merging two typelibs with conflicting interface definitions + raises an error. + + """ + # Same names, different IIDs + t1 = xpt.Typelib() + # add an unresolved interface + t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff")) + t2 = xpt.Typelib() + # add an unresolved interface, same name different IID + t2.interfaces.append(xpt.Interface("IFoo", iid="44332211-6655-8877-0099-aabbccddeeff")) + self.assertRaises(xpt.DataError, xpt.xpt_link, [t1, t2]) + + # Same IIDs, different names + t1 = xpt.Typelib() + # add an unresolved interface + t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff")) + t2 = xpt.Typelib() + # add an unresolved interface, same IID different name + t2.interfaces.append(xpt.Interface("IBar", iid="11223344-5566-7788-9900-aabbccddeeff")) + self.assertRaises(xpt.DataError, xpt.xpt_link, [t1, t2]) + + def test_mergeUnresolvedIID(self): + """ + Test that merging a typelib with an unresolved definition of + an interface that's also unresolved in this typelib, but one + has a valid IID copies the IID value to the resulting typelib. + + """ + # Unresolved in both, but t1 has an IID value + t1 = xpt.Typelib() + # add an unresolved interface with a valid IID + t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", scriptable=True)) + t2 = xpt.Typelib() + # add an unresolved interface, no IID + t2.interfaces.append(xpt.Interface("IFoo")) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(1, len(t3.interfaces)) + self.assertEqual("IFoo", t3.interfaces[0].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[0].iid) + # Unresolved in both, but t2 has an IID value + t1 = xpt.Typelib() + # add an unresolved interface, no IID + t1.interfaces.append(xpt.Interface("IFoo")) + t2 = xpt.Typelib() + # add an unresolved interface with a valid IID + t2.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(1, len(t3.interfaces)) + self.assertEqual("IFoo", t3.interfaces[0].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[0].iid) + + def test_mergeResolvedUnresolved(self): + """ + Test that merging two typelibs, one of which contains an unresolved + reference to an interface, and the other of which contains a + resolved reference to the same interface results in keeping the + resolved reference. + + """ + # t1 has an unresolved interface, t2 has a resolved version + t1 = xpt.Typelib() + # add an unresolved interface + t1.interfaces.append(xpt.Interface("IFoo")) + t2 = xpt.Typelib() + # add a resolved interface + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + t2.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(1, len(t3.interfaces)) + self.assertEqual("IFoo", t3.interfaces[0].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[0].iid) + self.assert_(t3.interfaces[0].resolved) + self.assertEqual(1, len(t3.interfaces[0].methods)) + self.assertEqual("Bar", t3.interfaces[0].methods[0].name) + + # t1 has a resolved interface, t2 has an unresolved version + t1 = xpt.Typelib() + # add a resolved interface + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t2 = xpt.Typelib() + # add an unresolved interface + t2.interfaces.append(xpt.Interface("IFoo")) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(1, len(t3.interfaces)) + self.assertEqual("IFoo", t3.interfaces[0].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[0].iid) + self.assert_(t3.interfaces[0].resolved) + self.assertEqual(1, len(t3.interfaces[0].methods)) + self.assertEqual("Bar", t3.interfaces[0].methods[0].name) + + def test_mergeReplaceParents(self): + """ + Test that merging an interface results in other interfaces' parent + member being updated properly. + + """ + # t1 has an unresolved interface, t2 has a resolved version, + # but t1 also has another interface whose parent is the unresolved + # interface. + t1 = xpt.Typelib() + # add an unresolved interface + pi = xpt.Interface("IFoo") + t1.interfaces.append(pi) + # add a child of the unresolved interface + t1.interfaces.append(xpt.Interface("IChild", iid="11111111-1111-1111-1111-111111111111", + resolved=True, parent=pi, scriptable=True)) + t2 = xpt.Typelib() + # add a resolved interface + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + t2.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + self.assertEqual("IChild", t3.interfaces[0].name) + self.assertEqual("11111111-1111-1111-1111-111111111111", t3.interfaces[0].iid) + self.assertEqual("IFoo", t3.interfaces[1].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[1].iid) + self.assert_(t3.interfaces[0].resolved) + # Ensure that IChild's parent has been updated + self.assertEqual(t3.interfaces[1], t3.interfaces[0].parent) + self.assert_(t3.interfaces[0].parent.resolved) + + # t1 has a resolved interface, t2 has an unresolved version, + # but t2 also has another interface whose parent is the unresolved + # interface. + t1 = xpt.Typelib() + # add a resolved interface + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t2 = xpt.Typelib() + # add an unresolved interface + pi = xpt.Interface("IFoo") + t2.interfaces.append(pi) + # add a child of the unresolved interface + t2.interfaces.append(xpt.Interface("IChild", iid="11111111-1111-1111-1111-111111111111", + resolved=True, parent=pi, scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + self.assertEqual("IChild", t3.interfaces[0].name) + self.assertEqual("11111111-1111-1111-1111-111111111111", t3.interfaces[0].iid) + self.assertEqual("IFoo", t3.interfaces[1].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[1].iid) + self.assert_(t3.interfaces[0].resolved) + # Ensure that IChild's parent has been updated + self.assertEqual(t3.interfaces[1], t3.interfaces[0].parent) + self.assert_(t3.interfaces[0].parent.resolved) + + def test_mergeReplaceRetval(self): + """ + Test that merging an interface correctly updates InterfaceType + return values on methods of other interfaces. + + """ + # t1 has an unresolved interface and an interface that uses the + # unresolved interface as a return value from a method. t2 + # has a resolved version of the unresolved interface. + t1 = xpt.Typelib() + # add an unresolved interface + i = xpt.Interface("IFoo") + t1.interfaces.append(i) + # add an interface that uses the unresolved interface + # as a return value in a method. + p = xpt.Param(xpt.InterfaceType(i)) + m = xpt.Method("ReturnIface", p) + t1.interfaces.append(xpt.Interface("IRetval", iid="11111111-1111-1111-1111-111111111111", + methods=[m], scriptable=True)) + t2 = xpt.Typelib() + # add a resolved interface + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + t2.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + self.assertEqual("IRetval", t3.interfaces[0].name) + self.assertEqual("11111111-1111-1111-1111-111111111111", t3.interfaces[0].iid) + self.assertEqual("IFoo", t3.interfaces[1].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[1].iid) + self.assert_(t3.interfaces[1].resolved) + # Ensure that IRetval's method's return value type has been updated. + self.assertEqual(1, len(t3.interfaces[0].methods)) + self.assert_(t3.interfaces[0].methods[0].result.type.iface.resolved) + self.assertEqual(t3.interfaces[1], + t3.interfaces[0].methods[0].result.type.iface) + + # t1 has a resolved interface. t2 has an unresolved version and + # an interface that uses the unresolved interface as a return value + # from a method. + t1 = xpt.Typelib() + # add a resolved interface + p = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + m = xpt.Method("Bar", p) + t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t2 = xpt.Typelib() + # add an unresolved interface + i = xpt.Interface("IFoo") + t2.interfaces.append(i) + # add an interface that uses the unresolved interface + # as a return value in a method. + p = xpt.Param(xpt.InterfaceType(i)) + m = xpt.Method("ReturnIface", p) + t2.interfaces.append(xpt.Interface("IRetval", iid="11111111-1111-1111-1111-111111111111", + methods=[m], scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + self.assertEqual("IRetval", t3.interfaces[0].name) + self.assertEqual("11111111-1111-1111-1111-111111111111", t3.interfaces[0].iid) + self.assertEqual("IFoo", t3.interfaces[1].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[1].iid) + self.assert_(t3.interfaces[1].resolved) + # Ensure that IRetval's method's return value type has been updated. + self.assertEqual(1, len(t3.interfaces[0].methods)) + self.assert_(t3.interfaces[0].methods[0].result.type.iface.resolved) + self.assertEqual(t3.interfaces[1], + t3.interfaces[0].methods[0].result.type.iface) + + def test_mergeReplaceParams(self): + """ + Test that merging an interface correctly updates InterfaceType + params on methods of other interfaces. + + """ + # t1 has an unresolved interface and an interface that uses the + # unresolved interface as a param value in a method. t2 + # has a resolved version of the unresolved interface. + t1 = xpt.Typelib() + # add an unresolved interface + i = xpt.Interface("IFoo") + t1.interfaces.append(i) + # add an interface that uses the unresolved interface + # as a param value in a method. + vp = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + p = xpt.Param(xpt.InterfaceType(i)) + m = xpt.Method("IfaceParam", vp, params=[p]) + t1.interfaces.append(xpt.Interface("IParam", iid="11111111-1111-1111-1111-111111111111", + methods=[m], scriptable=True)) + t2 = xpt.Typelib() + # add a resolved interface + m = xpt.Method("Bar", vp) + t2.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + self.assertEqual("IParam", t3.interfaces[0].name) + self.assertEqual("11111111-1111-1111-1111-111111111111", t3.interfaces[0].iid) + self.assertEqual("IFoo", t3.interfaces[1].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[1].iid) + self.assert_(t3.interfaces[1].resolved) + # Ensure that IRetval's method's param type has been updated. + self.assertEqual(1, len(t3.interfaces[0].methods)) + self.assert_(t3.interfaces[0].methods[0].params[0].type.iface.resolved) + self.assertEqual(t3.interfaces[1], + t3.interfaces[0].methods[0].params[0].type.iface) + + # t1 has a resolved interface. t2 has an unresolved version + # and an interface that uses the unresolved interface as a + # param value in a method. + t1 = xpt.Typelib() + # add a resolved interface + m = xpt.Method("Bar", vp) + t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t2 = xpt.Typelib() + # add an unresolved interface + i = xpt.Interface("IFoo") + t2.interfaces.append(i) + # add an interface that uses the unresolved interface + # as a param value in a method. + vp = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + p = xpt.Param(xpt.InterfaceType(i)) + m = xpt.Method("IfaceParam", vp, params=[p]) + t2.interfaces.append(xpt.Interface("IParam", iid="11111111-1111-1111-1111-111111111111", + methods=[m], scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + self.assertEqual("IParam", t3.interfaces[0].name) + self.assertEqual("11111111-1111-1111-1111-111111111111", t3.interfaces[0].iid) + self.assertEqual("IFoo", t3.interfaces[1].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[1].iid) + self.assert_(t3.interfaces[1].resolved) + # Ensure that IRetval's method's param type has been updated. + self.assertEqual(1, len(t3.interfaces[0].methods)) + self.assert_(t3.interfaces[0].methods[0].params[0].type.iface.resolved) + self.assertEqual(t3.interfaces[1], + t3.interfaces[0].methods[0].params[0].type.iface) + + def test_mergeReplaceArrayTypeParams(self): + """ + Test that merging an interface correctly updates ArrayType + params whose element_type is an InterfaceType on methods + of other interfaces. + + """ + # t1 has an unresolved interface and an interface that uses the + # unresolved interface as a type in an ArrayType in a parameter + # of a method. t2 has a resolved version of the unresolved interface. + t1 = xpt.Typelib() + # add an unresolved interface + i = xpt.Interface("IFoo") + t1.interfaces.append(i) + # add an interface that uses the unresolved interface + # as a type in an ArrayType in a param value in a method. + vp = xpt.Param(xpt.SimpleType(xpt.Type.Tags.void)) + intp = xpt.Param(xpt.SimpleType(xpt.Type.Tags.int32)) + p = xpt.Param(xpt.ArrayType(xpt.InterfaceType(i), 1, 2)) + m = xpt.Method("ArrayIfaceParam", vp, params=[p, intp, intp]) + t1.interfaces.append(xpt.Interface("IParam", iid="11111111-1111-1111-1111-111111111111", + methods=[m], scriptable=True)) + t2 = xpt.Typelib() + # add a resolved interface + m = xpt.Method("Bar", vp) + t2.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff", + methods=[m], scriptable=True)) + t3 = xpt.xpt_link([t1, t2]) + + self.assertEqual(2, len(t3.interfaces)) + self.assertEqual("IParam", t3.interfaces[0].name) + self.assertEqual("11111111-1111-1111-1111-111111111111", t3.interfaces[0].iid) + self.assertEqual("IFoo", t3.interfaces[1].name) + self.assertEqual("11223344-5566-7788-9900-aabbccddeeff", t3.interfaces[1].iid) + self.assert_(t3.interfaces[1].resolved) + # Ensure that IRetval's method's param type has been updated. + self.assertEqual(1, len(t3.interfaces[0].methods)) + self.assert_(t3.interfaces[0].methods[0].params[0].type.element_type.iface.resolved) + self.assertEqual(t3.interfaces[1], + t3.interfaces[0].methods[0].params[0].type.element_type.iface) + +if __name__ == '__main__': + mozunit.main() 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]) diff --git a/xpcom/typelib/xpt/xpt_arena.cpp b/xpcom/typelib/xpt/xpt_arena.cpp new file mode 100644 index 000000000..21be3c00b --- /dev/null +++ b/xpcom/typelib/xpt/xpt_arena.cpp @@ -0,0 +1,196 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Quick arena hack for xpt. */ + +/* XXX This exists because we don't want to drag in NSPR. It *seemed* +* to make more sense to write a quick and dirty arena than to clone +* plarena (like js/src did). This is not optimal, but it works. +*/ + +#include "xpt_arena.h" +#include "mozilla/MemoryReporting.h" +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +/****************************************************/ + +/* Block header for each block in the arena */ +struct BLK_HDR +{ + BLK_HDR *next; +}; + +#define XPT_MIN_BLOCK_SIZE 32 + +/* XXX this is lame. Should clone the code to do this bitwise */ +#define ALIGN_RND(s,a) ((a)==1?(s):((((s)+(a)-1)/(a))*(a))) + +struct XPTSubArena +{ + BLK_HDR *first; + uint8_t *next; + size_t space; + size_t block_size; +}; + +struct XPTArena +{ + // We have one sub-arena with 8-byte alignment for most allocations, and + // one with 1-byte alignment for C string allocations. The latter sub-arena + // avoids significant amounts of unnecessary padding between C strings. + XPTSubArena subarena8; + XPTSubArena subarena1; +}; + +XPT_PUBLIC_API(XPTArena *) +XPT_NewArena(size_t block_size8, size_t block_size1) +{ + XPTArena *arena = static_cast<XPTArena*>(calloc(1, sizeof(XPTArena))); + if (arena) { + if (block_size8 < XPT_MIN_BLOCK_SIZE) + block_size8 = XPT_MIN_BLOCK_SIZE; + arena->subarena8.block_size = ALIGN_RND(block_size8, 8); + + if (block_size1 < XPT_MIN_BLOCK_SIZE) + block_size1 = XPT_MIN_BLOCK_SIZE; + arena->subarena1.block_size = block_size1; + } + return arena; +} + +static void +DestroySubArena(XPTSubArena *subarena) +{ + BLK_HDR* cur = subarena->first; + while (cur) { + BLK_HDR* next = cur->next; + free(cur); + cur = next; + } +} + +XPT_PUBLIC_API(void) +XPT_DestroyArena(XPTArena *arena) +{ + DestroySubArena(&arena->subarena8); + DestroySubArena(&arena->subarena1); + free(arena); +} + +/* +* Our alignment rule is that we always round up the size of each allocation +* so that the 'arena->next' pointer one will point to properly aligned space. +*/ + +XPT_PUBLIC_API(void *) +XPT_ArenaCalloc(XPTArena *arena, size_t size, size_t alignment) +{ + if (!size) + return NULL; + + if (!arena) { + XPT_ASSERT(0); + return NULL; + } + + XPTSubArena *subarena; + if (alignment == 8) { + subarena = &arena->subarena8; + } else if (alignment == 1) { + subarena = &arena->subarena1; + } else { + XPT_ASSERT(0); + return NULL; + } + + size_t bytes = ALIGN_RND(size, alignment); + + if (bytes > subarena->space) { + BLK_HDR* new_block; + size_t block_header_size = ALIGN_RND(sizeof(BLK_HDR), alignment); + size_t new_space = subarena->block_size; + + while (bytes > new_space - block_header_size) + new_space += subarena->block_size; + + new_block = + static_cast<BLK_HDR*>(calloc(new_space / alignment, alignment)); + if (!new_block) { + subarena->next = NULL; + subarena->space = 0; + return NULL; + } + + /* link block into the list of blocks for use when we destroy */ + new_block->next = subarena->first; + subarena->first = new_block; + + /* set info for current block */ + subarena->next = + reinterpret_cast<uint8_t*>(new_block) + block_header_size; + subarena->space = new_space - block_header_size; + +#ifdef DEBUG + /* mark block for corruption check */ + memset(subarena->next, 0xcd, subarena->space); +#endif + } + +#ifdef DEBUG + { + /* do corruption check */ + size_t i; + for (i = 0; i < bytes; ++i) { + XPT_ASSERT(subarena->next[i] == 0xcd); + } + /* we guarantee that the block will be filled with zeros */ + memset(subarena->next, 0, bytes); + } +#endif + + uint8_t* p = subarena->next; + subarena->next += bytes; + subarena->space -= bytes; + + return p; +} + +/***************************************************************************/ + +#ifdef DEBUG +XPT_PUBLIC_API(void) +XPT_AssertFailed(const char *s, const char *file, uint32_t lineno) +{ + fprintf(stderr, "Assertion failed: %s, file %s, line %d\n", + s, file, lineno); + abort(); +} +#endif + +static size_t +SizeOfSubArenaExcludingThis(XPTSubArena *subarena, MozMallocSizeOf mallocSizeOf) +{ + size_t n = 0; + + BLK_HDR* cur = subarena->first; + while (cur) { + BLK_HDR* next = cur->next; + n += mallocSizeOf(cur); + cur = next; + } + + return n; +} + +XPT_PUBLIC_API(size_t) +XPT_SizeOfArenaIncludingThis(XPTArena *arena, MozMallocSizeOf mallocSizeOf) +{ + size_t n = mallocSizeOf(arena); + n += SizeOfSubArenaExcludingThis(&arena->subarena8, mallocSizeOf); + n += SizeOfSubArenaExcludingThis(&arena->subarena1, mallocSizeOf); + return n; +} diff --git a/xpcom/typelib/xpt/xpt_arena.h b/xpcom/typelib/xpt/xpt_arena.h new file mode 100644 index 000000000..6ac146ffe --- /dev/null +++ b/xpcom/typelib/xpt/xpt_arena.h @@ -0,0 +1,70 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Simple arena allocator for xpt (which avoids using NSPR). + */ + +#ifndef __xpt_arena_h__ +#define __xpt_arena_h__ + +#include <stdlib.h> +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReporting.h" +#include <stdint.h> + + +/* + * The XPT library is statically linked: no functions are exported from + * shared libraries. + */ +#define XPT_PUBLIC_API(t) t +#define XPT_PUBLIC_DATA(t) t + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Simple Arena support. Use with caution! + */ + +typedef struct XPTArena XPTArena; + +XPT_PUBLIC_API(XPTArena *) +XPT_NewArena(size_t block_size8, size_t block_size1); + +XPT_PUBLIC_API(void) +XPT_DestroyArena(XPTArena *arena); + +XPT_PUBLIC_API(void *) +XPT_ArenaCalloc(XPTArena *arena, size_t size, size_t alignment); + +XPT_PUBLIC_API(size_t) +XPT_SizeOfArenaIncludingThis(XPTArena *arena, MozMallocSizeOf mallocSizeOf); + +/* --------------------------------------------------------- */ + +#define XPT_CALLOC8(_arena, _bytes) XPT_ArenaCalloc((_arena), (_bytes), 8) +#define XPT_CALLOC1(_arena, _bytes) XPT_ArenaCalloc((_arena), (_bytes), 1) +#define XPT_NEWZAP(_arena, _struct) ((_struct *) XPT_CALLOC8((_arena), sizeof(_struct))) + +/* --------------------------------------------------------- */ + +#ifdef DEBUG +XPT_PUBLIC_API(void) +XPT_AssertFailed(const char *s, const char *file, uint32_t lineno) + MOZ_PRETEND_NORETURN_FOR_STATIC_ANALYSIS; +#define XPT_ASSERT(_expr) \ + ((_expr)?((void)0):XPT_AssertFailed(# _expr, __FILE__, __LINE__)) +#else +#define XPT_ASSERT(_expr) ((void)0) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __xpt_arena_h__ */ diff --git a/xpcom/typelib/xpt/xpt_struct.cpp b/xpcom/typelib/xpt/xpt_struct.cpp new file mode 100644 index 000000000..c2e09abf0 --- /dev/null +++ b/xpcom/typelib/xpt/xpt_struct.cpp @@ -0,0 +1,432 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implementation of XDR routines for typelib structures. */ + +#include "xpt_xdr.h" +#include "xpt_struct.h" +#include <string.h> +#include <stdio.h> + +using mozilla::WrapNotNull; + +/***************************************************************************/ +/* Forward declarations. */ + +static bool +DoInterfaceDirectoryEntry(XPTArena *arena, NotNull<XPTCursor*> cursor, + XPTInterfaceDirectoryEntry *ide); + +static bool +DoConstDescriptor(XPTArena *arena, NotNull<XPTCursor*> cursor, + XPTConstDescriptor *cd, XPTInterfaceDescriptor *id); + +static bool +DoMethodDescriptor(XPTArena *arena, NotNull<XPTCursor*> cursor, + XPTMethodDescriptor *md, XPTInterfaceDescriptor *id); + +static bool +SkipAnnotation(NotNull<XPTCursor*> cursor, bool *isLast); + +static bool +DoInterfaceDescriptor(XPTArena *arena, NotNull<XPTCursor*> outer, + XPTInterfaceDescriptor **idp); + +static bool +DoTypeDescriptorPrefix(XPTArena *arena, NotNull<XPTCursor*> cursor, + XPTTypeDescriptorPrefix *tdp); + +static bool +DoTypeDescriptor(XPTArena *arena, NotNull<XPTCursor*> cursor, + XPTTypeDescriptor *td, XPTInterfaceDescriptor *id); + +static bool +DoParamDescriptor(XPTArena *arena, NotNull<XPTCursor*> cursor, + XPTParamDescriptor *pd, XPTInterfaceDescriptor *id); + +/***************************************************************************/ + +XPT_PUBLIC_API(bool) +XPT_DoHeader(XPTArena *arena, NotNull<XPTCursor*> cursor, XPTHeader **headerp) +{ + unsigned int i; + uint32_t file_length = 0; + uint32_t ide_offset; + + XPTHeader* header = XPT_NEWZAP(arena, XPTHeader); + if (!header) + return false; + *headerp = header; + + uint8_t magic[16]; + for (i = 0; i < sizeof(magic); i++) { + if (!XPT_Do8(cursor, &magic[i])) + return false; + } + + if (strncmp((const char*)magic, XPT_MAGIC, 16) != 0) { + /* Require that the header contain the proper magic */ + fprintf(stderr, + "libxpt: bad magic header in input file; " + "found '%s', expected '%s'\n", + magic, XPT_MAGIC_STRING); + return false; + } + + if (!XPT_Do8(cursor, &header->major_version) || + !XPT_Do8(cursor, &header->minor_version)) { + return false; + } + + if (header->major_version >= XPT_MAJOR_INCOMPATIBLE_VERSION) { + /* This file is newer than we are and set to an incompatible version + * number. We must set the header state thusly and return. + */ + header->num_interfaces = 0; + return true; + } + + if (!XPT_Do16(cursor, &header->num_interfaces) || + !XPT_Do32(cursor, &file_length) || + !XPT_Do32(cursor, &ide_offset)) { + return false; + } + + /* + * Make sure the file length reported in the header is the same size as + * as our buffer unless it is zero (not set) + */ + if (file_length != 0 && + cursor->state->pool_allocated < file_length) { + fputs("libxpt: File length in header does not match actual length. File may be corrupt\n", + stderr); + return false; + } + + uint32_t data_pool; + if (!XPT_Do32(cursor, &data_pool)) + return false; + + XPT_SetDataOffset(cursor->state, data_pool); + + if (header->num_interfaces) { + size_t n = header->num_interfaces * sizeof(XPTInterfaceDirectoryEntry); + header->interface_directory = + static_cast<XPTInterfaceDirectoryEntry*>(XPT_CALLOC8(arena, n)); + if (!header->interface_directory) + return false; + } + + /* + * Iterate through the annotations rather than recurring, to avoid blowing + * the stack on large xpt files. We don't actually store annotations, we + * just skip over them. + */ + bool isLast; + do { + if (!SkipAnnotation(cursor, &isLast)) + return false; + } while (!isLast); + + /* shouldn't be necessary now, but maybe later */ + XPT_SeekTo(cursor, ide_offset); + + for (i = 0; i < header->num_interfaces; i++) { + if (!DoInterfaceDirectoryEntry(arena, cursor, + &header->interface_directory[i])) + return false; + } + + return true; +} + +/* InterfaceDirectoryEntry records go in the header */ +bool +DoInterfaceDirectoryEntry(XPTArena *arena, NotNull<XPTCursor*> cursor, + XPTInterfaceDirectoryEntry *ide) +{ + char* dummy_name_space; + + /* write the IID in our cursor space */ + if (!XPT_DoIID(cursor, &(ide->iid)) || + + /* write the name string in the data pool, and the offset in our + cursor space */ + !XPT_DoCString(arena, cursor, &(ide->name)) || + + /* don't write the name_space string in the data pool, because we don't + * need it. Do write the offset in our cursor space */ + !XPT_DoCString(arena, cursor, &dummy_name_space, /* ignore = */ true) || + + /* do InterfaceDescriptors */ + !DoInterfaceDescriptor(arena, cursor, &ide->interface_descriptor)) { + return false; + } + + return true; +} + +static bool +InterfaceDescriptorAddTypes(XPTArena *arena, XPTInterfaceDescriptor *id, + uint16_t num) +{ + XPTTypeDescriptor *old = id->additional_types; + XPTTypeDescriptor *new_; + size_t old_size = id->num_additional_types * sizeof(XPTTypeDescriptor); + size_t new_size = (num * sizeof(XPTTypeDescriptor)) + old_size; + + /* XXX should grow in chunks to minimize alloc overhead */ + new_ = static_cast<XPTTypeDescriptor*>(XPT_CALLOC8(arena, new_size)); + if (!new_) + return false; + if (old) { + memcpy(new_, old, old_size); + } + id->additional_types = new_; + + if (num + uint16_t(id->num_additional_types) > 256) + return false; + + id->num_additional_types += num; + return true; +} + +bool +DoInterfaceDescriptor(XPTArena *arena, NotNull<XPTCursor*> outer, + XPTInterfaceDescriptor **idp) +{ + XPTInterfaceDescriptor *id; + XPTCursor curs; + NotNull<XPTCursor*> cursor = WrapNotNull(&curs); + uint32_t i, id_sz = 0; + + id = XPT_NEWZAP(arena, XPTInterfaceDescriptor); + if (!id) + return false; + *idp = id; + + if (!XPT_MakeCursor(outer->state, XPT_DATA, id_sz, cursor)) + return false; + + if (!XPT_Do32(outer, &cursor->offset)) + return false; + if (!cursor->offset) { + *idp = NULL; + return true; + } + if(!XPT_Do16(cursor, &id->parent_interface) || + !XPT_Do16(cursor, &id->num_methods)) { + return false; + } + + if (id->num_methods) { + size_t n = id->num_methods * sizeof(XPTMethodDescriptor); + id->method_descriptors = + static_cast<XPTMethodDescriptor*>(XPT_CALLOC8(arena, n)); + if (!id->method_descriptors) + return false; + } + + for (i = 0; i < id->num_methods; i++) { + if (!DoMethodDescriptor(arena, cursor, &id->method_descriptors[i], id)) + return false; + } + + if (!XPT_Do16(cursor, &id->num_constants)) { + return false; + } + + if (id->num_constants) { + size_t n = id->num_constants * sizeof(XPTConstDescriptor); + id->const_descriptors = + static_cast<XPTConstDescriptor*>(XPT_CALLOC8(arena, n)); + if (!id->const_descriptors) + return false; + } + + for (i = 0; i < id->num_constants; i++) { + if (!DoConstDescriptor(arena, cursor, &id->const_descriptors[i], id)) { + return false; + } + } + + if (!XPT_Do8(cursor, &id->flags)) { + return false; + } + + return true; +} + +bool +DoConstDescriptor(XPTArena *arena, NotNull<XPTCursor*> cursor, + XPTConstDescriptor *cd, XPTInterfaceDescriptor *id) +{ + bool ok = false; + + if (!XPT_DoCString(arena, cursor, &cd->name) || + !DoTypeDescriptor(arena, cursor, &cd->type, id)) { + + return false; + } + + switch(XPT_TDP_TAG(cd->type.prefix)) { + case TD_INT8: + ok = XPT_Do8(cursor, (uint8_t*) &cd->value.i8); + break; + case TD_INT16: + ok = XPT_Do16(cursor, (uint16_t*) &cd->value.i16); + break; + case TD_INT32: + ok = XPT_Do32(cursor, (uint32_t*) &cd->value.i32); + break; + case TD_INT64: + ok = XPT_Do64(cursor, &cd->value.i64); + break; + case TD_UINT8: + ok = XPT_Do8(cursor, &cd->value.ui8); + break; + case TD_UINT16: + ok = XPT_Do16(cursor, &cd->value.ui16); + break; + case TD_UINT32: + ok = XPT_Do32(cursor, &cd->value.ui32); + break; + case TD_UINT64: + ok = XPT_Do64(cursor, (int64_t *)&cd->value.ui64); + break; + case TD_CHAR: + ok = XPT_Do8(cursor, (uint8_t*) &cd->value.ch); + break; + case TD_WCHAR: + ok = XPT_Do16(cursor, &cd->value.wch); + break; + /* fall-through */ + default: + fprintf(stderr, "illegal type!\n"); + break; + } + + return ok; + +} + +bool +DoMethodDescriptor(XPTArena *arena, NotNull<XPTCursor*> cursor, + XPTMethodDescriptor *md, XPTInterfaceDescriptor *id) +{ + int i; + + if (!XPT_Do8(cursor, &md->flags) || + !XPT_DoCString(arena, cursor, &md->name) || + !XPT_Do8(cursor, &md->num_args)) + return false; + + if (md->num_args) { + size_t n = md->num_args * sizeof(XPTParamDescriptor); + md->params = static_cast<XPTParamDescriptor*>(XPT_CALLOC8(arena, n)); + if (!md->params) + return false; + } + + for(i = 0; i < md->num_args; i++) { + if (!DoParamDescriptor(arena, cursor, &md->params[i], id)) + return false; + } + + if (!DoParamDescriptor(arena, cursor, &md->result, id)) + return false; + + return true; +} + +bool +DoParamDescriptor(XPTArena *arena, NotNull<XPTCursor*> cursor, + XPTParamDescriptor *pd, XPTInterfaceDescriptor *id) +{ + if (!XPT_Do8(cursor, &pd->flags) || + !DoTypeDescriptor(arena, cursor, &pd->type, id)) + return false; + + return true; +} + +bool +DoTypeDescriptorPrefix(XPTArena *arena, NotNull<XPTCursor*> cursor, + XPTTypeDescriptorPrefix *tdp) +{ + return XPT_Do8(cursor, &tdp->flags); +} + +bool +DoTypeDescriptor(XPTArena *arena, NotNull<XPTCursor*> cursor, + XPTTypeDescriptor *td, XPTInterfaceDescriptor *id) +{ + if (!DoTypeDescriptorPrefix(arena, cursor, &td->prefix)) { + return false; + } + + switch (XPT_TDP_TAG(td->prefix)) { + case TD_INTERFACE_TYPE: + uint16_t iface; + if (!XPT_Do16(cursor, &iface)) + return false; + td->u.iface.iface_hi8 = (iface >> 8) & 0xff; + td->u.iface.iface_lo8 = iface & 0xff; + break; + case TD_INTERFACE_IS_TYPE: + if (!XPT_Do8(cursor, &td->u.interface_is.argnum)) + return false; + break; + case TD_ARRAY: { + // argnum2 appears in the on-disk format but it isn't used. + uint8_t argnum2 = 0; + if (!XPT_Do8(cursor, &td->u.array.argnum) || + !XPT_Do8(cursor, &argnum2)) + return false; + + if (!InterfaceDescriptorAddTypes(arena, id, 1)) + return false; + td->u.array.additional_type = id->num_additional_types - 1; + + if (!DoTypeDescriptor(arena, cursor, + &id->additional_types[td->u.array.additional_type], + id)) + return false; + break; + } + case TD_PSTRING_SIZE_IS: + case TD_PWSTRING_SIZE_IS: { + // argnum2 appears in the on-disk format but it isn't used. + uint8_t argnum2 = 0; + if (!XPT_Do8(cursor, &td->u.pstring_is.argnum) || + !XPT_Do8(cursor, &argnum2)) + return false; + break; + } + default: + /* nothing special */ + break; + } + return true; +} + +bool +SkipAnnotation(NotNull<XPTCursor*> cursor, bool *isLast) +{ + uint8_t flags; + if (!XPT_Do8(cursor, &flags)) + return false; + + *isLast = XPT_ANN_IS_LAST(flags); + + if (XPT_ANN_IS_PRIVATE(flags)) { + if (!XPT_SkipStringInline(cursor) || + !XPT_SkipStringInline(cursor)) + return false; + } + + return true; +} + diff --git a/xpcom/typelib/xpt/xpt_struct.h b/xpcom/typelib/xpt/xpt_struct.h new file mode 100644 index 000000000..b4da4a3fd --- /dev/null +++ b/xpcom/typelib/xpt/xpt_struct.h @@ -0,0 +1,366 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Structures matching the in-memory representation of typelib structures. + * http://www.mozilla.org/scriptable/typelib_file.html + */ + +#ifndef __xpt_struct_h__ +#define __xpt_struct_h__ + +#include "xpt_arena.h" +#include <stdint.h> + +extern "C" { + +/* + * Originally, I was going to have structures that exactly matched the on-disk + * representation, but that proved difficult: different compilers can pack + * their structs differently, and that makes overlaying them atop a + * read-from-disk byte buffer troublesome. So now I just have some structures + * that are used in memory, and we're going to write a nice XDR library to + * write them to disk and stuff. It is pure joy. -- shaver + */ + +/* Structures for the typelib components */ + +typedef struct XPTHeader XPTHeader; +typedef struct XPTInterfaceDirectoryEntry XPTInterfaceDirectoryEntry; +typedef struct XPTInterfaceDescriptor XPTInterfaceDescriptor; +typedef struct XPTConstDescriptor XPTConstDescriptor; +typedef struct XPTMethodDescriptor XPTMethodDescriptor; +typedef struct XPTParamDescriptor XPTParamDescriptor; +typedef struct XPTTypeDescriptor XPTTypeDescriptor; +typedef struct XPTTypeDescriptorPrefix XPTTypeDescriptorPrefix; + +#ifndef nsID_h__ +/* + * We can't include nsID.h, because it's full of C++ goop and we're not doing + * C++ here, so we define our own minimal struct. We protect against multiple + * definitions of this struct, though, and use the same field naming. + */ +struct nsID { + uint32_t m0; + uint16_t m1; + uint16_t m2; + uint8_t m3[8]; +}; + +typedef struct nsID nsID; +#endif + +/* + * Every XPCOM typelib file begins with a header. + */ +struct XPTHeader { + // Some of these fields exists in the on-disk format but don't need to be + // stored in memory (other than very briefly, which can be done with local + // variables). + + //uint8_t magic[16]; + uint8_t major_version; + uint8_t minor_version; + uint16_t num_interfaces; + //uint32_t file_length; + XPTInterfaceDirectoryEntry *interface_directory; + //uint32_t data_pool; +}; + +#define XPT_MAGIC "XPCOM\nTypeLib\r\n\032" +/* For error messages. */ +#define XPT_MAGIC_STRING "XPCOM\\nTypeLib\\r\\n\\032" +#define XPT_MAJOR_VERSION 0x01 +#define XPT_MINOR_VERSION 0x02 + +/* Any file with a major version number of XPT_MAJOR_INCOMPATIBLE_VERSION + * or higher is to be considered incompatible by this version of xpt and + * we will refuse to read it. We will return a header with magic, major and + * minor versions set from the file. num_interfaces will be set to zero to + * confirm our inability to read the file; i.e. even if some client of this + * library gets out of sync with us regarding the agreed upon value for + * XPT_MAJOR_INCOMPATIBLE_VERSION, anytime num_interfaces is zero we *know* + * that this library refused to read the file due to version incompatibility. + */ +#define XPT_MAJOR_INCOMPATIBLE_VERSION 0x02 + +/* + * A contiguous array of fixed-size InterfaceDirectoryEntry records begins at + * the byte offset identified by the interface_directory field in the file + * header. The array is used to quickly locate an interface description + * using its IID. No interface should appear more than once in the array. + */ +struct XPTInterfaceDirectoryEntry { + nsID iid; + char *name; + + // This field exists in the on-disk format. But it isn't used so we don't + // allocate space for it in memory. + //char *name_space; + + XPTInterfaceDescriptor *interface_descriptor; +}; + +/* + * An InterfaceDescriptor describes a single XPCOM interface, including all of + * its methods. + */ +struct XPTInterfaceDescriptor { + /* This field ordering minimizes the size of this struct. + * The fields are serialized on disk in a different order. + * See DoInterfaceDescriptor(). + */ + XPTMethodDescriptor *method_descriptors; + XPTConstDescriptor *const_descriptors; + XPTTypeDescriptor *additional_types; + uint16_t parent_interface; + uint16_t num_methods; + uint16_t num_constants; + uint8_t flags; + + /* additional_types are used for arrays where we may need multiple + * XPTTypeDescriptors for a single XPTMethodDescriptor. Since we still + * want to have a simple array of XPTMethodDescriptor (each with a single + * embedded XPTTypeDescriptor), a XPTTypeDescriptor can have a reference + * to an 'additional_type'. That reference is an index in this + * "additional_types" array. So a given XPTMethodDescriptor might have + * a whole chain of these XPTTypeDescriptors to represent, say, a multi + * dimensional array. + * + * Note that in the typelib file these additional types are stored 'inline' + * in the MethodDescriptor. But, in the typelib MethodDescriptors can be + * of varying sizes, where in XPT's in memory mapping of the data we want + * them to be of fixed size. This additional_types scheme is here to allow + * for that. + */ + uint8_t num_additional_types; +}; + +#define XPT_ID_SCRIPTABLE 0x80 +#define XPT_ID_FUNCTION 0x40 +#define XPT_ID_BUILTINCLASS 0x20 +#define XPT_ID_MAIN_PROCESS_SCRIPTABLE_ONLY 0x10 +#define XPT_ID_FLAGMASK 0xf0 + +#define XPT_ID_IS_SCRIPTABLE(flags) (!!(flags & XPT_ID_SCRIPTABLE)) +#define XPT_ID_IS_FUNCTION(flags) (!!(flags & XPT_ID_FUNCTION)) +#define XPT_ID_IS_BUILTINCLASS(flags) (!!(flags & XPT_ID_BUILTINCLASS)) +#define XPT_ID_IS_MAIN_PROCESS_SCRIPTABLE_ONLY(flags) (!!(flags & XPT_ID_MAIN_PROCESS_SCRIPTABLE_ONLY)) + +/* + * A TypeDescriptor is a variable-size record used to identify the type of a + * method argument or return value. + * + * There are three types of TypeDescriptors: + * + * SimpleTypeDescriptor + * InterfaceTypeDescriptor + * InterfaceIsTypeDescriptor + * + * The tag field in the prefix indicates which of the variant TypeDescriptor + * records is being used, and hence the way any remaining fields should be + * parsed. Values from 0 to 17 refer to SimpleTypeDescriptors. The value 18 + * designates an InterfaceTypeDescriptor, while 19 represents an + * InterfaceIsTypeDescriptor. + */ + +/* why bother with a struct? - other code relies on this being a struct */ +struct XPTTypeDescriptorPrefix { + uint8_t flags; +}; + +/* flag bits */ + +#define XPT_TDP_FLAGMASK 0xe0 +#define XPT_TDP_TAGMASK (~XPT_TDP_FLAGMASK) +#define XPT_TDP_TAG(tdp) ((tdp).flags & XPT_TDP_TAGMASK) + +/* + * The following enum maps mnemonic names to the different numeric values + * of XPTTypeDescriptor->tag. + */ +enum XPTTypeDescriptorTags { + TD_INT8 = 0, + TD_INT16 = 1, + TD_INT32 = 2, + TD_INT64 = 3, + TD_UINT8 = 4, + TD_UINT16 = 5, + TD_UINT32 = 6, + TD_UINT64 = 7, + TD_FLOAT = 8, + TD_DOUBLE = 9, + TD_BOOL = 10, + TD_CHAR = 11, + TD_WCHAR = 12, + TD_VOID = 13, + TD_PNSIID = 14, + TD_DOMSTRING = 15, + TD_PSTRING = 16, + TD_PWSTRING = 17, + TD_INTERFACE_TYPE = 18, + TD_INTERFACE_IS_TYPE = 19, + TD_ARRAY = 20, + TD_PSTRING_SIZE_IS = 21, + TD_PWSTRING_SIZE_IS = 22, + TD_UTF8STRING = 23, + TD_CSTRING = 24, + TD_ASTRING = 25, + TD_JSVAL = 26 +}; + +struct XPTTypeDescriptor { + XPTTypeDescriptorPrefix prefix; + + // The memory layout here doesn't exactly match (for the appropriate types) + // the on-disk format. This is to save memory. + union { + // Used for TD_INTERFACE_IS_TYPE. + struct { + uint8_t argnum; + } interface_is; + + // Used for TD_PSTRING_SIZE_IS, TD_PWSTRING_SIZE_IS. + struct { + uint8_t argnum; + //uint8_t argnum2; // Present on disk, omitted here. + } pstring_is; + + // Used for TD_ARRAY. + struct { + uint8_t argnum; + //uint8_t argnum2; // Present on disk, omitted here. + uint8_t additional_type; // uint16_t on disk, uint8_t here; + // in practice it never exceeds 20. + } array; + + // Used for TD_INTERFACE_TYPE. + struct { + // We store the 16-bit iface value as two 8-bit values in order to + // avoid 16-bit alignment requirements for XPTTypeDescriptor, which + // reduces its size and also the size of XPTParamDescriptor. + uint8_t iface_hi8; + uint8_t iface_lo8; + } iface; + } u; +}; + +/* + * A ConstDescriptor is a variable-size record that records the name and + * value of a scoped interface constant. + * + * The types of the method parameter are restricted to the following subset + * of TypeDescriptors: + * + * int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, + * int64_t, uint64_t, wchar_t, char + * + * The type (and thus the size) of the value record is determined by the + * contents of the associated TypeDescriptor record. For instance, if type + * corresponds to int16_t, then value is a two-byte record consisting of a + * 16-bit signed integer. + */ +union XPTConstValue { + int8_t i8; + uint8_t ui8; + int16_t i16; + uint16_t ui16; + int32_t i32; + uint32_t ui32; + int64_t i64; + uint64_t ui64; + char ch; + uint16_t wch; +}; /* varies according to type */ + +struct XPTConstDescriptor { + char *name; + XPTTypeDescriptor type; + union XPTConstValue value; +}; + +/* + * A ParamDescriptor is a variable-size record used to describe either a + * single argument to a method or a method's result. + */ +struct XPTParamDescriptor { + uint8_t flags; + XPTTypeDescriptor type; +}; + +/* flag bits */ +#define XPT_PD_IN 0x80 +#define XPT_PD_OUT 0x40 +#define XPT_PD_RETVAL 0x20 +#define XPT_PD_SHARED 0x10 +#define XPT_PD_DIPPER 0x08 +#define XPT_PD_OPTIONAL 0x04 +#define XPT_PD_FLAGMASK 0xfc + +#define XPT_PD_IS_IN(flags) (flags & XPT_PD_IN) +#define XPT_PD_IS_OUT(flags) (flags & XPT_PD_OUT) +#define XPT_PD_IS_RETVAL(flags) (flags & XPT_PD_RETVAL) +#define XPT_PD_IS_SHARED(flags) (flags & XPT_PD_SHARED) +#define XPT_PD_IS_DIPPER(flags) (flags & XPT_PD_DIPPER) +#define XPT_PD_IS_OPTIONAL(flags) (flags & XPT_PD_OPTIONAL) + +/* + * A MethodDescriptor is a variable-size record used to describe a single + * interface method. + */ +struct XPTMethodDescriptor { + char *name; + XPTParamDescriptor *params; + XPTParamDescriptor result; + uint8_t flags; + uint8_t num_args; +}; + +/* flag bits */ +#define XPT_MD_GETTER 0x80 +#define XPT_MD_SETTER 0x40 +#define XPT_MD_NOTXPCOM 0x20 +#define XPT_MD_HIDDEN 0x08 +#define XPT_MD_OPT_ARGC 0x04 +#define XPT_MD_CONTEXT 0x02 +#define XPT_MD_FLAGMASK 0xfe + +#define XPT_MD_IS_GETTER(flags) (flags & XPT_MD_GETTER) +#define XPT_MD_IS_SETTER(flags) (flags & XPT_MD_SETTER) +#define XPT_MD_IS_NOTXPCOM(flags) (flags & XPT_MD_NOTXPCOM) +#define XPT_MD_IS_HIDDEN(flags) (flags & XPT_MD_HIDDEN) +#define XPT_MD_WANTS_OPT_ARGC(flags) (flags & XPT_MD_OPT_ARGC) +#define XPT_MD_WANTS_CONTEXT(flags) (flags & XPT_MD_CONTEXT) + +/* + * Annotation records are variable-size records used to store secondary + * information about the typelib, e.g. such as the name of the tool that + * generated the typelib file, the date it was generated, etc. The + * information is stored with very loose format requirements so as to + * allow virtually any private data to be stored in the typelib. + * + * There are two types of Annotations: + * + * EmptyAnnotation + * PrivateAnnotation + * + * The tag field of the prefix discriminates among the variant record + * types for Annotation's. If the tag is 0, this record is an + * EmptyAnnotation. EmptyAnnotation's are ignored - they're only used to + * indicate an array of Annotation's that's completely empty. If the tag + * is 1, the record is a PrivateAnnotation. + * + * We don't actually store annotations; we just skip over them if they are + * present. + */ + +#define XPT_ANN_LAST 0x80 +#define XPT_ANN_IS_LAST(flags) (flags & XPT_ANN_LAST) +#define XPT_ANN_PRIVATE 0x40 +#define XPT_ANN_IS_PRIVATE(flags) (flags & XPT_ANN_PRIVATE) + +} + +#endif /* __xpt_struct_h__ */ diff --git a/xpcom/typelib/xpt/xpt_xdr.cpp b/xpcom/typelib/xpt/xpt_xdr.cpp new file mode 100644 index 000000000..04b90422a --- /dev/null +++ b/xpcom/typelib/xpt/xpt_xdr.cpp @@ -0,0 +1,227 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Implementation of XDR primitives. */ + +#include "xpt_xdr.h" +#include "nscore.h" +#include <string.h> /* strchr */ +#include "mozilla/EndianUtils.h" + +#define CURS_POOL_OFFSET_RAW(cursor) \ + ((cursor)->pool == XPT_HEADER \ + ? (cursor)->offset \ + : (XPT_ASSERT((cursor)->state->data_offset), \ + (cursor)->offset + (cursor)->state->data_offset)) + +#define CURS_POOL_OFFSET(cursor) \ + (CURS_POOL_OFFSET_RAW(cursor) - 1) + +/* can be used as lvalue */ +#define CURS_POINT(cursor) \ + ((cursor)->state->pool_data[CURS_POOL_OFFSET(cursor)]) + +static bool +CHECK_COUNT(NotNull<XPTCursor*> cursor, uint32_t space) +{ + // Fail if we're in the data area and about to exceed the allocation. + // XXX Also fail if we're in the data area and !state->data_offset + if (cursor->pool == XPT_DATA && + (CURS_POOL_OFFSET(cursor) + space > (cursor)->state->pool_allocated)) { + XPT_ASSERT(0); + fprintf(stderr, "FATAL: no room for %d in cursor\n", space); + return false; + } + + return true; +} + +XPT_PUBLIC_API(void) +XPT_InitXDRState(XPTState* state, char *data, uint32_t len) +{ + state->next_cursor[0] = state->next_cursor[1] = 1; + state->pool_data = data; + state->pool_allocated = len; +} + +/* All offsets are 1-based */ +XPT_PUBLIC_API(void) +XPT_SetDataOffset(XPTState *state, uint32_t data_offset) +{ + state->data_offset = data_offset; +} + +XPT_PUBLIC_API(bool) +XPT_MakeCursor(XPTState *state, XPTPool pool, uint32_t len, + NotNull<XPTCursor*> cursor) +{ + cursor->state = state; + cursor->pool = pool; + cursor->bits = 0; + cursor->offset = state->next_cursor[pool]; + + if (!(CHECK_COUNT(cursor, len))) + return false; + + /* this check should be in CHECK_CURSOR */ + if (pool == XPT_DATA && !state->data_offset) { + fprintf(stderr, "no data offset for XPT_DATA cursor!\n"); + return false; + } + + state->next_cursor[pool] += len; + + return true; +} + +XPT_PUBLIC_API(bool) +XPT_SeekTo(NotNull<XPTCursor*> cursor, uint32_t offset) +{ + /* XXX do some real checking and update len and stuff */ + cursor->offset = offset; + return true; +} + +XPT_PUBLIC_API(bool) +XPT_SkipStringInline(NotNull<XPTCursor*> cursor) +{ + uint16_t length; + if (!XPT_Do16(cursor, &length)) + return false; + + uint8_t byte; + for (uint16_t i = 0; i < length; i++) + if (!XPT_Do8(cursor, &byte)) + return false; + + return true; +} + +XPT_PUBLIC_API(bool) +XPT_DoCString(XPTArena *arena, NotNull<XPTCursor*> cursor, char **identp, + bool ignore) +{ + uint32_t offset = 0; + if (!XPT_Do32(cursor, &offset)) + return false; + + if (!offset) { + *identp = NULL; + return true; + } + + XPTCursor my_cursor; + my_cursor.pool = XPT_DATA; + my_cursor.offset = offset; + my_cursor.state = cursor->state; + char* start = &CURS_POINT(&my_cursor); + + char* end = strchr(start, 0); /* find the end of the string */ + if (!end) { + fprintf(stderr, "didn't find end of string on decode!\n"); + return false; + } + int len = end - start; + XPT_ASSERT(len > 0); + + if (!ignore) { + char *ident = (char*)XPT_CALLOC1(arena, len + 1u); + if (!ident) + return false; + + memcpy(ident, start, (size_t)len); + ident[len] = 0; + *identp = ident; + } + + return true; +} + +/* + * IIDs are written in struct order, in the usual big-endian way. From the + * typelib file spec: + * + * "For example, this IID: + * {00112233-4455-6677-8899-aabbccddeeff} + * is converted to the 128-bit value + * 0x00112233445566778899aabbccddeeff + * Note that the byte storage order corresponds to the layout of the nsIID + * C-struct on a big-endian architecture." + * + * (http://www.mozilla.org/scriptable/typelib_file.html#iid) + */ +XPT_PUBLIC_API(bool) +XPT_DoIID(NotNull<XPTCursor*> cursor, nsID *iidp) +{ + int i; + + if (!XPT_Do32(cursor, &iidp->m0) || + !XPT_Do16(cursor, &iidp->m1) || + !XPT_Do16(cursor, &iidp->m2)) + return false; + + for (i = 0; i < 8; i++) + if (!XPT_Do8(cursor, (uint8_t *)&iidp->m3[i])) + return false; + + return true; +} + +// MSVC apparently cannot handle functions as template parameters very well, +// so we need to use a macro approach here. + +#define XPT_DOINT(T, func, valuep) \ + do { \ + const size_t sz = sizeof(T); \ + \ + if (!CHECK_COUNT(cursor, sz)) { \ + return false; \ + } \ + \ + *valuep = func(&CURS_POINT(cursor)); \ + cursor->offset += sz; \ + return true; \ + } while(0) + +XPT_PUBLIC_API(bool) +XPT_Do64(NotNull<XPTCursor*> cursor, int64_t *u64p) +{ + XPT_DOINT(int64_t, mozilla::BigEndian::readInt64, u64p); +} + +/* + * When we're handling 32- or 16-bit quantities, we handle a byte at a time to + * avoid alignment issues. Someone could come and optimize this to detect + * well-aligned cases and do a single store, if they cared. I might care + * later. + */ +XPT_PUBLIC_API(bool) +XPT_Do32(NotNull<XPTCursor*> cursor, uint32_t *u32p) +{ + XPT_DOINT(uint32_t, mozilla::BigEndian::readUint32, u32p); +} + +XPT_PUBLIC_API(bool) +XPT_Do16(NotNull<XPTCursor*> cursor, uint16_t *u16p) +{ + XPT_DOINT(uint16_t, mozilla::BigEndian::readUint16, u16p); +} + +#undef XPT_DOINT + +XPT_PUBLIC_API(bool) +XPT_Do8(NotNull<XPTCursor*> cursor, uint8_t *u8p) +{ + if (!CHECK_COUNT(cursor, 1)) + return false; + + *u8p = CURS_POINT(cursor); + + cursor->offset++; + + return true; +} + + diff --git a/xpcom/typelib/xpt/xpt_xdr.h b/xpcom/typelib/xpt/xpt_xdr.h new file mode 100644 index 000000000..7bf23aa40 --- /dev/null +++ b/xpcom/typelib/xpt/xpt_xdr.h @@ -0,0 +1,86 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Basic APIs for streaming typelib structures from disk. + */ + +#ifndef __xpt_xdr_h__ +#define __xpt_xdr_h__ + +#include "xpt_struct.h" +#include "mozilla/NotNull.h" + +using mozilla::NotNull; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct XPTState XPTState; +typedef struct XPTCursor XPTCursor; + +extern XPT_PUBLIC_API(bool) +XPT_SkipStringInline(NotNull<XPTCursor*> cursor); + +extern XPT_PUBLIC_API(bool) +XPT_DoCString(XPTArena *arena, NotNull<XPTCursor*> cursor, char **strp, + bool ignore = false); + +extern XPT_PUBLIC_API(bool) +XPT_DoIID(NotNull<XPTCursor*> cursor, nsID *iidp); + +extern XPT_PUBLIC_API(bool) +XPT_Do64(NotNull<XPTCursor*> cursor, int64_t *u64p); + +extern XPT_PUBLIC_API(bool) +XPT_Do32(NotNull<XPTCursor*> cursor, uint32_t *u32p); + +extern XPT_PUBLIC_API(bool) +XPT_Do16(NotNull<XPTCursor*> cursor, uint16_t *u16p); + +extern XPT_PUBLIC_API(bool) +XPT_Do8(NotNull<XPTCursor*> cursor, uint8_t *u8p); + +extern XPT_PUBLIC_API(bool) +XPT_DoHeader(XPTArena *arena, NotNull<XPTCursor*> cursor, XPTHeader **headerp); + +typedef enum { + XPT_HEADER = 0, + XPT_DATA = 1 +} XPTPool; + +struct XPTState { + uint32_t data_offset; + uint32_t next_cursor[2]; + char *pool_data; + uint32_t pool_allocated; +}; + +struct XPTCursor { + XPTState *state; + XPTPool pool; + uint32_t offset; + uint8_t bits; +}; + +extern XPT_PUBLIC_API(void) +XPT_InitXDRState(XPTState* state, char* data, uint32_t len); + +extern XPT_PUBLIC_API(bool) +XPT_MakeCursor(XPTState *state, XPTPool pool, uint32_t len, + NotNull<XPTCursor*> cursor); + +extern XPT_PUBLIC_API(bool) +XPT_SeekTo(NotNull<XPTCursor*> cursor, uint32_t offset); + +extern XPT_PUBLIC_API(void) +XPT_SetDataOffset(XPTState *state, uint32_t data_offset); + +#ifdef __cplusplus +} +#endif + +#endif /* __xpt_xdr_h__ */ |