qemu with hax to log dma reads & writes jcs.org/2018/11/12/vfio

qapi: Better error messages for duplicated expressions

The previous commit demonstrated that the generator overlooked
duplicate expressions:
- a complex type or command reusing a built-in type name
- redeclaration of a type name, whether by the same or different
metatype
- redeclaration of a command or event
- collision of a type with implicit 'Kind' enum for a union
- collision with an implicit MAX enum constant

Since the c_type() function in the generator treats all names
as being in the same namespace, this patch adds a global array
to track all known names and their source, to prevent collisions
before it can cause further problems. While valid .json files
won't trigger any of these cases, we might as well be nicer to
developers that make a typo while trying to add new QAPI code.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>

authored by

Eric Blake and committed by
Markus Armbruster
4dc2e690 cfdd5bca

+70 -54
+49 -14
scripts/qapi.py
··· 32 32 'size': 'QTYPE_QINT', 33 33 } 34 34 35 + enum_types = [] 36 + struct_types = [] 37 + union_types = [] 38 + events = [] 39 + all_names = {} 40 + 35 41 def error_path(parent): 36 42 res = "" 37 43 while parent: ··· 256 262 return find_enum(discriminator_type) 257 263 258 264 def check_event(expr, expr_info): 265 + global events 266 + name = expr['event'] 259 267 params = expr.get('data') 268 + 269 + if name.upper() == 'MAX': 270 + raise QAPIExprError(expr_info, "Event name 'MAX' cannot be created") 271 + events.append(name) 272 + 260 273 if params: 261 274 for argname, argentry, optional, structured in parse_args(params): 262 275 if structured: ··· 430 443 431 444 432 445 def parse_schema(input_file): 446 + global all_names 447 + exprs = [] 448 + 433 449 # First pass: read entire file into memory 434 450 try: 435 451 schema = QAPISchema(open(input_file, "r")) ··· 437 453 print >>sys.stderr, e 438 454 exit(1) 439 455 440 - exprs = [] 441 - 442 456 try: 443 457 # Next pass: learn the types and check for valid expression keys. At 444 458 # this point, top-level 'include' has already been flattened. 459 + for builtin in builtin_types.keys(): 460 + all_names[builtin] = 'built-in' 445 461 for expr_elem in schema.exprs: 446 462 expr = expr_elem['expr'] 463 + info = expr_elem['info'] 447 464 if expr.has_key('enum'): 448 465 check_keys(expr_elem, 'enum', ['data']) 449 - add_enum(expr['enum'], expr['data']) 466 + add_enum(expr['enum'], info, expr['data']) 450 467 elif expr.has_key('union'): 451 468 check_keys(expr_elem, 'union', ['data'], 452 469 ['base', 'discriminator']) 453 - add_union(expr) 470 + add_union(expr, info) 454 471 elif expr.has_key('alternate'): 455 472 check_keys(expr_elem, 'alternate', ['data']) 473 + add_name(expr['alternate'], info, 'alternate') 456 474 elif expr.has_key('type'): 457 475 check_keys(expr_elem, 'type', ['data'], ['base']) 458 - add_struct(expr) 476 + add_struct(expr, info) 459 477 elif expr.has_key('command'): 460 478 check_keys(expr_elem, 'command', [], 461 479 ['data', 'returns', 'gen', 'success-response']) 480 + add_name(expr['command'], info, 'command') 462 481 elif expr.has_key('event'): 463 482 check_keys(expr_elem, 'event', [], ['data']) 483 + add_name(expr['event'], info, 'event') 464 484 else: 465 485 raise QAPIExprError(expr_elem['info'], 466 486 "Expression is missing metatype") ··· 471 491 expr = expr_elem['expr'] 472 492 if expr.has_key('union'): 473 493 if not discriminator_find_enum_define(expr): 474 - add_enum('%sKind' % expr['union']) 494 + add_enum('%sKind' % expr['union'], expr_elem['info'], 495 + implicit=True) 475 496 elif expr.has_key('alternate'): 476 - add_enum('%sKind' % expr['alternate']) 497 + add_enum('%sKind' % expr['alternate'], expr_elem['info'], 498 + implicit=True) 477 499 478 500 # Final pass - validate that exprs make sense 479 501 check_exprs(schema) ··· 567 589 return c_list_type(name[0]) 568 590 return name 569 591 570 - enum_types = [] 571 - struct_types = [] 572 - union_types = [] 592 + def add_name(name, info, meta, implicit = False): 593 + global all_names 594 + if name in all_names: 595 + raise QAPIExprError(info, 596 + "%s '%s' is already defined" 597 + % (all_names[name], name)) 598 + if not implicit and name[-4:] == 'Kind': 599 + raise QAPIExprError(info, 600 + "%s '%s' should not end in 'Kind'" 601 + % (meta, name)) 602 + all_names[name] = meta 573 603 574 - def add_struct(definition): 604 + def add_struct(definition, info): 575 605 global struct_types 606 + name = definition['type'] 607 + add_name(name, info, 'struct') 576 608 struct_types.append(definition) 577 609 578 610 def find_struct(name): ··· 582 614 return struct 583 615 return None 584 616 585 - def add_union(definition): 617 + def add_union(definition, info): 586 618 global union_types 619 + name = definition['union'] 620 + add_name(name, info, 'union') 587 621 union_types.append(definition) 588 622 589 623 def find_union(name): ··· 593 627 return union 594 628 return None 595 629 596 - def add_enum(name, enum_values = None): 630 + def add_enum(name, info, enum_values = None, implicit = False): 597 631 global enum_types 632 + add_name(name, info, 'enum', implicit) 598 633 enum_types.append({"enum_name": name, "enum_values": enum_values}) 599 634 600 635 def find_enum(name): ··· 636 671 return name 637 672 elif name == None or len(name) == 0: 638 673 return 'void' 639 - elif name == name.upper(): 674 + elif name in events: 640 675 return '%sEvent *%s' % (camel_case(name), eatspace) 641 676 else: 642 677 return '%s *%s' % (name, eatspace)
+1
tests/qapi-schema/command-int.err
··· 1 + tests/qapi-schema/command-int.json:2: built-in 'int' is already defined
+1 -1
tests/qapi-schema/command-int.exit
··· 1 - 0 1 + 1
+1 -1
tests/qapi-schema/command-int.json
··· 1 - # FIXME: we should reject collisions between commands and types 1 + # we reject collisions between commands and types 2 2 { 'command': 'int', 'data': { 'character': 'str' }, 3 3 'returns': { 'value': 'int' } }
-3
tests/qapi-schema/command-int.out
··· 1 - [OrderedDict([('command', 'int'), ('data', OrderedDict([('character', 'str')])), ('returns', OrderedDict([('value', 'int')]))])] 2 - [] 3 - []
+1
tests/qapi-schema/enum-union-clash.err
··· 1 + tests/qapi-schema/enum-union-clash.json:2: enum 'UnionKind' should not end in 'Kind'
+1 -1
tests/qapi-schema/enum-union-clash.exit
··· 1 - 0 1 + 1
+1 -1
tests/qapi-schema/enum-union-clash.json
··· 1 - # FIXME: we should reject types that would conflict with implicit union enum 1 + # we reject types that would conflict with implicit union enum 2 2 { 'enum': 'UnionKind', 'data': [ 'oops' ] } 3 3 { 'union': 'Union', 4 4 'data': { 'a': 'int' } }
-5
tests/qapi-schema/enum-union-clash.out
··· 1 - [OrderedDict([('enum', 'UnionKind'), ('data', ['oops'])]), 2 - OrderedDict([('union', 'Union'), ('data', OrderedDict([('a', 'int')]))])] 3 - [{'enum_name': 'UnionKind', 'enum_values': ['oops']}, 4 - {'enum_name': 'UnionKind', 'enum_values': None}] 5 - []
+1
tests/qapi-schema/event-max.err
··· 1 + tests/qapi-schema/event-max.json:2: Event name 'MAX' cannot be created
+1 -1
tests/qapi-schema/event-max.exit
··· 1 - 0 1 + 1
+1 -1
tests/qapi-schema/event-max.json
··· 1 - # FIXME: an event named 'MAX' would conflict with implicit C enum 1 + # an event named 'MAX' would conflict with implicit C enum 2 2 { 'event': 'MAX' }
-3
tests/qapi-schema/event-max.out
··· 1 - [OrderedDict([('event', 'MAX')])] 2 - [] 3 - []
+1
tests/qapi-schema/redefined-builtin.err
··· 1 + tests/qapi-schema/redefined-builtin.json:2: built-in 'size' is already defined
+1 -1
tests/qapi-schema/redefined-builtin.exit
··· 1 - 0 1 + 1
+1 -1
tests/qapi-schema/redefined-builtin.json
··· 1 - # FIXME: we should reject types that duplicate builtin names 1 + # we reject types that duplicate builtin names 2 2 { 'type': 'size', 'data': { 'myint': 'size' } }
-3
tests/qapi-schema/redefined-builtin.out
··· 1 - [OrderedDict([('type', 'size'), ('data', OrderedDict([('myint', 'size')]))])] 2 - [] 3 - [OrderedDict([('type', 'size'), ('data', OrderedDict([('myint', 'size')]))])]
+1
tests/qapi-schema/redefined-command.err
··· 1 + tests/qapi-schema/redefined-command.json:3: command 'foo' is already defined
+1 -1
tests/qapi-schema/redefined-command.exit
··· 1 - 0 1 + 1
+1 -1
tests/qapi-schema/redefined-command.json
··· 1 - # FIXME: we should reject commands defined more than once 1 + # we reject commands defined more than once 2 2 { 'command': 'foo', 'data': { 'one': 'str' } } 3 3 { 'command': 'foo', 'data': { '*two': 'str' } }
-4
tests/qapi-schema/redefined-command.out
··· 1 - [OrderedDict([('command', 'foo'), ('data', OrderedDict([('one', 'str')]))]), 2 - OrderedDict([('command', 'foo'), ('data', OrderedDict([('*two', 'str')]))])] 3 - [] 4 - []
+1
tests/qapi-schema/redefined-event.err
··· 1 + tests/qapi-schema/redefined-event.json:3: event 'EVENT_A' is already defined
+1 -1
tests/qapi-schema/redefined-event.exit
··· 1 - 0 1 + 1
+1 -1
tests/qapi-schema/redefined-event.json
··· 1 - # FIXME: we should reject duplicate events 1 + # we reject duplicate events 2 2 { 'event': 'EVENT_A', 'data': { 'myint': 'int' } } 3 3 { 'event': 'EVENT_A', 'data': { 'myint': 'int' } }
-4
tests/qapi-schema/redefined-event.out
··· 1 - [OrderedDict([('event', 'EVENT_A'), ('data', OrderedDict([('myint', 'int')]))]), 2 - OrderedDict([('event', 'EVENT_A'), ('data', OrderedDict([('myint', 'int')]))])] 3 - [] 4 - []
+1
tests/qapi-schema/redefined-type.err
··· 1 + tests/qapi-schema/redefined-type.json:3: struct 'foo' is already defined
+1 -1
tests/qapi-schema/redefined-type.exit
··· 1 - 0 1 + 1
+1 -1
tests/qapi-schema/redefined-type.json
··· 1 - # FIXME: we should reject types defined more than once 1 + # we reject types defined more than once 2 2 { 'type': 'foo', 'data': { 'one': 'str' } } 3 3 { 'enum': 'foo', 'data': [ 'two' ] }
-4
tests/qapi-schema/redefined-type.out
··· 1 - [OrderedDict([('type', 'foo'), ('data', OrderedDict([('one', 'str')]))]), 2 - OrderedDict([('enum', 'foo'), ('data', ['two'])])] 3 - [{'enum_name': 'foo', 'enum_values': ['two']}] 4 - [OrderedDict([('type', 'foo'), ('data', OrderedDict([('one', 'str')]))])]