• E
    qapi: Simplify visiting of alternate types · 0426d53c
    Eric Blake 提交于
    Previously, working with alternates required two lookup arrays
    and some indirection: for type Foo, we created Foo_qtypes[]
    which maps each qtype to a value of the generated FooKind enum,
    then look up that value in FooKind_lookup[] like we do for other
    union types.
    
    This has a couple of subtle bugs.  First, the generator was
    creating a call with a parameter '(int *) &(*obj)->type' where
    type is an enum type; this is unsafe if the compiler chooses
    to store the enum type in a different size than int, where
    assigning through the wrong size pointer can corrupt data or
    cause a SIGBUS.
    
    Related bug, not not fixed in this patch: qapi-visit.py's
    gen_visit_enum() generates a cast of its enum * argument to
    int *. Marked FIXME.
    
    Second, since the values of the FooKind enum start at zero, all
    entries of the Foo_qtypes[] array that were not explicitly
    initialized will map to the same branch of the union as the
    first member of the alternate, rather than triggering a desired
    failure in visit_get_next_type().  Fortunately, the bug seldom
    bites; the very next thing the input visitor does is try to
    parse the incoming JSON with the wrong parser, which normally
    fails; the output visitor is not used with a C struct in that
    state, and the dealloc visitor has nothing to clean up (so
    there is no leak).
    
    However, the second bug IS observable in one case: parsing an
    integer causes unusual behavior in an alternate that contains
    at least a 'number' member but no 'int' member, because the
    'number' parser accepts QTYPE_QINT in addition to the expected
    QTYPE_QFLOAT (that is, since 'int' is not a member, the type
    QTYPE_QINT accidentally maps to FooKind 0; if this enum value
    is the 'number' branch the integer parses successfully, but if
    the 'number' branch is not first, some other branch tries to
    parse the integer and rejects it).  A later patch will worry
    about fixing alternates to always parse all inputs that a
    non-alternate 'number' would accept, for now this is still
    marked FIXME in the updated test-qmp-input-visitor.c, to
    merely point out that new undesired behavior of 'ans' matches
    the existing undesired behavior of 'asn'.
    
    This patch fixes the default-initialization bug by deleting the
    indirection, and modifying get_next_type() to directly assign a
    QTypeCode parameter.  This in turn fixes the type-casting bug,
    as we are no longer casting a pointer to enum to a questionable
    size. There is no longer a need to generate an implicit FooKind
    enum associated with the alternate type (since the QMP wire
    format never uses the stringized counterparts of the C union
    member names).  Since the updated visit_get_next_type() does not
    know which qtypes are expected, the generated visitor is
    modified to generate an error statement if an unexpected type is
    encountered.
    
    Callers now have to know the QTYPE_* mapping when looking at the
    discriminator; but so far, only the testsuite was even using the
    C struct of an alternate types.  I considered the possibility of
    keeping the internal enum FooKind, but initialized differently
    than most generated arrays, as in:
      typedef enum FooKind {
          FOO_KIND_A = QTYPE_QDICT,
          FOO_KIND_B = QTYPE_QINT,
      } FooKind;
    to create nicer aliases for knowing when to use foo->a or foo->b
    when inspecting foo->type; but it turned out to add too much
    complexity, especially without a client.
    
    There is a user-visible side effect to this change, but I
    consider it to be an improvement. Previously,
    the invalid QMP command:
      {"execute":"blockdev-add", "arguments":{"options":
        {"driver":"raw", "id":"a", "file":true}}}
    failed with:
      {"error": {"class": "GenericError",
        "desc": "Invalid parameter type for 'file', expected: QDict"}}
    (visit_get_next_type() succeeded, and the error comes from the
    visit_type_BlockdevOptions() expecting {}; there is no mention of
    the fact that a string would also work).  Now it fails with:
      {"error": {"class": "GenericError",
        "desc": "Invalid parameter type for 'file', expected: BlockdevRef"}}
    (the error when the next type doesn't match any expected types for
    the overall alternate).
    Signed-off-by: NEric Blake <eblake@redhat.com>
    Message-Id: <1449033659-25497-5-git-send-email-eblake@redhat.com>
    Signed-off-by: NMarkus Armbruster <armbru@redhat.com>
    0426d53c
visitor.h 2.9 KB