1# -*- coding: utf-8 -*- 2# 3# QAPI schema internal representation 4# 5# Copyright (c) 2015-2019 Red Hat Inc. 6# 7# Authors: 8# Markus Armbruster <armbru@redhat.com> 9# Eric Blake <eblake@redhat.com> 10# Marc-André Lureau <marcandre.lureau@redhat.com> 11# 12# This work is licensed under the terms of the GNU GPL, version 2. 13# See the COPYING file in the top-level directory. 14 15# TODO catching name collisions in generated code would be nice 16 17from collections import OrderedDict 18import os 19import re 20from typing import Optional 21 22from .common import POINTER_SUFFIX, c_name 23from .error import QAPIError, QAPISemError 24from .expr import check_exprs 25from .parser import QAPISchemaParser 26 27 28class QAPISchemaEntity: 29 meta: Optional[str] = None 30 31 def __init__(self, name, info, doc, ifcond=None, features=None): 32 assert name is None or isinstance(name, str) 33 for f in features or []: 34 assert isinstance(f, QAPISchemaFeature) 35 f.set_defined_in(name) 36 self.name = name 37 self._module = None 38 # For explicitly defined entities, info points to the (explicit) 39 # definition. For builtins (and their arrays), info is None. 40 # For implicitly defined entities, info points to a place that 41 # triggered the implicit definition (there may be more than one 42 # such place). 43 self.info = info 44 self.doc = doc 45 self._ifcond = ifcond or [] 46 self.features = features or [] 47 self._checked = False 48 49 def c_name(self): 50 return c_name(self.name) 51 52 def check(self, schema): 53 assert not self._checked 54 seen = {} 55 for f in self.features: 56 f.check_clash(self.info, seen) 57 self._checked = True 58 59 def connect_doc(self, doc=None): 60 doc = doc or self.doc 61 if doc: 62 for f in self.features: 63 doc.connect_feature(f) 64 65 def check_doc(self): 66 if self.doc: 67 self.doc.check() 68 69 def _set_module(self, schema, info): 70 assert self._checked 71 self._module = schema.module_by_fname(info and info.fname) 72 self._module.add_entity(self) 73 74 def set_module(self, schema): 75 self._set_module(schema, self.info) 76 77 @property 78 def ifcond(self): 79 assert self._checked 80 return self._ifcond 81 82 def is_implicit(self): 83 return not self.info 84 85 def visit(self, visitor): 86 assert self._checked 87 88 def describe(self): 89 assert self.meta 90 return "%s '%s'" % (self.meta, self.name) 91 92 93class QAPISchemaVisitor: 94 def visit_begin(self, schema): 95 pass 96 97 def visit_end(self): 98 pass 99 100 def visit_module(self, name): 101 pass 102 103 def visit_needed(self, entity): 104 # Default to visiting everything 105 return True 106 107 def visit_include(self, name, info): 108 pass 109 110 def visit_builtin_type(self, name, info, json_type): 111 pass 112 113 def visit_enum_type(self, name, info, ifcond, features, members, prefix): 114 pass 115 116 def visit_array_type(self, name, info, ifcond, element_type): 117 pass 118 119 def visit_object_type(self, name, info, ifcond, features, 120 base, members, variants): 121 pass 122 123 def visit_object_type_flat(self, name, info, ifcond, features, 124 members, variants): 125 pass 126 127 def visit_alternate_type(self, name, info, ifcond, features, variants): 128 pass 129 130 def visit_command(self, name, info, ifcond, features, 131 arg_type, ret_type, gen, success_response, boxed, 132 allow_oob, allow_preconfig, coroutine): 133 pass 134 135 def visit_event(self, name, info, ifcond, features, arg_type, boxed): 136 pass 137 138 139class QAPISchemaModule: 140 def __init__(self, name): 141 self.name = name 142 self._entity_list = [] 143 144 @staticmethod 145 def is_system_module(name: Optional[str]) -> bool: 146 """ 147 System modules are internally defined modules. 148 149 Their names start with the "./" prefix. 150 """ 151 return name is None or name.startswith('./') 152 153 @classmethod 154 def is_user_module(cls, name: Optional[str]) -> bool: 155 """ 156 User modules are those defined by the user in qapi JSON files. 157 158 They do not start with the "./" prefix. 159 """ 160 return not cls.is_system_module(name) 161 162 @staticmethod 163 def is_builtin_module(name: Optional[str]) -> bool: 164 """ 165 The built-in module is a single System module for the built-in types. 166 167 It is presently always the value 'None'. 168 """ 169 return name is None 170 171 def add_entity(self, ent): 172 self._entity_list.append(ent) 173 174 def visit(self, visitor): 175 visitor.visit_module(self.name) 176 for entity in self._entity_list: 177 if visitor.visit_needed(entity): 178 entity.visit(visitor) 179 180 181class QAPISchemaInclude(QAPISchemaEntity): 182 def __init__(self, sub_module, info): 183 super().__init__(None, info, None) 184 self._sub_module = sub_module 185 186 def visit(self, visitor): 187 super().visit(visitor) 188 visitor.visit_include(self._sub_module.name, self.info) 189 190 191class QAPISchemaType(QAPISchemaEntity): 192 # Return the C type for common use. 193 # For the types we commonly box, this is a pointer type. 194 def c_type(self): 195 pass 196 197 # Return the C type to be used in a parameter list. 198 def c_param_type(self): 199 return self.c_type() 200 201 # Return the C type to be used where we suppress boxing. 202 def c_unboxed_type(self): 203 return self.c_type() 204 205 def json_type(self): 206 pass 207 208 def alternate_qtype(self): 209 json2qtype = { 210 'null': 'QTYPE_QNULL', 211 'string': 'QTYPE_QSTRING', 212 'number': 'QTYPE_QNUM', 213 'int': 'QTYPE_QNUM', 214 'boolean': 'QTYPE_QBOOL', 215 'object': 'QTYPE_QDICT' 216 } 217 return json2qtype.get(self.json_type()) 218 219 def doc_type(self): 220 if self.is_implicit(): 221 return None 222 return self.name 223 224 def check(self, schema): 225 QAPISchemaEntity.check(self, schema) 226 if 'deprecated' in [f.name for f in self.features]: 227 raise QAPISemError( 228 self.info, "feature 'deprecated' is not supported for types") 229 230 def describe(self): 231 assert self.meta 232 return "%s type '%s'" % (self.meta, self.name) 233 234 235class QAPISchemaBuiltinType(QAPISchemaType): 236 meta = 'built-in' 237 238 def __init__(self, name, json_type, c_type): 239 super().__init__(name, None, None) 240 assert not c_type or isinstance(c_type, str) 241 assert json_type in ('string', 'number', 'int', 'boolean', 'null', 242 'value') 243 self._json_type_name = json_type 244 self._c_type_name = c_type 245 246 def c_name(self): 247 return self.name 248 249 def c_type(self): 250 return self._c_type_name 251 252 def c_param_type(self): 253 if self.name == 'str': 254 return 'const ' + self._c_type_name 255 return self._c_type_name 256 257 def json_type(self): 258 return self._json_type_name 259 260 def doc_type(self): 261 return self.json_type() 262 263 def visit(self, visitor): 264 super().visit(visitor) 265 visitor.visit_builtin_type(self.name, self.info, self.json_type()) 266 267 268class QAPISchemaEnumType(QAPISchemaType): 269 meta = 'enum' 270 271 def __init__(self, name, info, doc, ifcond, features, members, prefix): 272 super().__init__(name, info, doc, ifcond, features) 273 for m in members: 274 assert isinstance(m, QAPISchemaEnumMember) 275 m.set_defined_in(name) 276 assert prefix is None or isinstance(prefix, str) 277 self.members = members 278 self.prefix = prefix 279 280 def check(self, schema): 281 super().check(schema) 282 seen = {} 283 for m in self.members: 284 m.check_clash(self.info, seen) 285 286 def connect_doc(self, doc=None): 287 super().connect_doc(doc) 288 doc = doc or self.doc 289 for m in self.members: 290 m.connect_doc(doc) 291 292 def is_implicit(self): 293 # See QAPISchema._make_implicit_enum_type() and ._def_predefineds() 294 return self.name.endswith('Kind') or self.name == 'QType' 295 296 def c_type(self): 297 return c_name(self.name) 298 299 def member_names(self): 300 return [m.name for m in self.members] 301 302 def json_type(self): 303 return 'string' 304 305 def visit(self, visitor): 306 super().visit(visitor) 307 visitor.visit_enum_type( 308 self.name, self.info, self.ifcond, self.features, 309 self.members, self.prefix) 310 311 312class QAPISchemaArrayType(QAPISchemaType): 313 meta = 'array' 314 315 def __init__(self, name, info, element_type): 316 super().__init__(name, info, None) 317 assert isinstance(element_type, str) 318 self._element_type_name = element_type 319 self.element_type = None 320 321 def check(self, schema): 322 super().check(schema) 323 self.element_type = schema.resolve_type( 324 self._element_type_name, self.info, 325 self.info and self.info.defn_meta) 326 assert not isinstance(self.element_type, QAPISchemaArrayType) 327 328 def set_module(self, schema): 329 self._set_module(schema, self.element_type.info) 330 331 @property 332 def ifcond(self): 333 assert self._checked 334 return self.element_type.ifcond 335 336 def is_implicit(self): 337 return True 338 339 def c_type(self): 340 return c_name(self.name) + POINTER_SUFFIX 341 342 def json_type(self): 343 return 'array' 344 345 def doc_type(self): 346 elt_doc_type = self.element_type.doc_type() 347 if not elt_doc_type: 348 return None 349 return 'array of ' + elt_doc_type 350 351 def visit(self, visitor): 352 super().visit(visitor) 353 visitor.visit_array_type(self.name, self.info, self.ifcond, 354 self.element_type) 355 356 def describe(self): 357 assert self.meta 358 return "%s type ['%s']" % (self.meta, self._element_type_name) 359 360 361class QAPISchemaObjectType(QAPISchemaType): 362 def __init__(self, name, info, doc, ifcond, features, 363 base, local_members, variants): 364 # struct has local_members, optional base, and no variants 365 # flat union has base, variants, and no local_members 366 # simple union has local_members, variants, and no base 367 super().__init__(name, info, doc, ifcond, features) 368 self.meta = 'union' if variants else 'struct' 369 assert base is None or isinstance(base, str) 370 for m in local_members: 371 assert isinstance(m, QAPISchemaObjectTypeMember) 372 m.set_defined_in(name) 373 if variants is not None: 374 assert isinstance(variants, QAPISchemaVariants) 375 variants.set_defined_in(name) 376 self._base_name = base 377 self.base = None 378 self.local_members = local_members 379 self.variants = variants 380 self.members = None 381 382 def check(self, schema): 383 # This calls another type T's .check() exactly when the C 384 # struct emitted by gen_object() contains that T's C struct 385 # (pointers don't count). 386 if self.members is not None: 387 # A previous .check() completed: nothing to do 388 return 389 if self._checked: 390 # Recursed: C struct contains itself 391 raise QAPISemError(self.info, 392 "object %s contains itself" % self.name) 393 394 super().check(schema) 395 assert self._checked and self.members is None 396 397 seen = OrderedDict() 398 if self._base_name: 399 self.base = schema.resolve_type(self._base_name, self.info, 400 "'base'") 401 if (not isinstance(self.base, QAPISchemaObjectType) 402 or self.base.variants): 403 raise QAPISemError( 404 self.info, 405 "'base' requires a struct type, %s isn't" 406 % self.base.describe()) 407 self.base.check(schema) 408 self.base.check_clash(self.info, seen) 409 for m in self.local_members: 410 m.check(schema) 411 m.check_clash(self.info, seen) 412 members = seen.values() 413 414 if self.variants: 415 self.variants.check(schema, seen) 416 self.variants.check_clash(self.info, seen) 417 418 self.members = members # mark completed 419 420 # Check that the members of this type do not cause duplicate JSON members, 421 # and update seen to track the members seen so far. Report any errors 422 # on behalf of info, which is not necessarily self.info 423 def check_clash(self, info, seen): 424 assert self._checked 425 assert not self.variants # not implemented 426 for m in self.members: 427 m.check_clash(info, seen) 428 429 def connect_doc(self, doc=None): 430 super().connect_doc(doc) 431 doc = doc or self.doc 432 if self.base and self.base.is_implicit(): 433 self.base.connect_doc(doc) 434 for m in self.local_members: 435 m.connect_doc(doc) 436 437 @property 438 def ifcond(self): 439 assert self._checked 440 if isinstance(self._ifcond, QAPISchemaType): 441 # Simple union wrapper type inherits from wrapped type; 442 # see _make_implicit_object_type() 443 return self._ifcond.ifcond 444 return self._ifcond 445 446 def is_implicit(self): 447 # See QAPISchema._make_implicit_object_type(), as well as 448 # _def_predefineds() 449 return self.name.startswith('q_') 450 451 def is_empty(self): 452 assert self.members is not None 453 return not self.members and not self.variants 454 455 def c_name(self): 456 assert self.name != 'q_empty' 457 return super().c_name() 458 459 def c_type(self): 460 assert not self.is_implicit() 461 return c_name(self.name) + POINTER_SUFFIX 462 463 def c_unboxed_type(self): 464 return c_name(self.name) 465 466 def json_type(self): 467 return 'object' 468 469 def visit(self, visitor): 470 super().visit(visitor) 471 visitor.visit_object_type( 472 self.name, self.info, self.ifcond, self.features, 473 self.base, self.local_members, self.variants) 474 visitor.visit_object_type_flat( 475 self.name, self.info, self.ifcond, self.features, 476 self.members, self.variants) 477 478 479class QAPISchemaAlternateType(QAPISchemaType): 480 meta = 'alternate' 481 482 def __init__(self, name, info, doc, ifcond, features, variants): 483 super().__init__(name, info, doc, ifcond, features) 484 assert isinstance(variants, QAPISchemaVariants) 485 assert variants.tag_member 486 variants.set_defined_in(name) 487 variants.tag_member.set_defined_in(self.name) 488 self.variants = variants 489 490 def check(self, schema): 491 super().check(schema) 492 self.variants.tag_member.check(schema) 493 # Not calling self.variants.check_clash(), because there's nothing 494 # to clash with 495 self.variants.check(schema, {}) 496 # Alternate branch names have no relation to the tag enum values; 497 # so we have to check for potential name collisions ourselves. 498 seen = {} 499 types_seen = {} 500 for v in self.variants.variants: 501 v.check_clash(self.info, seen) 502 qtype = v.type.alternate_qtype() 503 if not qtype: 504 raise QAPISemError( 505 self.info, 506 "%s cannot use %s" 507 % (v.describe(self.info), v.type.describe())) 508 conflicting = set([qtype]) 509 if qtype == 'QTYPE_QSTRING': 510 if isinstance(v.type, QAPISchemaEnumType): 511 for m in v.type.members: 512 if m.name in ['on', 'off']: 513 conflicting.add('QTYPE_QBOOL') 514 if re.match(r'[-+0-9.]', m.name): 515 # lazy, could be tightened 516 conflicting.add('QTYPE_QNUM') 517 else: 518 conflicting.add('QTYPE_QNUM') 519 conflicting.add('QTYPE_QBOOL') 520 for qt in conflicting: 521 if qt in types_seen: 522 raise QAPISemError( 523 self.info, 524 "%s can't be distinguished from '%s'" 525 % (v.describe(self.info), types_seen[qt])) 526 types_seen[qt] = v.name 527 528 def connect_doc(self, doc=None): 529 super().connect_doc(doc) 530 doc = doc or self.doc 531 for v in self.variants.variants: 532 v.connect_doc(doc) 533 534 def c_type(self): 535 return c_name(self.name) + POINTER_SUFFIX 536 537 def json_type(self): 538 return 'value' 539 540 def visit(self, visitor): 541 super().visit(visitor) 542 visitor.visit_alternate_type( 543 self.name, self.info, self.ifcond, self.features, self.variants) 544 545 546class QAPISchemaVariants: 547 def __init__(self, tag_name, info, tag_member, variants): 548 # Flat unions pass tag_name but not tag_member. 549 # Simple unions and alternates pass tag_member but not tag_name. 550 # After check(), tag_member is always set, and tag_name remains 551 # a reliable witness of being used by a flat union. 552 assert bool(tag_member) != bool(tag_name) 553 assert (isinstance(tag_name, str) or 554 isinstance(tag_member, QAPISchemaObjectTypeMember)) 555 for v in variants: 556 assert isinstance(v, QAPISchemaVariant) 557 self._tag_name = tag_name 558 self.info = info 559 self.tag_member = tag_member 560 self.variants = variants 561 562 def set_defined_in(self, name): 563 for v in self.variants: 564 v.set_defined_in(name) 565 566 def check(self, schema, seen): 567 if not self.tag_member: # flat union 568 self.tag_member = seen.get(c_name(self._tag_name)) 569 base = "'base'" 570 # Pointing to the base type when not implicit would be 571 # nice, but we don't know it here 572 if not self.tag_member or self._tag_name != self.tag_member.name: 573 raise QAPISemError( 574 self.info, 575 "discriminator '%s' is not a member of %s" 576 % (self._tag_name, base)) 577 # Here we do: 578 base_type = schema.lookup_type(self.tag_member.defined_in) 579 assert base_type 580 if not base_type.is_implicit(): 581 base = "base type '%s'" % self.tag_member.defined_in 582 if not isinstance(self.tag_member.type, QAPISchemaEnumType): 583 raise QAPISemError( 584 self.info, 585 "discriminator member '%s' of %s must be of enum type" 586 % (self._tag_name, base)) 587 if self.tag_member.optional: 588 raise QAPISemError( 589 self.info, 590 "discriminator member '%s' of %s must not be optional" 591 % (self._tag_name, base)) 592 if self.tag_member.ifcond: 593 raise QAPISemError( 594 self.info, 595 "discriminator member '%s' of %s must not be conditional" 596 % (self._tag_name, base)) 597 else: # simple union 598 assert isinstance(self.tag_member.type, QAPISchemaEnumType) 599 assert not self.tag_member.optional 600 assert self.tag_member.ifcond == [] 601 if self._tag_name: # flat union 602 # branches that are not explicitly covered get an empty type 603 cases = {v.name for v in self.variants} 604 for m in self.tag_member.type.members: 605 if m.name not in cases: 606 v = QAPISchemaVariant(m.name, self.info, 607 'q_empty', m.ifcond) 608 v.set_defined_in(self.tag_member.defined_in) 609 self.variants.append(v) 610 if not self.variants: 611 raise QAPISemError(self.info, "union has no branches") 612 for v in self.variants: 613 v.check(schema) 614 # Union names must match enum values; alternate names are 615 # checked separately. Use 'seen' to tell the two apart. 616 if seen: 617 if v.name not in self.tag_member.type.member_names(): 618 raise QAPISemError( 619 self.info, 620 "branch '%s' is not a value of %s" 621 % (v.name, self.tag_member.type.describe())) 622 if (not isinstance(v.type, QAPISchemaObjectType) 623 or v.type.variants): 624 raise QAPISemError( 625 self.info, 626 "%s cannot use %s" 627 % (v.describe(self.info), v.type.describe())) 628 v.type.check(schema) 629 630 def check_clash(self, info, seen): 631 for v in self.variants: 632 # Reset seen map for each variant, since qapi names from one 633 # branch do not affect another branch 634 v.type.check_clash(info, dict(seen)) 635 636 637class QAPISchemaMember: 638 """ Represents object members, enum members and features """ 639 role = 'member' 640 641 def __init__(self, name, info, ifcond=None): 642 assert isinstance(name, str) 643 self.name = name 644 self.info = info 645 self.ifcond = ifcond or [] 646 self.defined_in = None 647 648 def set_defined_in(self, name): 649 assert not self.defined_in 650 self.defined_in = name 651 652 def check_clash(self, info, seen): 653 cname = c_name(self.name) 654 if cname in seen: 655 raise QAPISemError( 656 info, 657 "%s collides with %s" 658 % (self.describe(info), seen[cname].describe(info))) 659 seen[cname] = self 660 661 def connect_doc(self, doc): 662 if doc: 663 doc.connect_member(self) 664 665 def describe(self, info): 666 role = self.role 667 defined_in = self.defined_in 668 assert defined_in 669 670 if defined_in.startswith('q_obj_'): 671 # See QAPISchema._make_implicit_object_type() - reverse the 672 # mapping there to create a nice human-readable description 673 defined_in = defined_in[6:] 674 if defined_in.endswith('-arg'): 675 # Implicit type created for a command's dict 'data' 676 assert role == 'member' 677 role = 'parameter' 678 elif defined_in.endswith('-base'): 679 # Implicit type created for a flat union's dict 'base' 680 role = 'base ' + role 681 else: 682 # Implicit type created for a simple union's branch 683 assert defined_in.endswith('-wrapper') 684 # Unreachable and not implemented 685 assert False 686 elif defined_in.endswith('Kind'): 687 # See QAPISchema._make_implicit_enum_type() 688 # Implicit enum created for simple union's branches 689 assert role == 'value' 690 role = 'branch' 691 elif defined_in != info.defn_name: 692 return "%s '%s' of type '%s'" % (role, self.name, defined_in) 693 return "%s '%s'" % (role, self.name) 694 695 696class QAPISchemaEnumMember(QAPISchemaMember): 697 role = 'value' 698 699 700class QAPISchemaFeature(QAPISchemaMember): 701 role = 'feature' 702 703 704class QAPISchemaObjectTypeMember(QAPISchemaMember): 705 def __init__(self, name, info, typ, optional, ifcond=None, features=None): 706 super().__init__(name, info, ifcond) 707 assert isinstance(typ, str) 708 assert isinstance(optional, bool) 709 for f in features or []: 710 assert isinstance(f, QAPISchemaFeature) 711 f.set_defined_in(name) 712 self._type_name = typ 713 self.type = None 714 self.optional = optional 715 self.features = features or [] 716 717 def check(self, schema): 718 assert self.defined_in 719 self.type = schema.resolve_type(self._type_name, self.info, 720 self.describe) 721 seen = {} 722 for f in self.features: 723 f.check_clash(self.info, seen) 724 725 def connect_doc(self, doc): 726 super().connect_doc(doc) 727 if doc: 728 for f in self.features: 729 doc.connect_feature(f) 730 731 732class QAPISchemaVariant(QAPISchemaObjectTypeMember): 733 role = 'branch' 734 735 def __init__(self, name, info, typ, ifcond=None): 736 super().__init__(name, info, typ, False, ifcond) 737 738 739class QAPISchemaCommand(QAPISchemaEntity): 740 meta = 'command' 741 742 def __init__(self, name, info, doc, ifcond, features, 743 arg_type, ret_type, 744 gen, success_response, boxed, allow_oob, allow_preconfig, 745 coroutine): 746 super().__init__(name, info, doc, ifcond, features) 747 assert not arg_type or isinstance(arg_type, str) 748 assert not ret_type or isinstance(ret_type, str) 749 self._arg_type_name = arg_type 750 self.arg_type = None 751 self._ret_type_name = ret_type 752 self.ret_type = None 753 self.gen = gen 754 self.success_response = success_response 755 self.boxed = boxed 756 self.allow_oob = allow_oob 757 self.allow_preconfig = allow_preconfig 758 self.coroutine = coroutine 759 760 def check(self, schema): 761 super().check(schema) 762 if self._arg_type_name: 763 self.arg_type = schema.resolve_type( 764 self._arg_type_name, self.info, "command's 'data'") 765 if not isinstance(self.arg_type, QAPISchemaObjectType): 766 raise QAPISemError( 767 self.info, 768 "command's 'data' cannot take %s" 769 % self.arg_type.describe()) 770 if self.arg_type.variants and not self.boxed: 771 raise QAPISemError( 772 self.info, 773 "command's 'data' can take %s only with 'boxed': true" 774 % self.arg_type.describe()) 775 if self._ret_type_name: 776 self.ret_type = schema.resolve_type( 777 self._ret_type_name, self.info, "command's 'returns'") 778 if self.name not in self.info.pragma.returns_whitelist: 779 typ = self.ret_type 780 if isinstance(typ, QAPISchemaArrayType): 781 typ = self.ret_type.element_type 782 assert typ 783 if not isinstance(typ, QAPISchemaObjectType): 784 raise QAPISemError( 785 self.info, 786 "command's 'returns' cannot take %s" 787 % self.ret_type.describe()) 788 789 def connect_doc(self, doc=None): 790 super().connect_doc(doc) 791 doc = doc or self.doc 792 if doc: 793 if self.arg_type and self.arg_type.is_implicit(): 794 self.arg_type.connect_doc(doc) 795 796 def visit(self, visitor): 797 super().visit(visitor) 798 visitor.visit_command( 799 self.name, self.info, self.ifcond, self.features, 800 self.arg_type, self.ret_type, self.gen, self.success_response, 801 self.boxed, self.allow_oob, self.allow_preconfig, 802 self.coroutine) 803 804 805class QAPISchemaEvent(QAPISchemaEntity): 806 meta = 'event' 807 808 def __init__(self, name, info, doc, ifcond, features, arg_type, boxed): 809 super().__init__(name, info, doc, ifcond, features) 810 assert not arg_type or isinstance(arg_type, str) 811 self._arg_type_name = arg_type 812 self.arg_type = None 813 self.boxed = boxed 814 815 def check(self, schema): 816 super().check(schema) 817 if self._arg_type_name: 818 self.arg_type = schema.resolve_type( 819 self._arg_type_name, self.info, "event's 'data'") 820 if not isinstance(self.arg_type, QAPISchemaObjectType): 821 raise QAPISemError( 822 self.info, 823 "event's 'data' cannot take %s" 824 % self.arg_type.describe()) 825 if self.arg_type.variants and not self.boxed: 826 raise QAPISemError( 827 self.info, 828 "event's 'data' can take %s only with 'boxed': true" 829 % self.arg_type.describe()) 830 831 def connect_doc(self, doc=None): 832 super().connect_doc(doc) 833 doc = doc or self.doc 834 if doc: 835 if self.arg_type and self.arg_type.is_implicit(): 836 self.arg_type.connect_doc(doc) 837 838 def visit(self, visitor): 839 super().visit(visitor) 840 visitor.visit_event( 841 self.name, self.info, self.ifcond, self.features, 842 self.arg_type, self.boxed) 843 844 845class QAPISchema: 846 def __init__(self, fname): 847 self.fname = fname 848 parser = QAPISchemaParser(fname) 849 exprs = check_exprs(parser.exprs) 850 self.docs = parser.docs 851 self._entity_list = [] 852 self._entity_dict = {} 853 self._module_dict = OrderedDict() 854 self._schema_dir = os.path.dirname(fname) 855 self._make_module(None) # built-ins 856 self._make_module(fname) 857 self._predefining = True 858 self._def_predefineds() 859 self._predefining = False 860 self._def_exprs(exprs) 861 self.check() 862 863 def _def_entity(self, ent): 864 # Only the predefined types are allowed to not have info 865 assert ent.info or self._predefining 866 self._entity_list.append(ent) 867 if ent.name is None: 868 return 869 # TODO reject names that differ only in '_' vs. '.' vs. '-', 870 # because they're liable to clash in generated C. 871 other_ent = self._entity_dict.get(ent.name) 872 if other_ent: 873 if other_ent.info: 874 where = QAPIError(other_ent.info, None, "previous definition") 875 raise QAPISemError( 876 ent.info, 877 "'%s' is already defined\n%s" % (ent.name, where)) 878 raise QAPISemError( 879 ent.info, "%s is already defined" % other_ent.describe()) 880 self._entity_dict[ent.name] = ent 881 882 def lookup_entity(self, name, typ=None): 883 ent = self._entity_dict.get(name) 884 if typ and not isinstance(ent, typ): 885 return None 886 return ent 887 888 def lookup_type(self, name): 889 return self.lookup_entity(name, QAPISchemaType) 890 891 def resolve_type(self, name, info, what): 892 typ = self.lookup_type(name) 893 if not typ: 894 if callable(what): 895 what = what(info) 896 raise QAPISemError( 897 info, "%s uses unknown type '%s'" % (what, name)) 898 return typ 899 900 def _module_name(self, fname): 901 if QAPISchemaModule.is_system_module(fname): 902 return fname 903 return os.path.relpath(fname, self._schema_dir) 904 905 def _make_module(self, fname): 906 name = self._module_name(fname) 907 if name not in self._module_dict: 908 self._module_dict[name] = QAPISchemaModule(name) 909 return self._module_dict[name] 910 911 def module_by_fname(self, fname): 912 name = self._module_name(fname) 913 assert name in self._module_dict 914 return self._module_dict[name] 915 916 def _def_include(self, expr, info, doc): 917 include = expr['include'] 918 assert doc is None 919 self._def_entity(QAPISchemaInclude(self._make_module(include), info)) 920 921 def _def_builtin_type(self, name, json_type, c_type): 922 self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type)) 923 # Instantiating only the arrays that are actually used would 924 # be nice, but we can't as long as their generated code 925 # (qapi-builtin-types.[ch]) may be shared by some other 926 # schema. 927 self._make_array_type(name, None) 928 929 def _def_predefineds(self): 930 for t in [('str', 'string', 'char' + POINTER_SUFFIX), 931 ('number', 'number', 'double'), 932 ('int', 'int', 'int64_t'), 933 ('int8', 'int', 'int8_t'), 934 ('int16', 'int', 'int16_t'), 935 ('int32', 'int', 'int32_t'), 936 ('int64', 'int', 'int64_t'), 937 ('uint8', 'int', 'uint8_t'), 938 ('uint16', 'int', 'uint16_t'), 939 ('uint32', 'int', 'uint32_t'), 940 ('uint64', 'int', 'uint64_t'), 941 ('size', 'int', 'uint64_t'), 942 ('bool', 'boolean', 'bool'), 943 ('any', 'value', 'QObject' + POINTER_SUFFIX), 944 ('null', 'null', 'QNull' + POINTER_SUFFIX)]: 945 self._def_builtin_type(*t) 946 self.the_empty_object_type = QAPISchemaObjectType( 947 'q_empty', None, None, None, None, None, [], None) 948 self._def_entity(self.the_empty_object_type) 949 950 qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', 951 'qbool'] 952 qtype_values = self._make_enum_members( 953 [{'name': n} for n in qtypes], None) 954 955 self._def_entity(QAPISchemaEnumType('QType', None, None, None, None, 956 qtype_values, 'QTYPE')) 957 958 def _make_features(self, features, info): 959 if features is None: 960 return [] 961 return [QAPISchemaFeature(f['name'], info, f.get('if')) 962 for f in features] 963 964 def _make_enum_members(self, values, info): 965 return [QAPISchemaEnumMember(v['name'], info, v.get('if')) 966 for v in values] 967 968 def _make_implicit_enum_type(self, name, info, ifcond, values): 969 # See also QAPISchemaObjectTypeMember.describe() 970 name = name + 'Kind' # reserved by check_defn_name_str() 971 self._def_entity(QAPISchemaEnumType( 972 name, info, None, ifcond, None, 973 self._make_enum_members(values, info), 974 None)) 975 return name 976 977 def _make_array_type(self, element_type, info): 978 name = element_type + 'List' # reserved by check_defn_name_str() 979 if not self.lookup_type(name): 980 self._def_entity(QAPISchemaArrayType(name, info, element_type)) 981 return name 982 983 def _make_implicit_object_type(self, name, info, ifcond, role, members): 984 if not members: 985 return None 986 # See also QAPISchemaObjectTypeMember.describe() 987 name = 'q_obj_%s-%s' % (name, role) 988 typ = self.lookup_entity(name, QAPISchemaObjectType) 989 if typ: 990 # The implicit object type has multiple users. This can 991 # happen only for simple unions' implicit wrapper types. 992 # Its ifcond should be the disjunction of its user's 993 # ifconds. Not implemented. Instead, we always pass the 994 # wrapped type's ifcond, which is trivially the same for all 995 # users. It's also necessary for the wrapper to compile. 996 # But it's not tight: the disjunction need not imply it. We 997 # may end up compiling useless wrapper types. 998 # TODO kill simple unions or implement the disjunction 999 1000 # pylint: disable=protected-access 1001 assert (ifcond or []) == typ._ifcond 1002 else: 1003 self._def_entity(QAPISchemaObjectType( 1004 name, info, None, ifcond, None, None, members, None)) 1005 return name 1006 1007 def _def_enum_type(self, expr, info, doc): 1008 name = expr['enum'] 1009 data = expr['data'] 1010 prefix = expr.get('prefix') 1011 ifcond = expr.get('if') 1012 features = self._make_features(expr.get('features'), info) 1013 self._def_entity(QAPISchemaEnumType( 1014 name, info, doc, ifcond, features, 1015 self._make_enum_members(data, info), prefix)) 1016 1017 def _make_member(self, name, typ, ifcond, features, info): 1018 optional = False 1019 if name.startswith('*'): 1020 name = name[1:] 1021 optional = True 1022 if isinstance(typ, list): 1023 assert len(typ) == 1 1024 typ = self._make_array_type(typ[0], info) 1025 return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond, 1026 self._make_features(features, info)) 1027 1028 def _make_members(self, data, info): 1029 return [self._make_member(key, value['type'], value.get('if'), 1030 value.get('features'), info) 1031 for (key, value) in data.items()] 1032 1033 def _def_struct_type(self, expr, info, doc): 1034 name = expr['struct'] 1035 base = expr.get('base') 1036 data = expr['data'] 1037 ifcond = expr.get('if') 1038 features = self._make_features(expr.get('features'), info) 1039 self._def_entity(QAPISchemaObjectType( 1040 name, info, doc, ifcond, features, base, 1041 self._make_members(data, info), 1042 None)) 1043 1044 def _make_variant(self, case, typ, ifcond, info): 1045 return QAPISchemaVariant(case, info, typ, ifcond) 1046 1047 def _make_simple_variant(self, case, typ, ifcond, info): 1048 if isinstance(typ, list): 1049 assert len(typ) == 1 1050 typ = self._make_array_type(typ[0], info) 1051 typ = self._make_implicit_object_type( 1052 typ, info, self.lookup_type(typ), 1053 'wrapper', [self._make_member('data', typ, None, None, info)]) 1054 return QAPISchemaVariant(case, info, typ, ifcond) 1055 1056 def _def_union_type(self, expr, info, doc): 1057 name = expr['union'] 1058 data = expr['data'] 1059 base = expr.get('base') 1060 ifcond = expr.get('if') 1061 features = self._make_features(expr.get('features'), info) 1062 tag_name = expr.get('discriminator') 1063 tag_member = None 1064 if isinstance(base, dict): 1065 base = self._make_implicit_object_type( 1066 name, info, ifcond, 1067 'base', self._make_members(base, info)) 1068 if tag_name: 1069 variants = [self._make_variant(key, value['type'], 1070 value.get('if'), info) 1071 for (key, value) in data.items()] 1072 members = [] 1073 else: 1074 variants = [self._make_simple_variant(key, value['type'], 1075 value.get('if'), info) 1076 for (key, value) in data.items()] 1077 enum = [{'name': v.name, 'if': v.ifcond} for v in variants] 1078 typ = self._make_implicit_enum_type(name, info, ifcond, enum) 1079 tag_member = QAPISchemaObjectTypeMember('type', info, typ, False) 1080 members = [tag_member] 1081 self._def_entity( 1082 QAPISchemaObjectType(name, info, doc, ifcond, features, 1083 base, members, 1084 QAPISchemaVariants( 1085 tag_name, info, tag_member, variants))) 1086 1087 def _def_alternate_type(self, expr, info, doc): 1088 name = expr['alternate'] 1089 data = expr['data'] 1090 ifcond = expr.get('if') 1091 features = self._make_features(expr.get('features'), info) 1092 variants = [self._make_variant(key, value['type'], value.get('if'), 1093 info) 1094 for (key, value) in data.items()] 1095 tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False) 1096 self._def_entity( 1097 QAPISchemaAlternateType(name, info, doc, ifcond, features, 1098 QAPISchemaVariants( 1099 None, info, tag_member, variants))) 1100 1101 def _def_command(self, expr, info, doc): 1102 name = expr['command'] 1103 data = expr.get('data') 1104 rets = expr.get('returns') 1105 gen = expr.get('gen', True) 1106 success_response = expr.get('success-response', True) 1107 boxed = expr.get('boxed', False) 1108 allow_oob = expr.get('allow-oob', False) 1109 allow_preconfig = expr.get('allow-preconfig', False) 1110 coroutine = expr.get('coroutine', False) 1111 ifcond = expr.get('if') 1112 features = self._make_features(expr.get('features'), info) 1113 if isinstance(data, OrderedDict): 1114 data = self._make_implicit_object_type( 1115 name, info, ifcond, 1116 'arg', self._make_members(data, info)) 1117 if isinstance(rets, list): 1118 assert len(rets) == 1 1119 rets = self._make_array_type(rets[0], info) 1120 self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, features, 1121 data, rets, 1122 gen, success_response, 1123 boxed, allow_oob, allow_preconfig, 1124 coroutine)) 1125 1126 def _def_event(self, expr, info, doc): 1127 name = expr['event'] 1128 data = expr.get('data') 1129 boxed = expr.get('boxed', False) 1130 ifcond = expr.get('if') 1131 features = self._make_features(expr.get('features'), info) 1132 if isinstance(data, OrderedDict): 1133 data = self._make_implicit_object_type( 1134 name, info, ifcond, 1135 'arg', self._make_members(data, info)) 1136 self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, features, 1137 data, boxed)) 1138 1139 def _def_exprs(self, exprs): 1140 for expr_elem in exprs: 1141 expr = expr_elem['expr'] 1142 info = expr_elem['info'] 1143 doc = expr_elem.get('doc') 1144 if 'enum' in expr: 1145 self._def_enum_type(expr, info, doc) 1146 elif 'struct' in expr: 1147 self._def_struct_type(expr, info, doc) 1148 elif 'union' in expr: 1149 self._def_union_type(expr, info, doc) 1150 elif 'alternate' in expr: 1151 self._def_alternate_type(expr, info, doc) 1152 elif 'command' in expr: 1153 self._def_command(expr, info, doc) 1154 elif 'event' in expr: 1155 self._def_event(expr, info, doc) 1156 elif 'include' in expr: 1157 self._def_include(expr, info, doc) 1158 else: 1159 assert False 1160 1161 def check(self): 1162 for ent in self._entity_list: 1163 ent.check(self) 1164 ent.connect_doc() 1165 ent.check_doc() 1166 for ent in self._entity_list: 1167 ent.set_module(self) 1168 1169 def visit(self, visitor): 1170 visitor.visit_begin(self) 1171 for mod in self._module_dict.values(): 1172 mod.visit(visitor) 1173 visitor.visit_end() 1174