1#!/usr/bin/env python 2# jsonlink.py - Merge JSON typelib files into a .cpp file 3# 4# This Source Code Form is subject to the terms of the Mozilla Public 5# License, v. 2.0. If a copy of the MPL was not distributed with this 6# file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 8import json 9from perfecthash import PerfectHash 10from collections import OrderedDict 11import buildconfig 12 13# Pick a nice power-of-two size for our intermediate PHF tables. 14PHFSIZE = 512 15 16 17def indented(s): 18 return s.replace("\n", "\n ") 19 20 21def cpp(v): 22 if type(v) == bool: 23 return "true" if v else "false" 24 return str(v) 25 26 27def mkstruct(*fields): 28 def mk(comment, **vals): 29 assert len(fields) == len(vals) 30 r = "{ // " + comment 31 r += indented(",".join("\n/* %s */ %s" % (k, cpp(vals[k])) for k in fields)) 32 r += "\n}" 33 return r 34 35 return mk 36 37 38########################################################## 39# Ensure these fields are in the same order as xptinfo.h # 40########################################################## 41nsXPTInterfaceInfo = mkstruct( 42 "mIID", 43 "mName", 44 "mParent", 45 "mBuiltinClass", 46 "mMainProcessScriptableOnly", 47 "mMethods", 48 "mConsts", 49 "mFunction", 50 "mNumMethods", 51 "mNumConsts", 52) 53 54########################################################## 55# Ensure these fields are in the same order as xptinfo.h # 56########################################################## 57nsXPTType = mkstruct( 58 "mTag", 59 "mInParam", 60 "mOutParam", 61 "mOptionalParam", 62 "mData1", 63 "mData2", 64) 65 66########################################################## 67# Ensure these fields are in the same order as xptinfo.h # 68########################################################## 69nsXPTParamInfo = mkstruct( 70 "mType", 71) 72 73########################################################## 74# Ensure these fields are in the same order as xptinfo.h # 75########################################################## 76nsXPTMethodInfo = mkstruct( 77 "mName", 78 "mParams", 79 "mNumParams", 80 "mGetter", 81 "mSetter", 82 "mReflectable", 83 "mOptArgc", 84 "mContext", 85 "mHasRetval", 86 "mIsSymbol", 87) 88 89########################################################## 90# Ensure these fields are in the same order as xptinfo.h # 91########################################################## 92nsXPTDOMObjectInfo = mkstruct( 93 "mUnwrap", 94 "mWrap", 95 "mCleanup", 96) 97 98########################################################## 99# Ensure these fields are in the same order as xptinfo.h # 100########################################################## 101nsXPTConstantInfo = mkstruct( 102 "mName", 103 "mSigned", 104 "mValue", 105) 106 107 108# Helper functions for dealing with IIDs. 109# 110# Unfortunately, the way we represent IIDs in memory depends on the endianness 111# of the target architecture. We store an nsIID as a 16-byte, 4-tuple of: 112# 113# (uint32_t, uint16_t, uint16_t, [uint8_t; 8]) 114# 115# Unfortunately, this means that when we hash the bytes of the nsIID on a 116# little-endian target system, we need to hash them in little-endian order. 117# These functions let us split the input hexadecimal string into components, 118# encoding each as a little-endian value, and producing an accurate bytearray. 119# 120# It would be nice to have a consistent representation of IIDs in memory such 121# that we don't have to do these gymnastics to get an accurate hash. 122 123 124def split_at_idxs(s, lengths): 125 idx = 0 126 for length in lengths: 127 yield s[idx : idx + length] 128 idx += length 129 assert idx == len(s) 130 131 132def split_iid(iid): # Get the individual components out of an IID string. 133 iid = iid.replace("-", "") # Strip any '-' delimiters 134 return tuple(split_at_idxs(iid, (8, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2))) 135 136 137def iid_bytes(iid): # Get the byte representation of the IID for hashing. 138 bs = bytearray() 139 for num in split_iid(iid): 140 b = bytearray.fromhex(num) 141 # Match endianness of the target platform for each component 142 if buildconfig.substs["TARGET_ENDIANNESS"] == "little": 143 b.reverse() 144 bs += b 145 return bs 146 147 148# Split a 16-bit integer into its high and low 8 bits 149def splitint(i): 150 assert i < 2 ** 16 151 return (i >> 8, i & 0xFF) 152 153 154# Occasionally in xpconnect, we need to fabricate types to pass into the 155# conversion methods. In some cases, these types need to be arrays, which hold 156# indicies into the extra types array. 157# 158# These are some types which should have known indexes into the extra types 159# array. 160utility_types = [ 161 {"tag": "TD_INT8"}, 162 {"tag": "TD_UINT8"}, 163 {"tag": "TD_INT16"}, 164 {"tag": "TD_UINT16"}, 165 {"tag": "TD_INT32"}, 166 {"tag": "TD_UINT32"}, 167 {"tag": "TD_INT64"}, 168 {"tag": "TD_UINT64"}, 169 {"tag": "TD_FLOAT"}, 170 {"tag": "TD_DOUBLE"}, 171 {"tag": "TD_BOOL"}, 172 {"tag": "TD_CHAR"}, 173 {"tag": "TD_WCHAR"}, 174 {"tag": "TD_NSIDPTR"}, 175 {"tag": "TD_PSTRING"}, 176 {"tag": "TD_PWSTRING"}, 177 {"tag": "TD_INTERFACE_IS_TYPE", "iid_is": 0}, 178] 179 180 181# Core of the code generator. Takes a list of raw JSON XPT interfaces, and 182# writes out a file containing the necessary static declarations into fd. 183def link_to_cpp(interfaces, fd, header_fd): 184 # Perfect Hash from IID to interface. 185 iid_phf = PerfectHash(interfaces, PHFSIZE, key=lambda i: iid_bytes(i["uuid"])) 186 for idx, iface in enumerate(iid_phf.entries): 187 iface["idx"] = idx # Store the index in iid_phf of the entry. 188 189 # Perfect Hash from name to iid_phf index. 190 name_phf = PerfectHash(interfaces, PHFSIZE, key=lambda i: i["name"].encode("ascii")) 191 192 def interface_idx(name): 193 entry = name and name_phf.get_entry(name.encode("ascii")) 194 if entry: 195 return entry["idx"] + 1 # 1-based, use 0 as a sentinel. 196 return 0 197 198 # NOTE: State used while linking. This is done with closures rather than a 199 # class due to how this file's code evolved. 200 includes = set() 201 types = [] 202 type_cache = {} 203 params = [] 204 param_cache = {} 205 methods = [] 206 max_params = 0 207 method_with_max_params = None 208 consts = [] 209 domobjects = [] 210 domobject_cache = {} 211 strings = OrderedDict() 212 213 def lower_uuid(uuid): 214 return ( 215 "{0x%s, 0x%s, 0x%s, {0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s}}" 216 % split_iid(uuid) 217 ) 218 219 def lower_domobject(do): 220 assert do["tag"] == "TD_DOMOBJECT" 221 222 idx = domobject_cache.get(do["name"]) 223 if idx is None: 224 idx = domobject_cache[do["name"]] = len(domobjects) 225 226 includes.add(do["headerFile"]) 227 domobjects.append( 228 nsXPTDOMObjectInfo( 229 "%d = %s" % (idx, do["name"]), 230 # These methods are defined at the top of the generated file. 231 mUnwrap="UnwrapDOMObject<mozilla::dom::prototypes::id::%s, %s>" 232 % (do["name"], do["native"]), 233 mWrap="WrapDOMObject<%s>" % do["native"], 234 mCleanup="CleanupDOMObject<%s>" % do["native"], 235 ) 236 ) 237 238 return idx 239 240 def lower_string(s): 241 if s in strings: 242 # We've already seen this string. 243 return strings[s] 244 elif len(strings): 245 # Get the last string we inserted (should be O(1) on OrderedDict). 246 last_s = next(reversed(strings)) 247 strings[s] = strings[last_s] + len(last_s) + 1 248 else: 249 strings[s] = 0 250 return strings[s] 251 252 def lower_symbol(s): 253 return "uint32_t(JS::SymbolCode::%s)" % s 254 255 def lower_extra_type(type): 256 key = describe_type(type) 257 idx = type_cache.get(key) 258 if idx is None: 259 idx = type_cache[key] = len(types) 260 # Make sure `types` is the proper length for any recursive calls 261 # to `lower_extra_type` that might happen from within `lower_type`. 262 types.append(None) 263 realtype = lower_type(type) 264 types[idx] = realtype 265 return idx 266 267 def describe_type(type): # Create the type's documentation comment. 268 tag = type["tag"][3:].lower() 269 if tag == "legacy_array": 270 return "%s[size_is=%d]" % (describe_type(type["element"]), type["size_is"]) 271 elif tag == "array": 272 return "Array<%s>" % describe_type(type["element"]) 273 elif tag == "interface_type" or tag == "domobject": 274 return type["name"] 275 elif tag == "interface_is_type": 276 return "iid_is(%d)" % type["iid_is"] 277 elif tag.endswith("_size_is"): 278 return "%s(size_is=%d)" % (tag, type["size_is"]) 279 return tag 280 281 def lower_type(type, in_=False, out=False, optional=False): 282 tag = type["tag"] 283 d1 = d2 = 0 284 285 # TD_VOID is used for types that can't be represented in JS, so they 286 # should not be represented in the XPT info. 287 assert tag != "TD_VOID" 288 289 if tag == "TD_LEGACY_ARRAY": 290 d1 = type["size_is"] 291 d2 = lower_extra_type(type["element"]) 292 293 elif tag == "TD_ARRAY": 294 # NOTE: TD_ARRAY can hold 16 bits of type index, while 295 # TD_LEGACY_ARRAY can only hold 8. 296 d1, d2 = splitint(lower_extra_type(type["element"])) 297 298 elif tag == "TD_INTERFACE_TYPE": 299 d1, d2 = splitint(interface_idx(type["name"])) 300 301 elif tag == "TD_INTERFACE_IS_TYPE": 302 d1 = type["iid_is"] 303 304 elif tag == "TD_DOMOBJECT": 305 d1, d2 = splitint(lower_domobject(type)) 306 307 elif tag.endswith("_SIZE_IS"): 308 d1 = type["size_is"] 309 310 assert d1 < 256 and d2 < 256, "Data values too large" 311 return nsXPTType( 312 describe_type(type), 313 mTag=tag, 314 mData1=d1, 315 mData2=d2, 316 mInParam=in_, 317 mOutParam=out, 318 mOptionalParam=optional, 319 ) 320 321 def lower_param(param, paramname): 322 params.append( 323 nsXPTParamInfo( 324 "%d = %s" % (len(params), paramname), 325 mType=lower_type( 326 param["type"], 327 in_="in" in param["flags"], 328 out="out" in param["flags"], 329 optional="optional" in param["flags"], 330 ), 331 ) 332 ) 333 334 def is_method_reflectable(method): 335 if "hidden" in method["flags"]: 336 return False 337 338 for param in method["params"]: 339 # Reflected methods can't use native types. All native types end up 340 # getting tagged as void*, so this check is easy. 341 if param["type"]["tag"] == "TD_VOID": 342 return False 343 344 return True 345 346 def lower_method(method, ifacename): 347 methodname = "%s::%s" % (ifacename, method["name"]) 348 349 isSymbol = "symbol" in method["flags"] 350 reflectable = is_method_reflectable(method) 351 352 if not reflectable: 353 # Hide the parameters of methods that can't be called from JS to 354 # reduce the size of the file. 355 paramidx = name = numparams = 0 356 else: 357 if isSymbol: 358 name = lower_symbol(method["name"]) 359 else: 360 name = lower_string(method["name"]) 361 362 numparams = len(method["params"]) 363 364 # Check cache for parameters 365 cachekey = json.dumps(method["params"], sort_keys=True) 366 paramidx = param_cache.get(cachekey) 367 if paramidx is None: 368 paramidx = param_cache[cachekey] = len(params) 369 for idx, param in enumerate(method["params"]): 370 lower_param(param, "%s[%d]" % (methodname, idx)) 371 372 nonlocal max_params, method_with_max_params 373 if numparams > max_params: 374 max_params = numparams 375 method_with_max_params = methodname 376 methods.append( 377 nsXPTMethodInfo( 378 "%d = %s" % (len(methods), methodname), 379 mName=name, 380 mParams=paramidx, 381 mNumParams=numparams, 382 # Flags 383 mGetter="getter" in method["flags"], 384 mSetter="setter" in method["flags"], 385 mReflectable=reflectable, 386 mOptArgc="optargc" in method["flags"], 387 mContext="jscontext" in method["flags"], 388 mHasRetval="hasretval" in method["flags"], 389 mIsSymbol=isSymbol, 390 ) 391 ) 392 393 def lower_const(const, ifacename): 394 assert const["type"]["tag"] in [ 395 "TD_INT16", 396 "TD_INT32", 397 "TD_UINT8", 398 "TD_UINT16", 399 "TD_UINT32", 400 ] 401 is_signed = const["type"]["tag"] in ["TD_INT16", "TD_INT32"] 402 403 # Constants are always either signed or unsigned 16 or 32 bit integers, 404 # which we will only need to convert to JS values. To save on space, 405 # don't bother storing the type, and instead just store a 32-bit 406 # unsigned integer, and stash whether to interpret it as signed. 407 consts.append( 408 nsXPTConstantInfo( 409 "%d = %s::%s" % (len(consts), ifacename, const["name"]), 410 mName=lower_string(const["name"]), 411 mSigned=is_signed, 412 mValue="(uint32_t)%d" % const["value"], 413 ) 414 ) 415 416 def ancestors(iface): 417 yield iface 418 while iface["parent"]: 419 iface = name_phf.get_entry(iface["parent"].encode("ascii")) 420 yield iface 421 422 def lower_iface(iface): 423 method_cnt = sum(len(i["methods"]) for i in ancestors(iface)) 424 const_cnt = sum(len(i["consts"]) for i in ancestors(iface)) 425 426 # The number of maximum methods is not arbitrary. It is the same value 427 # as in xpcom/reflect/xptcall/genstubs.pl; do not change this value 428 # without changing that one or you WILL see problems. 429 # 430 # In addition, mNumMethods and mNumConsts are stored as a 8-bit ints, 431 # meaning we cannot exceed 255 methods/consts on any interface. 432 assert method_cnt < 250, "%s has too many methods" % iface["name"] 433 assert const_cnt < 256, "%s has too many constants" % iface["name"] 434 435 # Store the lowered interface as 'cxx' on the iface object. 436 iface["cxx"] = nsXPTInterfaceInfo( 437 "%d = %s" % (iface["idx"], iface["name"]), 438 mIID=lower_uuid(iface["uuid"]), 439 mName=lower_string(iface["name"]), 440 mParent=interface_idx(iface["parent"]), 441 mMethods=len(methods), 442 mNumMethods=method_cnt, 443 mConsts=len(consts), 444 mNumConsts=const_cnt, 445 # Flags 446 mBuiltinClass="builtinclass" in iface["flags"], 447 mMainProcessScriptableOnly="main_process_only" in iface["flags"], 448 mFunction="function" in iface["flags"], 449 ) 450 451 # Lower methods and constants used by this interface 452 for method in iface["methods"]: 453 lower_method(method, iface["name"]) 454 for const in iface["consts"]: 455 lower_const(const, iface["name"]) 456 457 # Lower the types which have fixed indexes first, and check that the indexes 458 # seem correct. 459 for expected, ty in enumerate(utility_types): 460 got = lower_extra_type(ty) 461 assert got == expected, "Wrong index when lowering" 462 463 # Lower interfaces in the order of the IID phf's entries lookup. 464 for iface in iid_phf.entries: 465 lower_iface(iface) 466 467 # Write out the final output files 468 fd.write("/* THIS FILE WAS GENERATED BY xptcodegen.py - DO NOT EDIT */\n\n") 469 header_fd.write("/* THIS FILE WAS GENERATED BY xptcodegen.py - DO NOT EDIT */\n\n") 470 471 header_fd.write( 472 """ 473#ifndef xptdata_h 474#define xptdata_h 475 476enum class nsXPTInterface : uint16_t { 477""" 478 ) 479 480 for entry in iid_phf.entries: 481 header_fd.write(" %s,\n" % entry["name"]) 482 483 header_fd.write( 484 """ 485}; 486 487#endif 488""" 489 ) 490 491 # Include any bindings files which we need to include for webidl types 492 for include in sorted(includes): 493 fd.write('#include "%s"\n' % include) 494 495 # Write out our header 496 fd.write( 497 """ 498#include "xptinfo.h" 499#include "mozilla/PerfectHash.h" 500#include "mozilla/dom/BindingUtils.h" 501 502// These template methods are specialized to be used in the sDOMObjects table. 503template<mozilla::dom::prototypes::ID PrototypeID, typename T> 504static nsresult UnwrapDOMObject(JS::HandleValue aHandle, void** aObj, JSContext* aCx) 505{ 506 RefPtr<T> p; 507 nsresult rv = mozilla::dom::UnwrapObject<PrototypeID, T>(aHandle, p, aCx); 508 p.forget(aObj); 509 return rv; 510} 511 512template<typename T> 513static bool WrapDOMObject(JSContext* aCx, void* aObj, JS::MutableHandleValue aHandle) 514{ 515 return mozilla::dom::GetOrCreateDOMReflector(aCx, reinterpret_cast<T*>(aObj), aHandle); 516} 517 518template<typename T> 519static void CleanupDOMObject(void* aObj) 520{ 521 RefPtr<T> p = already_AddRefed<T>(reinterpret_cast<T*>(aObj)); 522} 523 524namespace xpt { 525namespace detail { 526 527""" 528 ) 529 530 # Static data arrays 531 def array(ty, name, els): 532 fd.write( 533 "const %s %s[] = {%s\n};\n\n" 534 % (ty, name, ",".join(indented("\n" + str(e)) for e in els)) 535 ) 536 537 array("nsXPTType", "sTypes", types) 538 array("nsXPTParamInfo", "sParams", params) 539 array("nsXPTMethodInfo", "sMethods", methods) 540 # Verify that stack-allocated buffers will do for xptcall implementations. 541 msg = ( 542 "Too many method arguments in %s. " 543 "Either reduce the number of arguments " 544 "or increase PARAM_BUFFER_COUNT." % method_with_max_params 545 ) 546 fd.write('static_assert(%s <= PARAM_BUFFER_COUNT, "%s");\n\n' % (max_params, msg)) 547 array("nsXPTDOMObjectInfo", "sDOMObjects", domobjects) 548 array("nsXPTConstantInfo", "sConsts", consts) 549 550 # The strings array. We write out individual characters to avoid MSVC restrictions. 551 fd.write("const char sStrings[] = {\n") 552 for s, off in strings.items(): 553 fd.write(" // %d = %s\n '%s','\\0',\n" % (off, s, "','".join(s))) 554 fd.write("};\n\n") 555 556 # Build the perfect hash table for InterfaceByIID 557 fd.write( 558 iid_phf.cxx_codegen( 559 name="InterfaceByIID", 560 entry_type="nsXPTInterfaceInfo", 561 entries_name="sInterfaces", 562 lower_entry=lambda iface: iface["cxx"], 563 # Check that the IIDs match to support IID keys not in the map. 564 return_type="const nsXPTInterfaceInfo*", 565 return_entry="return entry.IID().Equals(aKey) ? &entry : nullptr;", 566 key_type="const nsIID&", 567 key_bytes="reinterpret_cast<const char*>(&aKey)", 568 key_length="sizeof(nsIID)", 569 ) 570 ) 571 fd.write("\n") 572 573 # Build the perfect hash table for InterfaceByName 574 fd.write( 575 name_phf.cxx_codegen( 576 name="InterfaceByName", 577 entry_type="uint16_t", 578 lower_entry=lambda iface: "%-4d /* %s */" % (iface["idx"], iface["name"]), 579 # Get the actual nsXPTInterfaceInfo from sInterfaces, and 580 # double-check that names match. 581 return_type="const nsXPTInterfaceInfo*", 582 return_entry="return strcmp(sInterfaces[entry].Name(), aKey) == 0" 583 " ? &sInterfaces[entry] : nullptr;", 584 ) 585 ) 586 fd.write("\n") 587 588 # Generate some checks that the indexes for the utility types match the 589 # declared ones in xptinfo.h 590 for idx, ty in enumerate(utility_types): 591 fd.write( 592 'static_assert(%d == (uint8_t)nsXPTType::Idx::%s, "Bad idx");\n' 593 % (idx, ty["tag"][3:]) 594 ) 595 596 fd.write( 597 """ 598const uint16_t sInterfacesSize = mozilla::ArrayLength(sInterfaces); 599 600} // namespace detail 601} // namespace xpt 602""" 603 ) 604 605 606def link_and_write(files, outfile, outheader): 607 interfaces = [] 608 for file in files: 609 with open(file, "r") as fd: 610 interfaces += json.load(fd) 611 612 iids = set() 613 names = set() 614 for interface in interfaces: 615 assert interface["uuid"] not in iids, "duplicated UUID %s" % interface["uuid"] 616 assert interface["name"] not in names, "duplicated name %s" % interface["name"] 617 iids.add(interface["uuid"]) 618 names.add(interface["name"]) 619 620 link_to_cpp(interfaces, outfile, outheader) 621 622 623def main(): 624 from argparse import ArgumentParser 625 import sys 626 627 parser = ArgumentParser() 628 parser.add_argument("outfile", help="Output C++ file to generate") 629 parser.add_argument("outheader", help="Output C++ header file to generate") 630 parser.add_argument("xpts", nargs="*", help="source xpt files") 631 632 args = parser.parse_args(sys.argv[1:]) 633 with open(args.outfile, "w") as fd, open(args.outheader, "w") as header_fd: 634 link_and_write(args.xpts, fd, header_fd) 635 636 637if __name__ == "__main__": 638 main() 639