diff mbox

[RFC,v2,07/47] qapi: Generate a nicer struct for flat unions

Message ID 55B7E15D.50902@redhat.com
State New
Headers show

Commit Message

Eric Blake July 28, 2015, 8:09 p.m. UTC
On 07/01/2015 02:21 PM, Markus Armbruster wrote:
> The struct generated for a flat union is weird: the members of its
> base are at the end, except for the union tag, which is renamed to
> 'kind' and put at the beginning.

The renaming to 'kind' was a bug waiting to happen.  Consider this
example, which is broken before your series:



leading to this compilation error during 'make check-unit':

In file included from tests/test-qmp-output-visitor.c:17:0:
tests/test-qapi-types.h:617:11: error: duplicate member ‘kind’
     char *kind;
           ^
tests/test-qapi-types.h:631:11: error: duplicate member ‘kind’
     char *kind;
           ^

Therefore, it might be worth mentioning that avoiding the rename to
'kind' is a bug fix, not just a nicer struct :)

Comments

Markus Armbruster July 29, 2015, 7:33 a.m. UTC | #1
Eric Blake <eblake@redhat.com> writes:

> On 07/01/2015 02:21 PM, Markus Armbruster wrote:
>> The struct generated for a flat union is weird: the members of its
>> base are at the end, except for the union tag, which is renamed to
>> 'kind' and put at the beginning.
>
> The renaming to 'kind' was a bug waiting to happen.  Consider this
> example, which is broken before your series:
>
> diff --git i/tests/qapi-schema/qapi-schema-test.json
> w/tests/qapi-schema/qapi-schema-test.json
> index c7eaa86..12c09e3 100644
> --- i/tests/qapi-schema/qapi-schema-test.json
> +++ w/tests/qapi-schema/qapi-schema-test.json
> @@ -37,7 +37,7 @@
>    'data': { 'string1': 'str', 'string2': 'str' } }
>
>  { 'struct': 'UserDefUnionBase',
> -  'data': { 'string': 'str', 'enum1': 'EnumOne' } }
> +  'data': { 'kind': 'str', 'enum1': 'EnumOne' } }
>
>  { 'union': 'UserDefFlatUnion',
>    'base': 'UserDefUnionBase',
>
>
> leading to this compilation error during 'make check-unit':
>
> In file included from tests/test-qmp-output-visitor.c:17:0:
> tests/test-qapi-types.h:617:11: error: duplicate member ‘kind’
>      char *kind;
>            ^
> tests/test-qapi-types.h:631:11: error: duplicate member ‘kind’
>      char *kind;
>            ^
>
> Therefore, it might be worth mentioning that avoiding the rename to
> 'kind' is a bug fix, not just a nicer struct :)

Cool!  I'll work (a variation of) this test case into my series.
Eric Blake July 29, 2015, 8:15 p.m. UTC | #2
On 07/29/2015 01:33 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> On 07/01/2015 02:21 PM, Markus Armbruster wrote:
>>> The struct generated for a flat union is weird: the members of its
>>> base are at the end, except for the union tag, which is renamed to
>>> 'kind' and put at the beginning.
>>

>> Therefore, it might be worth mentioning that avoiding the rename to
>> 'kind' is a bug fix, not just a nicer struct :)
> 
> Cool!  I'll work (a variation of) this test case into my series.

Another name collision bug: our code generates flat unions as:

struct BlockdevOptions {
    BlockdevDriver driver;
...
    /* End fields inherited from BlockdevOptionsBase. */
    /* union tag is BlockdevDriver driver */
    union {
        void *data;
        BlockdevOptionsArchipelago *archipelago;
...

which means that if we name any of the branches 'data' (that is, if
'data' is a member of the enum discriminator), things fail to compile.
We could probably fix that by naming our dummy branch '_data'.
Markus Armbruster July 30, 2015, 7:11 a.m. UTC | #3
Eric Blake <eblake@redhat.com> writes:

> On 07/29/2015 01:33 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> On 07/01/2015 02:21 PM, Markus Armbruster wrote:
>>>> The struct generated for a flat union is weird: the members of its
>>>> base are at the end, except for the union tag, which is renamed to
>>>> 'kind' and put at the beginning.
>>>
>
>>> Therefore, it might be worth mentioning that avoiding the rename to
>>> 'kind' is a bug fix, not just a nicer struct :)
>> 
>> Cool!  I'll work (a variation of) this test case into my series.
>
> Another name collision bug: our code generates flat unions as:
>
> struct BlockdevOptions {
>     BlockdevDriver driver;
> ...
>     /* End fields inherited from BlockdevOptionsBase. */
>     /* union tag is BlockdevDriver driver */
>     union {
>         void *data;
>         BlockdevOptionsArchipelago *archipelago;
> ...
>
> which means that if we name any of the branches 'data' (that is, if
> 'data' is a member of the enum discriminator), things fail to compile.
> We could probably fix that by naming our dummy branch '_data'.

I wonder whether member data is actually used.  I'll find out.
Eric Blake July 30, 2015, 2:14 p.m. UTC | #4
On 07/30/2015 01:11 AM, Markus Armbruster wrote:

>> Another name collision bug: our code generates flat unions as:
>>
>> struct BlockdevOptions {
>>     BlockdevDriver driver;
>> ...
>>     /* End fields inherited from BlockdevOptionsBase. */
>>     /* union tag is BlockdevDriver driver */
>>     union {
>>         void *data;
>>         BlockdevOptionsArchipelago *archipelago;
>> ...
>>
>> which means that if we name any of the branches 'data' (that is, if
>> 'data' is a member of the enum discriminator), things fail to compile.
>> We could probably fix that by naming our dummy branch '_data'.
> 
> I wonder whether member data is actually used.  I'll find out.

The dealloc visitor uses 'data' being non-null as a flag on whether to
deallocate the union even if the tag was invalid for some reason; or
more importantly, if parsing consumed the tag but then detected an error
while parsing the union, leaving the union branch partially allocated.
To avoid a leak, we have to deallocate the branch.

But if the tag was invalid, then why did we ever allocate the union in
the first place, and how do we prove we are calling the correct free-ing
function?  And if the tag is valid, why can't we just guarantee that the
union is 0-initialized and that deleting the branch will work through
the correct branch type instead of worrying about 'data'?

We still need a dummy member if it is valid to do { 'union':'Foo',
'data':{} } since C doesn't like empty unions, but an empty union seems
like something we may want to reject, at which point you are probably
right that deleting the data member altogether should work and still let
us recover from bad partial parses without a leak.
Markus Armbruster July 30, 2015, 3:44 p.m. UTC | #5
Eric Blake <eblake@redhat.com> writes:

> On 07/30/2015 01:11 AM, Markus Armbruster wrote:
>
>>> Another name collision bug: our code generates flat unions as:
>>>
>>> struct BlockdevOptions {
>>>     BlockdevDriver driver;
>>> ...
>>>     /* End fields inherited from BlockdevOptionsBase. */
>>>     /* union tag is BlockdevDriver driver */
>>>     union {
>>>         void *data;
>>>         BlockdevOptionsArchipelago *archipelago;
>>> ...
>>>
>>> which means that if we name any of the branches 'data' (that is, if
>>> 'data' is a member of the enum discriminator), things fail to compile.
>>> We could probably fix that by naming our dummy branch '_data'.
>> 
>> I wonder whether member data is actually used.  I'll find out.
>
> The dealloc visitor uses 'data' being non-null as a flag on whether to
> deallocate the union even if the tag was invalid for some reason; or
> more importantly, if parsing consumed the tag but then detected an error
> while parsing the union, leaving the union branch partially allocated.
> To avoid a leak, we have to deallocate the branch.
>
> But if the tag was invalid, then why did we ever allocate the union in
> the first place, and how do we prove we are calling the correct free-ing
> function?  And if the tag is valid, why can't we just guarantee that the
> union is 0-initialized and that deleting the branch will work through
> the correct branch type instead of worrying about 'data'?

Good questions.  Someone will have to review and fix this code.  Let's
add a FIXME so we don't forget.  Care to propose one?

> We still need a dummy member if it is valid to do { 'union':'Foo',
> 'data':{} } since C doesn't like empty unions, but an empty union seems
> like something we may want to reject, at which point you are probably
> right that deleting the data member altogether should work and still let
> us recover from bad partial parses without a leak.

Either we reject empty unions, or we detect them and add a dummy,
similar to what we do for structs since commit 83ecb22.  I figure simply
rejecting them is easier.
Eric Blake July 30, 2015, 11:08 p.m. UTC | #6
On 07/30/2015 09:44 AM, Markus Armbruster wrote:

>>>> which means that if we name any of the branches 'data' (that is, if
>>>> 'data' is a member of the enum discriminator), things fail to compile.
>>>> We could probably fix that by naming our dummy branch '_data'.
>>>
>>> I wonder whether member data is actually used.  I'll find out.
>>
>> The dealloc visitor uses 'data' being non-null as a flag on whether to
>> deallocate the union even if the tag was invalid for some reason; or
>> more importantly, if parsing consumed the tag but then detected an error
>> while parsing the union, leaving the union branch partially allocated.
>> To avoid a leak, we have to deallocate the branch.
>>
>> But if the tag was invalid, then why did we ever allocate the union in
>> the first place, and how do we prove we are calling the correct free-ing
>> function?  And if the tag is valid, why can't we just guarantee that the
>> union is 0-initialized and that deleting the branch will work through
>> the correct branch type instead of worrying about 'data'?
> 
> Good questions.  Someone will have to review and fix this code.  Let's
> add a FIXME so we don't forget.  Care to propose one?

Sure; see 12.6/47 (since that is close to several other patches adding
FIXME comments).
Markus Armbruster July 31, 2015, 9:46 a.m. UTC | #7
Markus Armbruster <armbru@redhat.com> writes:

> Eric Blake <eblake@redhat.com> writes:
>
>> On 07/01/2015 02:21 PM, Markus Armbruster wrote:
>>> The struct generated for a flat union is weird: the members of its
>>> base are at the end, except for the union tag, which is renamed to
>>> 'kind' and put at the beginning.
>>
>> The renaming to 'kind' was a bug waiting to happen.  Consider this
>> example, which is broken before your series:
>>
>> diff --git i/tests/qapi-schema/qapi-schema-test.json
>> w/tests/qapi-schema/qapi-schema-test.json
>> index c7eaa86..12c09e3 100644
>> --- i/tests/qapi-schema/qapi-schema-test.json
>> +++ w/tests/qapi-schema/qapi-schema-test.json
>> @@ -37,7 +37,7 @@
>>    'data': { 'string1': 'str', 'string2': 'str' } }
>>
>>  { 'struct': 'UserDefUnionBase',
>> -  'data': { 'string': 'str', 'enum1': 'EnumOne' } }
>> +  'data': { 'kind': 'str', 'enum1': 'EnumOne' } }
>>
>>  { 'union': 'UserDefFlatUnion',
>>    'base': 'UserDefUnionBase',
>>
>>
>> leading to this compilation error during 'make check-unit':
>>
>> In file included from tests/test-qmp-output-visitor.c:17:0:
>> tests/test-qapi-types.h:617:11: error: duplicate member ‘kind’
>>      char *kind;
>>            ^
>> tests/test-qapi-types.h:631:11: error: duplicate member ‘kind’
>>      char *kind;
>>            ^
>>
>> Therefore, it might be worth mentioning that avoiding the rename to
>> 'kind' is a bug fix, not just a nicer struct :)
>
> Cool!  I'll work (a variation of) this test case into my series.

I split the bug fix off this patch.  I put the test in the commit
message, because I feel it has little value as regression test going
forward.
diff mbox

Patch

diff --git i/tests/qapi-schema/qapi-schema-test.json
w/tests/qapi-schema/qapi-schema-test.json
index c7eaa86..12c09e3 100644
--- i/tests/qapi-schema/qapi-schema-test.json
+++ w/tests/qapi-schema/qapi-schema-test.json
@@ -37,7 +37,7 @@ 
   'data': { 'string1': 'str', 'string2': 'str' } }

 { 'struct': 'UserDefUnionBase',
-  'data': { 'string': 'str', 'enum1': 'EnumOne' } }
+  'data': { 'kind': 'str', 'enum1': 'EnumOne' } }

 { 'union': 'UserDefFlatUnion',
   'base': 'UserDefUnionBase',