All the mail mirrored from lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add
@ 2015-09-21 21:57 Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 01/46] qapi: Sort qapi-schema tests Eric Blake
                   ` (46 more replies)
  0 siblings, 47 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

No longer RFC, but depends on Markus' pull-qapi-2015-09-21 tag
(https://lists.gnu.org/archive/html/qemu-devel/2015-09/msg05257.html)
which had not been merged as of this email.

Also available at this location (although I may rebase it):
git fetch git://repo.or.cz/qemu/ericb.git qapi
http://repo.or.cz/qemu/ericb.git/shortlog/refs/heads/qapi

v1 was here:
https://lists.gnu.org/archive/html/qemu-devel/2015-07/msg05266.html
https://lists.gnu.org/archive/html/qemu-devel/2015-07/msg05325.html

In v2:
https://lists.gnu.org/archive/html/qemu-devel/2015-08/msg00900.html
rebase to Markus' v3 series
rework how comments are emitted for fields inherited from base
additional patches added for deleting colliding 'void *data'
documentation updates to match code changes

In v3:
https://lists.gnu.org/archive/html/qemu-devel/2015-08/msg02059.html
redo cleanup of dealloc of partial struct
add patches to make all visit_type_*() avoid leaks on failure
add patches to allow boxed command arguments and events

In v4:
https://lists.gnu.org/archive/html/qemu-devel/2015-09/msg02580.html
add some more clean up patches
rebase to Markus' recent work
pull in part of Zoltán's work to make netdev_add a flat union,
further enhancing it to be introspectible

I might be able to rearrange some of these patches, or separate
it into smaller independent series, if requested; but I'm
posting now to get review started.

In v5:
I _did_ rearrange patches to try and group related features:

1-2: Groundwork cleanups
3-5: Add more test cases
6-16: Front-end cleanups
17-18: Introspection output cleanups
19-20: 'alternate' type cleanups
21-29: qapi visitor cleanups
30-45: qapi-ify netdev_add
46: add qapi shorthand for flat unions

Lots of fixes based on additional testing, and rebased to
track other changes that happened in the meantime.  The series
is huge; I can split off smaller portions as requested.

Although v1-v4 didn't get much review, here's a backport-diff
to v4 in case it proves useful.

Key:
[----] : patches are identical
[####] : number of functional differences between upstream/downstream patch
[down] : patch is downstream-only
The flags [FC] indicate (F)unctional and (C)ontextual differences, respectively

001/46:[down] 'qapi: Sort qapi-schema tests'
002/46:[0006] [FC] 'qapi: Clean up qapi.py per pep8'
003/46:[down] 'qapi: Test for C member name collisions'
004/46:[0006] [FC] 'qapi: Add tests for empty unions'
005/46:[0018] [FC] 'qapi: Test use of 'number' within alternates'
006/46:[down] 'qapi: Improve 'include' error message'
007/46:[----] [-C] 'qapi: Don't pass pre-existing error to later call'
008/46:[down] 'qapi: Reuse code for flat union base validation'
009/46:[0126] [FC] 'qapi: Use consistent generated code patterns'
010/46:[down] 'qapi: Merge generation of per-member visits'
011/46:[down] 'qapi: Don't use info as witness of implicit object type'
012/46:[down] 'qapi: Track location that created an implicit type'
013/46:[down] 'qapi: Track owner of each object member'
014/46:[down] 'qapi: Detect collisions in C member names'
015/46:[down] 'qapi: Defer duplicate member checks to schema check()'
016/46:[down] 'qapi: Detect base class loops'
017/46:[----] [--] 'qapi: Provide nicer array names in introspection'
018/46:[down] 'qapi-introspect: Guarantee particular sorting'
019/46:[0053] [FC] 'qapi: Simplify visiting of alternate types'
020/46:[0005] [FC] 'qapi: Fix alternates that accept 'number' but not 'int''
021/46:[down] 'qmp: Fix reference-counting of qnull on empty output visit'
022/46:[down] 'qapi: Don't abuse stack to track qmp-output root'
023/46:[0007] [FC] 'qapi: Remove dead visitor code'
024/46:[0024] [FC] 'qapi: Document visitor interfaces'
025/46:[----] [--] 'qapi: Plug leaks in test-qmp-input-visitor'
026/46:[----] [-C] 'qapi: Test failure in middle of array parse'
027/46:[down] 'qapi: Simplify visits of optional fields'
028/46:[0023] [FC] 'qapi: Rework deallocation of partial struct'
029/46:[0016] [FC] 'qapi: Change visit_type_FOO() to no longer return partial objects'
030/46:[----] [--] 'net: use Netdev instead of NetClientOptions in client init'
031/46:[0018] [FC] 'qapi: use 'type' in generated C code to match QMP union wire form'
032/46:[0008] [FC] 'qapi: Hide tag_name data member of variants'
033/46:[----] [--] 'vnc: hoist allocation of VncBasicInfo to callers'
034/46:[0022] [FC] 'qapi: Unbox base members'
035/46:[0024] [FC] 'qapi-visit: Remove redundant functions for flat union base'
036/46:[----] [--] 'qapi: Avoid use of 'data' member of qapi unions'
037/46:[0002] [FC] 'qapi: Forbid empty unions and useless alternates'
038/46:[0012] [FC] 'qapi: Drop useless 'data' member of unions'
039/46:[0083] [FC] 'qapi: Plumb in 'box' to qapi generator lower levels'
040/46:[0010] [FC] 'qapi: Implement boxed structs for commands/events'
041/46:[0008] [FC] 'qapi: Support boxed unions'
042/46:[down] 'qapi: support implicit structs in OptsVisitor'
043/46:[----] [-C] 'qapi: Change Netdev into a flat union'
044/46:[----] [--] 'net: Use correct type for bool flag'
045/46:[----] [--] 'net: Complete qapi-fication of netdev_add'
046/46:[down] 'qapi: Allow anonymous base for flat union'

Eric Blake (43):
  qapi: Sort qapi-schema tests
  qapi: Clean up qapi.py per pep8
  qapi: Test for C member name collisions
  qapi: Add tests for empty unions
  qapi: Test use of 'number' within alternates
  qapi: Improve 'include' error message
  qapi: Don't pass pre-existing error to later call
  qapi: Reuse code for flat union base validation
  qapi: Use consistent generated code patterns
  qapi: Merge generation of per-member visits
  qapi: Don't use info as witness of implicit object type
  qapi: Track location that created an implicit type
  qapi: Track owner of each object member
  qapi: Detect collisions in C member names
  qapi: Defer duplicate member checks to schema check()
  qapi: Detect base class loops
  qapi: Provide nicer array names in introspection
  qapi-introspect: Guarantee particular sorting
  qapi: Simplify visiting of alternate types
  qapi: Fix alternates that accept 'number' but not 'int'
  qmp: Fix reference-counting of qnull on empty output visit
  qapi: Don't abuse stack to track qmp-output root
  qapi: Remove dead visitor code
  qapi: Document visitor interfaces
  qapi: Plug leaks in test-qmp-input-visitor
  qapi: Test failure in middle of array parse
  qapi: Simplify visits of optional fields
  qapi: Rework deallocation of partial struct
  qapi: Change visit_type_FOO() to no longer return partial objects
  qapi: use 'type' in generated C code to match QMP union wire form
  qapi: Hide tag_name data member of variants
  vnc: hoist allocation of VncBasicInfo to callers
  qapi: Unbox base members
  qapi-visit: Remove redundant functions for flat union base
  qapi: Avoid use of 'data' member of qapi unions
  qapi: Forbid empty unions and useless alternates
  qapi: Drop useless 'data' member of unions
  qapi: Plumb in 'box' to qapi generator lower levels
  qapi: Implement boxed structs for commands/events
  qapi: Support boxed unions
  net: Use correct type for bool flag
  net: Complete qapi-fication of netdev_add
  qapi: Allow anonymous base for flat union

Kővágó, Zoltán (3):
  net: use Netdev instead of NetClientOptions in client init
  qapi: support implicit structs in OptsVisitor
  qapi: Change Netdev into a flat union

 block/qcow2.c                                      |   2 +-
 block/vmdk.c                                       |   2 +-
 blockdev.c                                         |  34 +-
 docs/qapi-code-gen.txt                             |  98 ++--
 hmp.c                                              |  18 +-
 hw/arm/musicpal.c                                  |   2 +-
 hw/core/qdev-properties-system.c                   |   2 +-
 hw/input/hid.c                                     |   2 +-
 hw/input/ps2.c                                     |   2 +-
 hw/input/virtio-input-hid.c                        |   2 +-
 hw/mem/pc-dimm.c                                   |   2 +-
 hw/net/allwinner_emac.c                            |   2 +-
 hw/net/cadence_gem.c                               |   2 +-
 hw/net/dp8393x.c                                   |   2 +-
 hw/net/e1000.c                                     |   2 +-
 hw/net/eepro100.c                                  |   2 +-
 hw/net/etraxfs_eth.c                               |   2 +-
 hw/net/fsl_etsec/etsec.c                           |   2 +-
 hw/net/imx_fec.c                                   |   2 +-
 hw/net/lan9118.c                                   |   2 +-
 hw/net/lance.c                                     |   2 +-
 hw/net/mcf_fec.c                                   |   2 +-
 hw/net/milkymist-minimac2.c                        |   2 +-
 hw/net/mipsnet.c                                   |   2 +-
 hw/net/ne2000-isa.c                                |   2 +-
 hw/net/ne2000.c                                    |   2 +-
 hw/net/opencores_eth.c                             |   2 +-
 hw/net/pcnet-pci.c                                 |   2 +-
 hw/net/rocker/rocker_fp.c                          |   2 +-
 hw/net/rtl8139.c                                   |   2 +-
 hw/net/smc91c111.c                                 |   2 +-
 hw/net/spapr_llan.c                                |   2 +-
 hw/net/stellaris_enet.c                            |   2 +-
 hw/net/vhost_net.c                                 |  18 +-
 hw/net/virtio-net.c                                |   6 +-
 hw/net/vmxnet3.c                                   |   2 +-
 hw/net/xen_nic.c                                   |   2 +-
 hw/net/xgmac.c                                     |   2 +-
 hw/net/xilinx_axienet.c                            |   2 +-
 hw/net/xilinx_ethlite.c                            |   2 +-
 hw/usb/dev-network.c                               |   4 +-
 include/net/net.h                                  |   7 +-
 include/qapi/visitor-impl.h                        |  60 ++-
 include/qapi/visitor.h                             | 225 ++++++++-
 monitor.c                                          |  14 +-
 net/clients.h                                      |  20 +-
 net/dump.c                                         |   8 +-
 net/hub.c                                          |  24 +-
 net/l2tpv3.c                                       |   8 +-
 net/net.c                                          | 180 +++----
 net/netmap.c                                       |   6 +-
 net/slirp.c                                        |   8 +-
 net/socket.c                                       |  10 +-
 net/tap-win32.c                                    |   8 +-
 net/tap.c                                          |  28 +-
 net/vde.c                                          |   8 +-
 net/vhost-user.c                                   |  14 +-
 numa.c                                             |   4 +-
 qapi-schema.json                                   |  72 ++-
 qapi/introspect.json                               |  22 +-
 qapi/opts-visitor.c                                |  17 +-
 qapi/qapi-dealloc-visitor.c                        |  26 -
 qapi/qapi-visit-core.c                             | 164 +++----
 qapi/qmp-input-visitor.c                           |  11 +-
 qapi/qmp-output-visitor.c                          |  70 +--
 qapi/string-input-visitor.c                        |   3 +-
 qemu-char.c                                        |  24 +-
 qmp-commands.hx                                    |   2 +-
 scripts/qapi-commands.py                           | 123 +++--
 scripts/qapi-event.py                              |  92 ++--
 scripts/qapi-introspect.py                         |  21 +-
 scripts/qapi-types.py                              |  86 +---
 scripts/qapi-visit.py                              | 223 +++++----
 scripts/qapi.py                                    | 528 ++++++++++++++-------
 tests/Makefile                                     | 170 +++++--
 tests/qapi-schema/alternate-empty.err              |   1 +
 ...at-union-bad-base.exit => alternate-empty.exit} |   0
 tests/qapi-schema/alternate-empty.json             |   2 +
 ...flat-union-bad-base.out => alternate-empty.out} |   0
 tests/qapi-schema/alternate-good.out               |   1 -
 tests/qapi-schema/alternate-nested.json            |   2 +-
 tests/qapi-schema/alternate-unknown.json           |   2 +-
 tests/qapi-schema/args-bad-box.err                 |   1 +
 tests/qapi-schema/args-bad-box.exit                |   1 +
 tests/qapi-schema/args-bad-box.json                |   2 +
 tests/qapi-schema/args-bad-box.out                 |   0
 tests/qapi-schema/args-box-anon.err                |   1 +
 tests/qapi-schema/args-box-anon.exit               |   1 +
 tests/qapi-schema/args-box-anon.json               |   2 +
 tests/qapi-schema/args-box-anon.out                |   0
 tests/qapi-schema/args-box-empty.err               |   1 +
 tests/qapi-schema/args-box-empty.exit              |   1 +
 tests/qapi-schema/args-box-empty.json              |   2 +
 tests/qapi-schema/args-box-empty.out               |   0
 tests/qapi-schema/args-member-array.out            |   2 +-
 tests/qapi-schema/args-name-clash.err              |   1 +
 tests/qapi-schema/args-name-clash.exit             |   1 +
 tests/qapi-schema/args-name-clash.json             |   2 +
 tests/qapi-schema/args-name-clash.out              |   0
 tests/qapi-schema/args-union.err                   |   2 +-
 tests/qapi-schema/args-union.json                  |   3 +-
 tests/qapi-schema/base-cycle.err                   |   1 +
 tests/qapi-schema/base-cycle.exit                  |   1 +
 tests/qapi-schema/base-cycle.json                  |   3 +
 tests/qapi-schema/base-cycle.out                   |   0
 tests/qapi-schema/event-case.out                   |   1 +
 tests/qapi-schema/flat-union-bad-base.err          |   1 -
 tests/qapi-schema/flat-union-bad-base.json         |  13 -
 tests/qapi-schema/flat-union-base-any.err          |   2 +-
 tests/qapi-schema/flat-union-base-union.err        |   2 +-
 tests/qapi-schema/flat-union-branch-clash.err      |   2 +-
 tests/qapi-schema/flat-union-branch-clash.json     |   2 +-
 tests/qapi-schema/flat-union-branch-clash2.err     |   1 +
 tests/qapi-schema/flat-union-branch-clash2.exit    |   1 +
 tests/qapi-schema/flat-union-branch-clash2.json    |  14 +
 tests/qapi-schema/flat-union-branch-clash2.out     |   0
 tests/qapi-schema/flat-union-cycle.err             |   1 +
 tests/qapi-schema/flat-union-cycle.exit            |   1 +
 tests/qapi-schema/flat-union-cycle.json            |   6 +
 tests/qapi-schema/flat-union-cycle.out             |   0
 tests/qapi-schema/flat-union-empty.err             |   1 +
 tests/qapi-schema/flat-union-empty.exit            |   1 +
 tests/qapi-schema/flat-union-empty.json            |   4 +
 tests/qapi-schema/flat-union-empty.out             |   0
 tests/qapi-schema/flat-union-inline.err            |   2 +-
 tests/qapi-schema/flat-union-inline.json           |   4 +-
 tests/qapi-schema/flat-union-no-base.err           |   2 +-
 tests/qapi-schema/ident-with-escape.out            |   2 +-
 tests/qapi-schema/include-non-file.err             |   2 +-
 tests/qapi-schema/include-non-file.json            |   2 +-
 tests/qapi-schema/indented-expr.out                |   4 +-
 tests/qapi-schema/qapi-schema-test.json            |  25 +-
 tests/qapi-schema/qapi-schema-test.out             |  59 ++-
 tests/qapi-schema/returns-int.out                  |   2 +-
 tests/qapi-schema/struct-base-clash-deep.err       |   2 +-
 tests/qapi-schema/struct-base-clash.err            |   2 +-
 tests/qapi-schema/test-qapi.py                     |  14 +-
 tests/qapi-schema/union-clash.err                  |   1 +
 tests/qapi-schema/union-clash.exit                 |   1 +
 tests/qapi-schema/union-clash.json                 |   3 +
 tests/qapi-schema/union-clash.out                  |   0
 tests/qapi-schema/union-empty.err                  |   1 +
 tests/qapi-schema/union-empty.exit                 |   1 +
 tests/qapi-schema/union-empty.json                 |   2 +
 tests/qapi-schema/union-empty.out                  |   0
 tests/qapi-schema/union-invalid-base.err           |   2 +-
 tests/test-qmp-commands.c                          |  40 +-
 tests/test-qmp-event.c                             |   8 +-
 tests/test-qmp-input-visitor.c                     | 198 +++++++-
 tests/test-qmp-output-visitor.c                    |  41 +-
 tests/test-visitor-serialization.c                 |  14 +-
 tpm.c                                              |   2 +-
 ui/input-keymap.c                                  |  10 +-
 ui/input-legacy.c                                  |   2 +-
 ui/input.c                                         |  24 +-
 ui/spice-core.c                                    |  23 +-
 ui/vnc.c                                           |  64 +--
 util/qemu-sockets.c                                |  12 +-
 158 files changed, 1945 insertions(+), 1249 deletions(-)
 create mode 100644 tests/qapi-schema/alternate-empty.err
 rename tests/qapi-schema/{flat-union-bad-base.exit => alternate-empty.exit} (100%)
 create mode 100644 tests/qapi-schema/alternate-empty.json
 rename tests/qapi-schema/{flat-union-bad-base.out => alternate-empty.out} (100%)
 create mode 100644 tests/qapi-schema/args-bad-box.err
 create mode 100644 tests/qapi-schema/args-bad-box.exit
 create mode 100644 tests/qapi-schema/args-bad-box.json
 create mode 100644 tests/qapi-schema/args-bad-box.out
 create mode 100644 tests/qapi-schema/args-box-anon.err
 create mode 100644 tests/qapi-schema/args-box-anon.exit
 create mode 100644 tests/qapi-schema/args-box-anon.json
 create mode 100644 tests/qapi-schema/args-box-anon.out
 create mode 100644 tests/qapi-schema/args-box-empty.err
 create mode 100644 tests/qapi-schema/args-box-empty.exit
 create mode 100644 tests/qapi-schema/args-box-empty.json
 create mode 100644 tests/qapi-schema/args-box-empty.out
 create mode 100644 tests/qapi-schema/args-name-clash.err
 create mode 100644 tests/qapi-schema/args-name-clash.exit
 create mode 100644 tests/qapi-schema/args-name-clash.json
 create mode 100644 tests/qapi-schema/args-name-clash.out
 create mode 100644 tests/qapi-schema/base-cycle.err
 create mode 100644 tests/qapi-schema/base-cycle.exit
 create mode 100644 tests/qapi-schema/base-cycle.json
 create mode 100644 tests/qapi-schema/base-cycle.out
 delete mode 100644 tests/qapi-schema/flat-union-bad-base.err
 delete mode 100644 tests/qapi-schema/flat-union-bad-base.json
 create mode 100644 tests/qapi-schema/flat-union-branch-clash2.err
 create mode 100644 tests/qapi-schema/flat-union-branch-clash2.exit
 create mode 100644 tests/qapi-schema/flat-union-branch-clash2.json
 create mode 100644 tests/qapi-schema/flat-union-branch-clash2.out
 create mode 100644 tests/qapi-schema/flat-union-cycle.err
 create mode 100644 tests/qapi-schema/flat-union-cycle.exit
 create mode 100644 tests/qapi-schema/flat-union-cycle.json
 create mode 100644 tests/qapi-schema/flat-union-cycle.out
 create mode 100644 tests/qapi-schema/flat-union-empty.err
 create mode 100644 tests/qapi-schema/flat-union-empty.exit
 create mode 100644 tests/qapi-schema/flat-union-empty.json
 create mode 100644 tests/qapi-schema/flat-union-empty.out
 create mode 100644 tests/qapi-schema/union-clash.err
 create mode 100644 tests/qapi-schema/union-clash.exit
 create mode 100644 tests/qapi-schema/union-clash.json
 create mode 100644 tests/qapi-schema/union-clash.out
 create mode 100644 tests/qapi-schema/union-empty.err
 create mode 100644 tests/qapi-schema/union-empty.exit
 create mode 100644 tests/qapi-schema/union-empty.json
 create mode 100644 tests/qapi-schema/union-empty.out

-- 
2.4.3

^ permalink raw reply	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 01/46] qapi: Sort qapi-schema tests
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-23 14:26   ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 02/46] qapi: Clean up qapi.py per pep8 Eric Blake
                   ` (45 subsequent siblings)
  46 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Recent changes to qapi have provided quite a bit of churn in
the makefile, because we are inconsistent on what order test
names appear in, and on whether to re-wrap the list of tests or
just add arbitrary line lengths.  Writing the list in a sorted
fashion, one test per line, will make future patches easier
to see what tests are being added or removed by a patch.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 tests/Makefile | 160 ++++++++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 114 insertions(+), 46 deletions(-)

diff --git a/tests/Makefile b/tests/Makefile
index 4063639..6fd5885 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -221,52 +221,120 @@ $(foreach target,$(SYSEMU_TARGET_LIST), \
 	$(if $(findstring tests/qom-test$(EXESUF), $(check-qtest-$(target)-y)),, \
 		$(eval check-qtest-$(target)-y += tests/qom-test$(EXESUF))))

-check-qapi-schema-y := $(addprefix tests/qapi-schema/, \
-	comments.json empty.json enum-empty.json enum-missing-data.json \
-	enum-wrong-data.json enum-int-member.json enum-dict-member.json \
-	enum-clash-member.json enum-max-member.json enum-union-clash.json \
-	enum-bad-name.json enum-bad-prefix.json \
-	funny-char.json indented-expr.json \
-	missing-type.json bad-ident.json ident-with-escape.json \
-	escape-outside-string.json unknown-escape.json \
-	escape-too-short.json escape-too-big.json unicode-str.json \
-	double-type.json bad-base.json bad-type-bool.json bad-type-int.json \
-	bad-type-dict.json double-data.json unknown-expr-key.json \
-	redefined-type.json redefined-command.json redefined-builtin.json \
-	redefined-event.json command-int.json bad-data.json event-max.json \
-	type-bypass-bad-gen.json \
-	args-invalid.json \
-	args-array-empty.json args-array-unknown.json args-int.json \
-	args-unknown.json args-member-unknown.json args-member-array.json \
-	args-member-array-bad.json args-alternate.json args-union.json \
-	args-any.json \
-	returns-array-bad.json returns-int.json returns-dict.json \
-	returns-unknown.json returns-alternate.json returns-whitelist.json \
-	missing-colon.json missing-comma-list.json missing-comma-object.json \
-	struct-data-invalid.json struct-member-invalid.json \
-	nested-struct-data.json non-objects.json \
-	qapi-schema-test.json quoted-structural-chars.json \
-	leading-comma-list.json leading-comma-object.json \
-	trailing-comma-list.json trailing-comma-object.json \
-	unclosed-list.json unclosed-object.json unclosed-string.json \
-	duplicate-key.json union-invalid-base.json union-bad-branch.json \
-	union-optional-branch.json union-unknown.json union-max.json \
-	flat-union-optional-discriminator.json flat-union-no-base.json \
-	flat-union-invalid-discriminator.json flat-union-inline.json \
-	flat-union-invalid-branch-key.json flat-union-reverse-define.json \
-	flat-union-string-discriminator.json union-base-no-discriminator.json \
-	flat-union-bad-discriminator.json flat-union-bad-base.json \
-	flat-union-base-any.json \
-	flat-union-array-branch.json flat-union-int-branch.json \
-	flat-union-base-union.json flat-union-branch-clash.json \
-	alternate-nested.json alternate-unknown.json alternate-clash.json \
-	alternate-good.json alternate-base.json alternate-array.json \
-	alternate-conflict-string.json alternate-conflict-dict.json \
-	include-simple.json include-relpath.json include-format-err.json \
-	include-non-file.json include-no-file.json include-before-err.json \
-	include-nested-err.json include-self-cycle.json include-cycle.json \
-	include-repetition.json event-nest-struct.json event-case.json \
-	struct-base-clash.json struct-base-clash-deep.json )
+qapi-schema += alternate-array.json
+qapi-schema += alternate-base.json
+qapi-schema += alternate-clash.json
+qapi-schema += alternate-conflict-dict.json
+qapi-schema += alternate-conflict-string.json
+qapi-schema += alternate-good.json
+qapi-schema += alternate-nested.json
+qapi-schema += alternate-unknown.json
+qapi-schema += args-alternate.json
+qapi-schema += args-any.json
+qapi-schema += args-array-empty.json
+qapi-schema += args-array-unknown.json
+qapi-schema += args-int.json
+qapi-schema += args-invalid.json
+qapi-schema += args-member-array-bad.json
+qapi-schema += args-member-array.json
+qapi-schema += args-member-unknown.json
+qapi-schema += args-union.json
+qapi-schema += args-unknown.json
+qapi-schema += bad-base.json
+qapi-schema += bad-data.json
+qapi-schema += bad-ident.json
+qapi-schema += bad-type-bool.json
+qapi-schema += bad-type-dict.json
+qapi-schema += bad-type-int.json
+qapi-schema += command-int.json
+qapi-schema += comments.json
+qapi-schema += double-data.json
+qapi-schema += double-type.json
+qapi-schema += duplicate-key.json
+qapi-schema += empty.json
+qapi-schema += enum-bad-name.json
+qapi-schema += enum-bad-prefix.json
+qapi-schema += enum-clash-member.json
+qapi-schema += enum-dict-member.json
+qapi-schema += enum-empty.json
+qapi-schema += enum-int-member.json
+qapi-schema += enum-max-member.json
+qapi-schema += enum-missing-data.json
+qapi-schema += enum-union-clash.json
+qapi-schema += enum-wrong-data.json
+qapi-schema += escape-outside-string.json
+qapi-schema += escape-too-big.json
+qapi-schema += escape-too-short.json
+qapi-schema += event-case.json
+qapi-schema += event-max.json
+qapi-schema += event-nest-struct.json
+qapi-schema += flat-union-array-branch.json
+qapi-schema += flat-union-bad-base.json
+qapi-schema += flat-union-bad-discriminator.json
+qapi-schema += flat-union-base-any.json
+qapi-schema += flat-union-base-union.json
+qapi-schema += flat-union-branch-clash.json
+qapi-schema += flat-union-inline.json
+qapi-schema += flat-union-int-branch.json
+qapi-schema += flat-union-invalid-branch-key.json
+qapi-schema += flat-union-invalid-discriminator.json
+qapi-schema += flat-union-no-base.json
+qapi-schema += flat-union-optional-discriminator.json
+qapi-schema += flat-union-reverse-define.json
+qapi-schema += flat-union-string-discriminator.json
+qapi-schema += funny-char.json
+qapi-schema += ident-with-escape.json
+qapi-schema += include-before-err.json
+qapi-schema += include-cycle.json
+qapi-schema += include-format-err.json
+qapi-schema += include-nested-err.json
+qapi-schema += include-no-file.json
+qapi-schema += include-non-file.json
+qapi-schema += include-relpath.json
+qapi-schema += include-repetition.json
+qapi-schema += include-self-cycle.json
+qapi-schema += include-simple.json
+qapi-schema += indented-expr.json
+qapi-schema += leading-comma-list.json
+qapi-schema += leading-comma-object.json
+qapi-schema += missing-colon.json
+qapi-schema += missing-comma-list.json
+qapi-schema += missing-comma-object.json
+qapi-schema += missing-type.json
+qapi-schema += nested-struct-data.json
+qapi-schema += non-objects.json
+qapi-schema += qapi-schema-test.json
+qapi-schema += quoted-structural-chars.json
+qapi-schema += redefined-builtin.json
+qapi-schema += redefined-command.json
+qapi-schema += redefined-event.json
+qapi-schema += redefined-type.json
+qapi-schema += returns-alternate.json
+qapi-schema += returns-array-bad.json
+qapi-schema += returns-dict.json
+qapi-schema += returns-int.json
+qapi-schema += returns-unknown.json
+qapi-schema += returns-whitelist.json
+qapi-schema += struct-base-clash-deep.json
+qapi-schema += struct-base-clash.json
+qapi-schema += struct-data-invalid.json
+qapi-schema += struct-member-invalid.json
+qapi-schema += trailing-comma-list.json
+qapi-schema += trailing-comma-object.json
+qapi-schema += type-bypass-bad-gen.json
+qapi-schema += unclosed-list.json
+qapi-schema += unclosed-object.json
+qapi-schema += unclosed-string.json
+qapi-schema += unicode-str.json
+qapi-schema += union-bad-branch.json
+qapi-schema += union-base-no-discriminator.json
+qapi-schema += union-invalid-base.json
+qapi-schema += union-max.json
+qapi-schema += union-optional-branch.json
+qapi-schema += union-unknown.json
+qapi-schema += unknown-escape.json
+qapi-schema += unknown-expr-key.json
+check-qapi-schema-y := $(addprefix tests/qapi-schema/, $(qapi-schema))

 GENERATED_HEADERS += tests/test-qapi-types.h tests/test-qapi-visit.h \
 	tests/test-qmp-commands.h tests/test-qapi-event.h \
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 02/46] qapi: Clean up qapi.py per pep8
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 01/46] qapi: Sort qapi-schema tests Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-22 14:00   ` Markus Armbruster
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 03/46] qapi: Test for C member name collisions Eric Blake
                   ` (44 subsequent siblings)
  46 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Silence pep8, and make pylint a bit happier.  Just style cleanups;
no semantic changes.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi.py | 165 ++++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 107 insertions(+), 58 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 06478bb..31c4bcc 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -71,6 +71,7 @@ all_names = {}
 # Parsing the schema into expressions
 #

+
 def error_path(parent):
     res = ""
     while parent:
@@ -79,8 +80,10 @@ def error_path(parent):
         parent = parent['parent']
     return res

+
 class QAPISchemaError(Exception):
     def __init__(self, schema, msg):
+        Exception.__init__(self)
         self.fname = schema.fname
         self.msg = msg
         self.col = 1
@@ -96,8 +99,10 @@ class QAPISchemaError(Exception):
         return error_path(self.info) + \
             "%s:%d:%d: %s" % (self.fname, self.line, self.col, self.msg)

+
 class QAPIExprError(Exception):
     def __init__(self, expr_info, msg):
+        Exception.__init__(self)
         self.info = expr_info
         self.msg = msg

@@ -105,9 +110,10 @@ class QAPIExprError(Exception):
         return error_path(self.info['parent']) + \
             "%s:%d: %s" % (self.info['file'], self.info['line'], self.msg)

+
 class QAPISchemaParser(object):

-    def __init__(self, fp, previously_included = [], incl_info = None):
+    def __init__(self, fp, previously_included=[], incl_info=None):
         abs_fname = os.path.abspath(fp.name)
         fname = fp.name
         self.fname = fname
@@ -122,18 +128,18 @@ class QAPISchemaParser(object):
         self.exprs = []
         self.accept()

-        while self.tok != None:
+        while self.tok is not None:
             expr_info = {'file': fname, 'line': self.line,
                          'parent': self.incl_info}
             expr = self.get_expr(False)
             if isinstance(expr, dict) and "include" in expr:
                 if len(expr) != 1:
-                    raise QAPIExprError(expr_info, "Invalid 'include' directive")
+                    raise QAPIExprError(expr_info,
+                                        "Invalid 'include' directive")
                 include = expr["include"]
                 if not isinstance(include, str):
-                    raise QAPIExprError(expr_info,
-                                        'Expected a file name (string), got: %s'
-                                        % include)
+                    raise QAPIExprError(expr_info, 'Expected a file name '
+                                        '(string), got: %s' % include)
                 incl_abs_fname = os.path.join(os.path.dirname(abs_fname),
                                               include)
                 # catch inclusion cycle
@@ -192,7 +198,7 @@ class QAPISchemaParser(object):
                             string += '\t'
                         elif ch == 'u':
                             value = 0
-                            for x in range(0, 4):
+                            for _ in range(0, 4):
                                 ch = self.src[self.cursor]
                                 self.cursor += 1
                                 if ch not in "0123456789abcdefABCDEF":
@@ -214,7 +220,7 @@ class QAPISchemaParser(object):
                             string += ch
                         else:
                             raise QAPISchemaError(self,
-                                                  "Unknown escape \\%s" %ch)
+                                                  "Unknown escape \\%s" % ch)
                         esc = False
                     elif ch == "\\":
                         esc = True
@@ -274,7 +280,7 @@ class QAPISchemaParser(object):
         if self.tok == ']':
             self.accept()
             return expr
-        if not self.tok in "{['tfn":
+        if self.tok not in "{['tfn":
             raise QAPISchemaError(self, 'Expected "{", "[", "]", string, '
                                   'boolean or "null"')
         while True:
@@ -308,15 +314,17 @@ class QAPISchemaParser(object):
 # TODO catching name collisions in generated code would be nice
 #

+
 def find_base_fields(base):
     base_struct_define = find_struct(base)
     if not base_struct_define:
         return None
     return base_struct_define['data']

+
 # Return the qtype of an alternate branch, or None on error.
 def find_alternate_member_qtype(qapi_type):
-    if builtin_types.has_key(qapi_type):
+    if qapi_type in builtin_types:
         return builtin_types[qapi_type]
     elif find_struct(qapi_type):
         return "QTYPE_QDICT"
@@ -326,6 +334,7 @@ def find_alternate_member_qtype(qapi_type):
         return "QTYPE_QDICT"
     return None

+
 # Return the discriminator enum define if discriminator is specified as an
 # enum type, otherwise return None.
 def discriminator_find_enum_define(expr):
@@ -345,11 +354,14 @@ def discriminator_find_enum_define(expr):

     return find_enum(discriminator_type)

+
 # FIXME should enforce "other than downstream extensions [...], all
 # names should begin with a letter".
 valid_name = re.compile('^[a-zA-Z_][a-zA-Z0-9_.-]*$')
-def check_name(expr_info, source, name, allow_optional = False,
-               enum_member = False):
+
+
+def check_name(expr_info, source, name, allow_optional=False,
+               enum_member=False):
     global valid_name
     membername = name

@@ -370,7 +382,8 @@ def check_name(expr_info, source, name, allow_optional = False,
         raise QAPIExprError(expr_info,
                             "%s uses invalid name '%s'" % (source, name))

-def add_name(name, info, meta, implicit = False):
+
+def add_name(name, info, meta, implicit=False):
     global all_names
     check_name(info, "'%s'" % meta, name)
     # FIXME should reject names that differ only in '_' vs. '.'
@@ -385,12 +398,14 @@ def add_name(name, info, meta, implicit = False):
                             % (meta, name))
     all_names[name] = meta

+
 def add_struct(definition, info):
     global struct_types
     name = definition['struct']
     add_name(name, info, 'struct')
     struct_types.append(definition)

+
 def find_struct(name):
     global struct_types
     for struct in struct_types:
@@ -398,12 +413,14 @@ def find_struct(name):
             return struct
     return None

+
 def add_union(definition, info):
     global union_types
     name = definition['union']
     add_name(name, info, 'union')
     union_types.append(definition)

+
 def find_union(name):
     global union_types
     for union in union_types:
@@ -411,11 +428,13 @@ def find_union(name):
             return union
     return None

-def add_enum(name, info, enum_values = None, implicit = False):
+
+def add_enum(name, info, enum_values=None, implicit=False):
     global enum_types
     add_name(name, info, 'enum', implicit)
     enum_types.append({"enum_name": name, "enum_values": enum_values})

+
 def find_enum(name):
     global enum_types
     for enum in enum_types:
@@ -423,12 +442,14 @@ def find_enum(name):
             return enum
     return None

+
 def is_enum(name):
-    return find_enum(name) != None
+    return find_enum(name) is not None

-def check_type(expr_info, source, value, allow_array = False,
-               allow_dict = False, allow_optional = False,
-               allow_metas = []):
+
+def check_type(expr_info, source, value, allow_array=False,
+               allow_dict=False, allow_optional=False,
+               allow_metas=[]):
     global all_names

     if value is None:
@@ -447,7 +468,7 @@ def check_type(expr_info, source, value, allow_array = False,

     # Check if type name for value is okay
     if isinstance(value, str):
-        if not value in all_names:
+        if value not in all_names:
             raise QAPIExprError(expr_info,
                                 "%s uses unknown type '%s'"
                                 % (source, value))
@@ -476,7 +497,8 @@ def check_type(expr_info, source, value, allow_array = False,
                    allow_metas=['built-in', 'union', 'alternate', 'struct',
                                 'enum'])

-def check_member_clash(expr_info, base_name, data, source = ""):
+
+def check_member_clash(expr_info, base_name, data, source=""):
     base = find_struct(base_name)
     assert base
     base_members = base['data']
@@ -490,6 +512,7 @@ def check_member_clash(expr_info, base_name, data, source = ""):
     if base.get('base'):
         check_member_clash(expr_info, base['base'], data, source)

+
 def check_command(expr, expr_info):
     name = expr['command']

@@ -503,6 +526,7 @@ def check_command(expr, expr_info):
                expr.get('returns'), allow_array=True,
                allow_optional=True, allow_metas=returns_meta)

+
 def check_event(expr, expr_info):
     global events
     name = expr['event']
@@ -514,19 +538,20 @@ def check_event(expr, expr_info):
                expr.get('data'), allow_dict=True, allow_optional=True,
                allow_metas=['struct'])

+
 def check_union(expr, expr_info):
     name = expr['union']
     base = expr.get('base')
     discriminator = expr.get('discriminator')
     members = expr['data']
-    values = { 'MAX': '(automatic)' }
+    values = {'MAX': '(automatic)'}

     # Two types of unions, determined by discriminator.

     # With no discriminator it is a simple union.
     if discriminator is None:
         enum_define = None
-        allow_metas=['built-in', 'union', 'alternate', 'struct', 'enum']
+        allow_metas = ['built-in', 'union', 'alternate', 'struct', 'enum']
         if base is not None:
             raise QAPIExprError(expr_info,
                                 "Simple union '%s' must not have a base"
@@ -556,7 +581,7 @@ def check_union(expr, expr_info):
                                 "struct '%s'"
                                 % (discriminator, base))
         enum_define = find_enum(discriminator_type)
-        allow_metas=['struct']
+        allow_metas = ['struct']
         # Do not allow string discriminator
         if not enum_define:
             raise QAPIExprError(expr_info,
@@ -580,7 +605,7 @@ def check_union(expr, expr_info):
         # If the discriminator names an enum type, then all members
         # of 'data' must also be members of the enum type.
         if enum_define:
-            if not key in enum_define['enum_values']:
+            if key not in enum_define['enum_values']:
                 raise QAPIExprError(expr_info,
                                     "Discriminator value '%s' is not found in "
                                     "enum '%s'" %
@@ -595,10 +620,11 @@ def check_union(expr, expr_info):
                                     % (name, key, values[c_key]))
             values[c_key] = key

+
 def check_alternate(expr, expr_info):
     name = expr['alternate']
     members = expr['data']
-    values = { 'MAX': '(automatic)' }
+    values = {'MAX': '(automatic)'}
     types_seen = {}

     # Check every branch
@@ -626,11 +652,12 @@ def check_alternate(expr, expr_info):
                                 % (name, key, types_seen[qtype]))
         types_seen[qtype] = key

+
 def check_enum(expr, expr_info):
     name = expr['enum']
     members = expr.get('data')
     prefix = expr.get('prefix')
-    values = { 'MAX': '(automatic)' }
+    values = {'MAX': '(automatic)'}

     if not isinstance(members, list):
         raise QAPIExprError(expr_info,
@@ -639,7 +666,7 @@ def check_enum(expr, expr_info):
         raise QAPIExprError(expr_info,
                             "Enum '%s' requires a string for 'prefix'" % name)
     for member in members:
-        check_name(expr_info, "Member of enum '%s'" %name, member,
+        check_name(expr_info, "Member of enum '%s'" % name, member,
                    enum_member=True)
         key = camel_to_upper(member)
         if key in values:
@@ -648,6 +675,7 @@ def check_enum(expr, expr_info):
                                 % (name, member, values[key]))
         values[key] = member

+
 def check_struct(expr, expr_info):
     name = expr['struct']
     members = expr['data']
@@ -659,6 +687,7 @@ def check_struct(expr, expr_info):
     if expr.get('base'):
         check_member_clash(expr_info, expr['base'], expr['data'])

+
 def check_keys(expr_elem, meta, required, optional=[]):
     expr = expr_elem['expr']
     info = expr_elem['info']
@@ -666,22 +695,23 @@ def check_keys(expr_elem, meta, required, optional=[]):
     if not isinstance(name, str):
         raise QAPIExprError(info,
                             "'%s' key must have a string value" % meta)
-    required = required + [ meta ]
+    required = required + [meta]
     for (key, value) in expr.items():
-        if not key in required and not key in optional:
+        if key not in required + optional:
             raise QAPIExprError(info,
                                 "Unknown key '%s' in %s '%s'"
                                 % (key, meta, name))
-        if (key == 'gen' or key == 'success-response') and value != False:
+        if (key == 'gen' or key == 'success-response') and value is not False:
             raise QAPIExprError(info,
                                 "'%s' of %s '%s' should only use false value"
                                 % (key, meta, name))
     for key in required:
-        if not expr.has_key(key):
+        if key not in expr:
             raise QAPIExprError(info,
                                 "Key '%s' is missing from %s '%s'"
                                 % (key, meta, name))

+
 def check_exprs(exprs):
     global all_names

@@ -691,24 +721,24 @@ def check_exprs(exprs):
     for expr_elem in exprs:
         expr = expr_elem['expr']
         info = expr_elem['info']
-        if expr.has_key('enum'):
+        if 'enum' in expr:
             check_keys(expr_elem, 'enum', ['data'], ['prefix'])
             add_enum(expr['enum'], info, expr['data'])
-        elif expr.has_key('union'):
+        elif 'union' in expr:
             check_keys(expr_elem, 'union', ['data'],
                        ['base', 'discriminator'])
             add_union(expr, info)
-        elif expr.has_key('alternate'):
+        elif 'alternate' in expr:
             check_keys(expr_elem, 'alternate', ['data'])
             add_name(expr['alternate'], info, 'alternate')
-        elif expr.has_key('struct'):
+        elif 'struct' in expr:
             check_keys(expr_elem, 'struct', ['data'], ['base'])
             add_struct(expr, info)
-        elif expr.has_key('command'):
+        elif 'command' in expr:
             check_keys(expr_elem, 'command', [],
                        ['data', 'returns', 'gen', 'success-response'])
             add_name(expr['command'], info, 'command')
-        elif expr.has_key('event'):
+        elif 'event' in expr:
             check_keys(expr_elem, 'event', [], ['data'])
             add_name(expr['event'], info, 'event')
         else:
@@ -718,11 +748,11 @@ def check_exprs(exprs):
     # Try again for hidden UnionKind enum
     for expr_elem in exprs:
         expr = expr_elem['expr']
-        if expr.has_key('union'):
+        if 'union' in expr:
             if not discriminator_find_enum_define(expr):
                 add_enum('%sKind' % expr['union'], expr_elem['info'],
                          implicit=True)
-        elif expr.has_key('alternate'):
+        elif 'alternate' in expr:
             add_enum('%sKind' % expr['alternate'], expr_elem['info'],
                      implicit=True)

@@ -731,17 +761,17 @@ def check_exprs(exprs):
         expr = expr_elem['expr']
         info = expr_elem['info']

-        if expr.has_key('enum'):
+        if 'enum' in expr:
             check_enum(expr, info)
-        elif expr.has_key('union'):
+        elif 'union' in expr:
             check_union(expr, info)
-        elif expr.has_key('alternate'):
+        elif 'alternate' in expr:
             check_alternate(expr, info)
-        elif expr.has_key('struct'):
+        elif 'struct' in expr:
             check_struct(expr, info)
-        elif expr.has_key('command'):
+        elif 'command' in expr:
             check_command(expr, info)
-        elif expr.has_key('event'):
+        elif 'event' in expr:
             check_event(expr, info)
         else:
             assert False, 'unexpected meta type'
@@ -993,6 +1023,7 @@ class QAPISchemaObjectTypeVariants(object):
             vseen = dict(seen)
             v.check(schema, self.tag_member.type, vseen)

+
 class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
     def __init__(self, name, typ):
         QAPISchemaObjectTypeMember.__init__(self, name, typ, False)
@@ -1292,6 +1323,7 @@ def camel_case(name):
             new_name += ch.lower()
     return new_name

+
 # ENUMName -> ENUM_NAME, EnumName1 -> ENUM_NAME1
 # ENUM_NAME -> ENUM_NAME, ENUM_NAME1 -> ENUM_NAME1, ENUM_Name2 -> ENUM_NAME2
 # ENUM24_Name -> ENUM24_NAME
@@ -1308,12 +1340,13 @@ def camel_to_upper(value):
         if c.isupper() and (i > 0) and c_fun_str[i - 1] != "_":
             # Case 1: next string is lower
             # Case 2: previous string is digit
-            if (i < (l - 1) and c_fun_str[i + 1].islower()) or \
-            c_fun_str[i - 1].isdigit():
+            next_lower = i < (l - 1) and c_fun_str[i + 1].islower()
+            if next_lower or c_fun_str[i - 1].isdigit():
                 new_name += '_'
         new_name += c
     return new_name.lstrip('_').upper()

+
 def c_enum_const(type_name, const_name, prefix=None):
     if prefix is not None:
         type_name = prefix
@@ -1321,6 +1354,7 @@ def c_enum_const(type_name, const_name, prefix=None):

 c_name_trans = string.maketrans('.-', '__')

+
 # Map @name to a valid C identifier.
 # If @protect, avoid returning certain ticklish identifiers (like
 # C keywords) by prepending "q_".
@@ -1333,15 +1367,16 @@ c_name_trans = string.maketrans('.-', '__')
 def c_name(name, protect=True):
     # ANSI X3J11/88-090, 3.1.1
     c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue',
-                     'default', 'do', 'double', 'else', 'enum', 'extern', 'float',
-                     'for', 'goto', 'if', 'int', 'long', 'register', 'return',
-                     'short', 'signed', 'sizeof', 'static', 'struct', 'switch',
-                     'typedef', 'union', 'unsigned', 'void', 'volatile', 'while'])
+                     'default', 'do', 'double', 'else', 'enum', 'extern',
+                     'float', 'for', 'goto', 'if', 'int', 'long', 'register',
+                     'return', 'short', 'signed', 'sizeof', 'static',
+                     'struct', 'switch', 'typedef', 'union', 'unsigned',
+                     'void', 'volatile', 'while'])
     # ISO/IEC 9899:1999, 6.4.1
     c99_words = set(['inline', 'restrict', '_Bool', '_Complex', '_Imaginary'])
     # ISO/IEC 9899:2011, 6.4.1
-    c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic', '_Noreturn',
-                     '_Static_assert', '_Thread_local'])
+    c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic',
+                     '_Noreturn', '_Static_assert', '_Thread_local'])
     # GCC http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Extensions.html
     # excluding _.*
     gcc_words = set(['asm', 'typeof'])
@@ -1357,29 +1392,34 @@ def c_name(name, protect=True):
                      'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'])
     # namespace pollution:
     polluted_words = set(['unix', 'errno'])
-    if protect and (name in c89_words | c99_words | c11_words | gcc_words | cpp_words | polluted_words):
+    if protect and (name in c89_words | c99_words | c11_words | gcc_words
+                    | cpp_words | polluted_words):
         return "q_" + name
     return name.translate(c_name_trans)

 eatspace = '\033EATSPACE.'
 pointer_suffix = ' *' + eatspace

+
 def genindent(count):
     ret = ""
-    for i in range(count):
+    for _ in range(count):
         ret += " "
     return ret

 indent_level = 0

+
 def push_indent(indent_amount=4):
     global indent_level
     indent_level += indent_amount

+
 def pop_indent(indent_amount=4):
     global indent_level
     indent_level -= indent_amount

+
 # Generate @code with @kwds interpolated.
 # Obey indent_level, and strip eatspace.
 def cgen(code, **kwds):
@@ -1392,6 +1432,7 @@ def cgen(code, **kwds):
         raw = raw[0]
     return re.sub(re.escape(eatspace) + ' *', '', raw)

+
 def mcgen(code, **kwds):
     if code[0] == '\n':
         code = code[1:]
@@ -1401,6 +1442,7 @@ def mcgen(code, **kwds):
 def guardname(filename):
     return c_name(filename, protect=False).upper()

+
 def guardstart(name):
     return mcgen('''

@@ -1410,6 +1452,7 @@ def guardstart(name):
 ''',
                  name=guardname(name))

+
 def guardend(name):
     return mcgen('''

@@ -1418,6 +1461,7 @@ def guardend(name):
 ''',
                  name=guardname(name))

+
 def gen_enum_lookup(name, values, prefix=None):
     ret = mcgen('''

@@ -1439,6 +1483,7 @@ const char *const %(c_name)s_lookup[] = {
                  max_index=max_index)
     return ret

+
 def gen_enum(name, values, prefix=None):
     # append automatically generated _MAX value
     enum_values = values + ['MAX']
@@ -1470,6 +1515,7 @@ extern const char *const %(c_name)s_lookup[];
                  c_name=c_name(name))
     return ret

+
 def gen_params(arg_type, extra):
     if not arg_type:
         return extra
@@ -1490,7 +1536,8 @@ def gen_params(arg_type, extra):
 # Common command line parsing
 #

-def parse_command_line(extra_options = "", extra_long_options = []):
+
+def parse_command_line(extra_options="", extra_long_options=[]):

     try:
         opts, args = getopt.gnu_getopt(sys.argv[1:],
@@ -1541,6 +1588,7 @@ def parse_command_line(extra_options = "", extra_long_options = []):
 # Generate output files with boilerplate
 #

+
 def open_output(output_dir, do_c, do_h, prefix, c_file, h_file,
                 c_comment, h_comment):
     guard = guardname(prefix + h_file)
@@ -1568,7 +1616,7 @@ def open_output(output_dir, do_c, do_h, prefix, c_file, h_file,
 /* AUTOMATICALLY GENERATED, DO NOT MODIFY */
 %(comment)s
 ''',
-                     comment = c_comment))
+                     comment=c_comment))

     fdecl.write(mcgen('''
 /* AUTOMATICALLY GENERATED, DO NOT MODIFY */
@@ -1577,10 +1625,11 @@ def open_output(output_dir, do_c, do_h, prefix, c_file, h_file,
 #define %(guard)s

 ''',
-                      comment = h_comment, guard = guard))
+                      comment=h_comment, guard=guard))

     return (fdef, fdecl)

+
 def close_output(fdef, fdecl):
     fdecl.write('''
 #endif
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 03/46] qapi: Test for C member name collisions
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 01/46] qapi: Sort qapi-schema tests Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 02/46] qapi: Clean up qapi.py per pep8 Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-22 15:23   ` Markus Armbruster
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 04/46] qapi: Add tests for empty unions Eric Blake
                   ` (43 subsequent siblings)
  46 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Expose some weaknesses in the generator: we don't always forbid
the generation of structs that contain multiple members that map
to the same C name.  This has already been marked FIXME in
qapi.py, but having more tests will make sure future patches
produce desired behavior.

Some of these tests will be deleted later, and a positive test
added to qapi-schema-test.json in its place, when the code is
reworked so that the collision no longer occurs.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 tests/Makefile                                  |  6 ++++++
 tests/qapi-schema/args-name-clash.err           |  0
 tests/qapi-schema/args-name-clash.exit          |  1 +
 tests/qapi-schema/args-name-clash.json          |  2 ++
 tests/qapi-schema/args-name-clash.out           |  6 ++++++
 tests/qapi-schema/flat-union-branch-clash.json  |  2 +-
 tests/qapi-schema/flat-union-branch-clash2.err  |  0
 tests/qapi-schema/flat-union-branch-clash2.exit |  1 +
 tests/qapi-schema/flat-union-branch-clash2.json | 14 ++++++++++++++
 tests/qapi-schema/flat-union-branch-clash2.out  | 14 ++++++++++++++
 tests/qapi-schema/flat-union-cycle.err          |  1 +
 tests/qapi-schema/flat-union-cycle.exit         |  1 +
 tests/qapi-schema/flat-union-cycle.json         |  6 ++++++
 tests/qapi-schema/flat-union-cycle.out          |  0
 tests/qapi-schema/qapi-schema-test.json         |  5 +++--
 tests/qapi-schema/qapi-schema-test.out          |  2 ++
 tests/qapi-schema/struct-base-clash2.err        |  0
 tests/qapi-schema/struct-base-clash2.exit       |  1 +
 tests/qapi-schema/struct-base-clash2.json       |  5 +++++
 tests/qapi-schema/struct-base-clash2.out        |  5 +++++
 tests/qapi-schema/union-clash.err               |  1 +
 tests/qapi-schema/union-clash.exit              |  1 +
 tests/qapi-schema/union-clash.json              |  3 +++
 tests/qapi-schema/union-clash.out               |  0
 tests/qapi-schema/union-clash2.err              |  0
 tests/qapi-schema/union-clash2.exit             |  1 +
 tests/qapi-schema/union-clash2.json             |  3 +++
 tests/qapi-schema/union-clash2.out              |  6 ++++++
 28 files changed, 84 insertions(+), 3 deletions(-)
 create mode 100644 tests/qapi-schema/args-name-clash.err
 create mode 100644 tests/qapi-schema/args-name-clash.exit
 create mode 100644 tests/qapi-schema/args-name-clash.json
 create mode 100644 tests/qapi-schema/args-name-clash.out
 create mode 100644 tests/qapi-schema/flat-union-branch-clash2.err
 create mode 100644 tests/qapi-schema/flat-union-branch-clash2.exit
 create mode 100644 tests/qapi-schema/flat-union-branch-clash2.json
 create mode 100644 tests/qapi-schema/flat-union-branch-clash2.out
 create mode 100644 tests/qapi-schema/flat-union-cycle.err
 create mode 100644 tests/qapi-schema/flat-union-cycle.exit
 create mode 100644 tests/qapi-schema/flat-union-cycle.json
 create mode 100644 tests/qapi-schema/flat-union-cycle.out
 create mode 100644 tests/qapi-schema/struct-base-clash2.err
 create mode 100644 tests/qapi-schema/struct-base-clash2.exit
 create mode 100644 tests/qapi-schema/struct-base-clash2.json
 create mode 100644 tests/qapi-schema/struct-base-clash2.out
 create mode 100644 tests/qapi-schema/union-clash.err
 create mode 100644 tests/qapi-schema/union-clash.exit
 create mode 100644 tests/qapi-schema/union-clash.json
 create mode 100644 tests/qapi-schema/union-clash.out
 create mode 100644 tests/qapi-schema/union-clash2.err
 create mode 100644 tests/qapi-schema/union-clash2.exit
 create mode 100644 tests/qapi-schema/union-clash2.json
 create mode 100644 tests/qapi-schema/union-clash2.out

diff --git a/tests/Makefile b/tests/Makefile
index 6fd5885..97434f6 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -238,6 +238,7 @@ qapi-schema += args-invalid.json
 qapi-schema += args-member-array-bad.json
 qapi-schema += args-member-array.json
 qapi-schema += args-member-unknown.json
+qapi-schema += args-name-clash.json
 qapi-schema += args-union.json
 qapi-schema += args-unknown.json
 qapi-schema += bad-base.json
@@ -274,6 +275,8 @@ qapi-schema += flat-union-bad-discriminator.json
 qapi-schema += flat-union-base-any.json
 qapi-schema += flat-union-base-union.json
 qapi-schema += flat-union-branch-clash.json
+qapi-schema += flat-union-branch-clash2.json
+qapi-schema += flat-union-cycle.json
 qapi-schema += flat-union-inline.json
 qapi-schema += flat-union-int-branch.json
 qapi-schema += flat-union-invalid-branch-key.json
@@ -317,6 +320,7 @@ qapi-schema += returns-unknown.json
 qapi-schema += returns-whitelist.json
 qapi-schema += struct-base-clash-deep.json
 qapi-schema += struct-base-clash.json
+qapi-schema += struct-base-clash2.json
 qapi-schema += struct-data-invalid.json
 qapi-schema += struct-member-invalid.json
 qapi-schema += trailing-comma-list.json
@@ -328,6 +332,8 @@ qapi-schema += unclosed-string.json
 qapi-schema += unicode-str.json
 qapi-schema += union-bad-branch.json
 qapi-schema += union-base-no-discriminator.json
+qapi-schema += union-clash.json
+qapi-schema += union-clash2.json
 qapi-schema += union-invalid-base.json
 qapi-schema += union-max.json
 qapi-schema += union-optional-branch.json
diff --git a/tests/qapi-schema/args-name-clash.err b/tests/qapi-schema/args-name-clash.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/args-name-clash.exit b/tests/qapi-schema/args-name-clash.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/args-name-clash.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/args-name-clash.json b/tests/qapi-schema/args-name-clash.json
new file mode 100644
index 0000000..19bf792
--- /dev/null
+++ b/tests/qapi-schema/args-name-clash.json
@@ -0,0 +1,2 @@
+# FIXME - we should reject data with members that clash when mapped to C names
+{ 'command': 'oops', 'data': { 'a-b': 'str', 'a_b': 'str' } }
diff --git a/tests/qapi-schema/args-name-clash.out b/tests/qapi-schema/args-name-clash.out
new file mode 100644
index 0000000..9b2f6e4
--- /dev/null
+++ b/tests/qapi-schema/args-name-clash.out
@@ -0,0 +1,6 @@
+object :empty
+object :obj-oops-arg
+    member a-b: str optional=False
+    member a_b: str optional=False
+command oops :obj-oops-arg -> None
+   gen=True success_response=True
diff --git a/tests/qapi-schema/flat-union-branch-clash.json b/tests/qapi-schema/flat-union-branch-clash.json
index 8fb054f..3d7e6c6 100644
--- a/tests/qapi-schema/flat-union-branch-clash.json
+++ b/tests/qapi-schema/flat-union-branch-clash.json
@@ -1,4 +1,4 @@
-# we check for no duplicate keys between branches and base
+# we check for no duplicate keys between branch members and base
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
 { 'struct': 'Base',
diff --git a/tests/qapi-schema/flat-union-branch-clash2.err b/tests/qapi-schema/flat-union-branch-clash2.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/flat-union-branch-clash2.exit b/tests/qapi-schema/flat-union-branch-clash2.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/flat-union-branch-clash2.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/flat-union-branch-clash2.json b/tests/qapi-schema/flat-union-branch-clash2.json
new file mode 100644
index 0000000..b3eefb3
--- /dev/null
+++ b/tests/qapi-schema/flat-union-branch-clash2.json
@@ -0,0 +1,14 @@
+# FIXME: we should check for no duplicate C names between branches and base
+{ 'enum': 'TestEnum',
+  'data': [ 'base', 'c-d' ] }
+{ 'struct': 'Base',
+  'data': { 'enum1': 'TestEnum', '*c_d': 'str' } }
+{ 'struct': 'Branch1',
+  'data': { 'string': 'str' } }
+{ 'struct': 'Branch2',
+  'data': { 'value': 'int' } }
+{ 'union': 'TestUnion',
+  'base': 'Base',
+  'discriminator': 'enum1',
+  'data': { 'base': 'Branch1',
+            'c-d': 'Branch2' } }
diff --git a/tests/qapi-schema/flat-union-branch-clash2.out b/tests/qapi-schema/flat-union-branch-clash2.out
new file mode 100644
index 0000000..8e0da73
--- /dev/null
+++ b/tests/qapi-schema/flat-union-branch-clash2.out
@@ -0,0 +1,14 @@
+object :empty
+object Base
+    member enum1: TestEnum optional=False
+    member c_d: str optional=True
+object Branch1
+    member string: str optional=False
+object Branch2
+    member value: int optional=False
+enum TestEnum ['base', 'c-d']
+object TestUnion
+    base Base
+    tag enum1
+    case base: Branch1
+    case c-d: Branch2
diff --git a/tests/qapi-schema/flat-union-cycle.err b/tests/qapi-schema/flat-union-cycle.err
new file mode 100644
index 0000000..152c6f0
--- /dev/null
+++ b/tests/qapi-schema/flat-union-cycle.err
@@ -0,0 +1 @@
+tests/qapi-schema/flat-union-cycle.json:5: Member name 'switch' of branch 'loop' clashes with base 'Base'
diff --git a/tests/qapi-schema/flat-union-cycle.exit b/tests/qapi-schema/flat-union-cycle.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/flat-union-cycle.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/flat-union-cycle.json b/tests/qapi-schema/flat-union-cycle.json
new file mode 100644
index 0000000..8f6cd93
--- /dev/null
+++ b/tests/qapi-schema/flat-union-cycle.json
@@ -0,0 +1,6 @@
+# we reject a loop in flat unions, due to member collision
+{ 'enum': 'Enum', 'data': [ 'okay', 'loop' ] }
+{ 'struct': 'Base', 'data': { 'switch': 'Enum' } }
+{ 'struct': 'Okay', 'data': { 'int': 'int' } }
+{ 'union': 'Union', 'base': 'Base', 'discriminator': 'switch',
+  'data': { 'okay': 'Okay', 'loop': 'Base' } }
diff --git a/tests/qapi-schema/flat-union-cycle.out b/tests/qapi-schema/flat-union-cycle.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index 6897a6e..c904db4 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -32,11 +32,12 @@
             'dict1': 'UserDefTwoDict' } }

 # for testing unions
+# name collisions between branches should not clash
 { 'struct': 'UserDefA',
-  'data': { 'boolean': 'bool' } }
+  'data': { 'boolean': 'bool', '*a_b': 'int' } }

 { 'struct': 'UserDefB',
-  'data': { 'intb': 'int' } }
+  'data': { 'intb': 'int', '*a-b': 'bool' } }

 { 'union': 'UserDefFlatUnion',
   'base': 'UserDefUnionBase',   # intentional forward reference
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 1f6e858..28a0b3c 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -71,6 +71,7 @@ enum QEnumTwo ['value1', 'value2']
     prefix QENUM_TWO
 object UserDefA
     member boolean: bool optional=False
+    member a_b: int optional=True
 alternate UserDefAlternate
     case uda: UserDefA
     case s: str
@@ -78,6 +79,7 @@ alternate UserDefAlternate
 enum UserDefAlternateKind ['uda', 's', 'i']
 object UserDefB
     member intb: int optional=False
+    member a-b: bool optional=True
 object UserDefC
     member string1: str optional=False
     member string2: str optional=False
diff --git a/tests/qapi-schema/struct-base-clash2.err b/tests/qapi-schema/struct-base-clash2.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/struct-base-clash2.exit b/tests/qapi-schema/struct-base-clash2.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/struct-base-clash2.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/struct-base-clash2.json b/tests/qapi-schema/struct-base-clash2.json
new file mode 100644
index 0000000..56166e0
--- /dev/null
+++ b/tests/qapi-schema/struct-base-clash2.json
@@ -0,0 +1,5 @@
+# FIXME - a base class collides with a member named base
+{ 'struct': 'Base', 'data': {} }
+{ 'struct': 'Sub',
+  'base': 'Base',
+  'data': { 'base': 'str' } }
diff --git a/tests/qapi-schema/struct-base-clash2.out b/tests/qapi-schema/struct-base-clash2.out
new file mode 100644
index 0000000..e69a416
--- /dev/null
+++ b/tests/qapi-schema/struct-base-clash2.out
@@ -0,0 +1,5 @@
+object :empty
+object Base
+object Sub
+    base Base
+    member base: str optional=False
diff --git a/tests/qapi-schema/union-clash.err b/tests/qapi-schema/union-clash.err
new file mode 100644
index 0000000..64637ed
--- /dev/null
+++ b/tests/qapi-schema/union-clash.err
@@ -0,0 +1 @@
+tests/qapi-schema/union-clash.json:2: Union 'TestUnion' member 'a_b' clashes with 'a-b'
diff --git a/tests/qapi-schema/union-clash.exit b/tests/qapi-schema/union-clash.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/union-clash.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/union-clash.json b/tests/qapi-schema/union-clash.json
new file mode 100644
index 0000000..0393ed8
--- /dev/null
+++ b/tests/qapi-schema/union-clash.json
@@ -0,0 +1,3 @@
+# we reject unions where branch names clash when mapped to C
+{ 'union': 'TestUnion',
+  'data': { 'a-b': 'int', 'a_b': 'str' } }
diff --git a/tests/qapi-schema/union-clash.out b/tests/qapi-schema/union-clash.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/union-clash2.err b/tests/qapi-schema/union-clash2.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/union-clash2.exit b/tests/qapi-schema/union-clash2.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/union-clash2.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/union-clash2.json b/tests/qapi-schema/union-clash2.json
new file mode 100644
index 0000000..b2d45fb
--- /dev/null
+++ b/tests/qapi-schema/union-clash2.json
@@ -0,0 +1,3 @@
+# FIXME - a union branch named 'data' collides with generated C code
+{ 'union': 'TestUnion',
+  'data': { 'data': 'int' } }
diff --git a/tests/qapi-schema/union-clash2.out b/tests/qapi-schema/union-clash2.out
new file mode 100644
index 0000000..6277239
--- /dev/null
+++ b/tests/qapi-schema/union-clash2.out
@@ -0,0 +1,6 @@
+object :empty
+object :obj-int-wrapper
+    member data: int optional=False
+object TestUnion
+    case data: :obj-int-wrapper
+enum TestUnionKind ['data']
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 04/46] qapi: Add tests for empty unions
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (2 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 03/46] qapi: Test for C member name collisions Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-24 14:16   ` Markus Armbruster
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 05/46] qapi: Test use of 'number' within alternates Eric Blake
                   ` (42 subsequent siblings)
  46 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

The documentation claims that alternates are useful for
allowing two types, although nothing enforces this.  Meanwhile,
it is silent on whether empty unions are allowed.  In practice,
the generated code will compile, in part because we have a
'void *data' branch; but attempting to visit such a type will
cause an abort().  Add some tests to expose the problems, and
adjust existing tests that should be failing for other reasons.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 tests/Makefile                           | 3 +++
 tests/qapi-schema/alternate-empty.err    | 0
 tests/qapi-schema/alternate-empty.exit   | 1 +
 tests/qapi-schema/alternate-empty.json   | 2 ++
 tests/qapi-schema/alternate-empty.out    | 4 ++++
 tests/qapi-schema/alternate-nested.json  | 2 +-
 tests/qapi-schema/alternate-unknown.json | 2 +-
 tests/qapi-schema/flat-union-empty.err   | 0
 tests/qapi-schema/flat-union-empty.exit  | 1 +
 tests/qapi-schema/flat-union-empty.json  | 4 ++++
 tests/qapi-schema/flat-union-empty.out   | 7 +++++++
 tests/qapi-schema/union-empty.err        | 0
 tests/qapi-schema/union-empty.exit       | 1 +
 tests/qapi-schema/union-empty.json       | 2 ++
 tests/qapi-schema/union-empty.out        | 3 +++
 15 files changed, 30 insertions(+), 2 deletions(-)
 create mode 100644 tests/qapi-schema/alternate-empty.err
 create mode 100644 tests/qapi-schema/alternate-empty.exit
 create mode 100644 tests/qapi-schema/alternate-empty.json
 create mode 100644 tests/qapi-schema/alternate-empty.out
 create mode 100644 tests/qapi-schema/flat-union-empty.err
 create mode 100644 tests/qapi-schema/flat-union-empty.exit
 create mode 100644 tests/qapi-schema/flat-union-empty.json
 create mode 100644 tests/qapi-schema/flat-union-empty.out
 create mode 100644 tests/qapi-schema/union-empty.err
 create mode 100644 tests/qapi-schema/union-empty.exit
 create mode 100644 tests/qapi-schema/union-empty.json
 create mode 100644 tests/qapi-schema/union-empty.out

diff --git a/tests/Makefile b/tests/Makefile
index 97434f6..df16c9c 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -226,6 +226,7 @@ qapi-schema += alternate-base.json
 qapi-schema += alternate-clash.json
 qapi-schema += alternate-conflict-dict.json
 qapi-schema += alternate-conflict-string.json
+qapi-schema += alternate-empty.json
 qapi-schema += alternate-good.json
 qapi-schema += alternate-nested.json
 qapi-schema += alternate-unknown.json
@@ -277,6 +278,7 @@ qapi-schema += flat-union-base-union.json
 qapi-schema += flat-union-branch-clash.json
 qapi-schema += flat-union-branch-clash2.json
 qapi-schema += flat-union-cycle.json
+qapi-schema += flat-union-empty.json
 qapi-schema += flat-union-inline.json
 qapi-schema += flat-union-int-branch.json
 qapi-schema += flat-union-invalid-branch-key.json
@@ -334,6 +336,7 @@ qapi-schema += union-bad-branch.json
 qapi-schema += union-base-no-discriminator.json
 qapi-schema += union-clash.json
 qapi-schema += union-clash2.json
+qapi-schema += union-empty.json
 qapi-schema += union-invalid-base.json
 qapi-schema += union-max.json
 qapi-schema += union-optional-branch.json
diff --git a/tests/qapi-schema/alternate-empty.err b/tests/qapi-schema/alternate-empty.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/alternate-empty.exit b/tests/qapi-schema/alternate-empty.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/alternate-empty.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/alternate-empty.json b/tests/qapi-schema/alternate-empty.json
new file mode 100644
index 0000000..db3820f
--- /dev/null
+++ b/tests/qapi-schema/alternate-empty.json
@@ -0,0 +1,2 @@
+# FIXME - alternates should list at least two types to be useful
+{ 'alternate': 'Alt', 'data': { 'i': 'int' } }
diff --git a/tests/qapi-schema/alternate-empty.out b/tests/qapi-schema/alternate-empty.out
new file mode 100644
index 0000000..0f153b6
--- /dev/null
+++ b/tests/qapi-schema/alternate-empty.out
@@ -0,0 +1,4 @@
+object :empty
+alternate Alt
+    case i: int
+enum AltKind ['i']
diff --git a/tests/qapi-schema/alternate-nested.json b/tests/qapi-schema/alternate-nested.json
index c4233b9..8e22186 100644
--- a/tests/qapi-schema/alternate-nested.json
+++ b/tests/qapi-schema/alternate-nested.json
@@ -2,4 +2,4 @@
 { 'alternate': 'Alt1',
   'data': { 'name': 'str', 'value': 'int' } }
 { 'alternate': 'Alt2',
-  'data': { 'nested': 'Alt1' } }
+  'data': { 'nested': 'Alt1', 'b': 'bool' } }
diff --git a/tests/qapi-schema/alternate-unknown.json b/tests/qapi-schema/alternate-unknown.json
index ad5c103..08c80dc 100644
--- a/tests/qapi-schema/alternate-unknown.json
+++ b/tests/qapi-schema/alternate-unknown.json
@@ -1,3 +1,3 @@
 # we reject an alternate with unknown type in branch
 { 'alternate': 'Alt',
-  'data': { 'unknown': 'MissingType' } }
+  'data': { 'unknown': 'MissingType', 'i': 'int' } }
diff --git a/tests/qapi-schema/flat-union-empty.err b/tests/qapi-schema/flat-union-empty.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/flat-union-empty.exit b/tests/qapi-schema/flat-union-empty.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/flat-union-empty.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/flat-union-empty.json b/tests/qapi-schema/flat-union-empty.json
new file mode 100644
index 0000000..67dd297
--- /dev/null
+++ b/tests/qapi-schema/flat-union-empty.json
@@ -0,0 +1,4 @@
+# FIXME - flat unions should not be empty
+{ 'enum': 'Empty', 'data': [ ] }
+{ 'struct': 'Base', 'data': { 'type': 'Empty' } }
+{ 'union': 'Union', 'base': 'Base', 'discriminator': 'type', 'data': { } }
diff --git a/tests/qapi-schema/flat-union-empty.out b/tests/qapi-schema/flat-union-empty.out
new file mode 100644
index 0000000..0e0665a
--- /dev/null
+++ b/tests/qapi-schema/flat-union-empty.out
@@ -0,0 +1,7 @@
+object :empty
+object Base
+    member type: Empty optional=False
+enum Empty []
+object Union
+    base Base
+    tag type
diff --git a/tests/qapi-schema/union-empty.err b/tests/qapi-schema/union-empty.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/union-empty.exit b/tests/qapi-schema/union-empty.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/union-empty.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/union-empty.json b/tests/qapi-schema/union-empty.json
new file mode 100644
index 0000000..1785007
--- /dev/null
+++ b/tests/qapi-schema/union-empty.json
@@ -0,0 +1,2 @@
+# FIXME - unions should not be empty
+{ 'union': 'Union', 'data': { } }
diff --git a/tests/qapi-schema/union-empty.out b/tests/qapi-schema/union-empty.out
new file mode 100644
index 0000000..8b5a7bf
--- /dev/null
+++ b/tests/qapi-schema/union-empty.out
@@ -0,0 +1,3 @@
+object :empty
+object Union
+enum UnionKind []
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 05/46] qapi: Test use of 'number' within alternates
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (3 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 04/46] qapi: Add tests for empty unions Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-24 14:36   ` Markus Armbruster
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 06/46] qapi: Improve 'include' error message Eric Blake
                   ` (41 subsequent siblings)
  46 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Add some testsuite exposure for use of a 'number' as part of
an alternate.  The current state of the tree has a few bugs
exposed by this: our input parser depends on the ordering of
how the qapi schema declared the alternate, and the parser
does not accept integers for a 'number' in an alternate even
though it does for numbers outside of an alternate.

Mixing 'int' and 'number' in the same alternate is unusual,
since both are supplied by json-numbers, but there does not
seem to be a technical reason to forbid it given that our
json lexer distinguishes between json-numbers that can be
represented as an int vs. those that cannot.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 tests/qapi-schema/qapi-schema-test.json |   8 ++
 tests/qapi-schema/qapi-schema-test.out  |  24 ++++++
 tests/test-qmp-input-visitor.c          | 129 +++++++++++++++++++++++++++++++-
 3 files changed, 158 insertions(+), 3 deletions(-)

diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index c904db4..7593ecb 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -65,6 +65,14 @@
 { 'struct': 'UserDefC',
   'data': { 'string1': 'str', 'string2': 'str' } }

+# for testing use of 'number' within alternates
+{ 'alternate': 'AltOne', 'data': { 's': 'str', 'b': 'bool' } }
+{ 'alternate': 'AltTwo', 'data': { 's': 'str', 'n': 'number' } }
+{ 'alternate': 'AltThree', 'data': { 'n': 'number', 's': 'str' } }
+{ 'alternate': 'AltFour', 'data': { 's': 'str', 'i': 'int' } }
+{ 'alternate': 'AltFive', 'data': { 'i': 'int', 'n': 'number' } }
+{ 'alternate': 'AltSix', 'data': { 'n': 'number', 'i': 'int' } }
+
 # for testing native lists
 { 'union': 'UserDefNativeListUnion',
   'data': { 'integer': ['int'],
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 28a0b3c..de29a45 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -53,6 +53,30 @@ object :obj-user_def_cmd2-arg
 object :obj-user_def_cmd3-arg
     member a: int optional=False
     member b: int optional=True
+alternate AltFive
+    case i: int
+    case n: number
+enum AltFiveKind ['i', 'n']
+alternate AltFour
+    case s: str
+    case i: int
+enum AltFourKind ['s', 'i']
+alternate AltOne
+    case s: str
+    case b: bool
+enum AltOneKind ['s', 'b']
+alternate AltSix
+    case n: number
+    case i: int
+enum AltSixKind ['n', 'i']
+alternate AltThree
+    case n: number
+    case s: str
+enum AltThreeKind ['n', 's']
+alternate AltTwo
+    case s: str
+    case n: number
+enum AltTwoKind ['s', 'n']
 event EVENT_A None
 event EVENT_B None
 event EVENT_C :obj-EVENT_C-arg
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index 61715b3..cd41847 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -368,15 +368,136 @@ static void test_visitor_in_alternate(TestInputVisitorData *data,
 {
     Visitor *v;
     Error *err = NULL;
-    UserDefAlternate *tmp;
+    UserDefAlternate *tmp = NULL;

     v = visitor_input_test_init(data, "42");

-    visit_type_UserDefAlternate(v, &tmp, NULL, &err);
-    g_assert(err == NULL);
+    visit_type_UserDefAlternate(v, &tmp, NULL, &error_abort);
     g_assert_cmpint(tmp->kind, ==, USER_DEF_ALTERNATE_KIND_I);
     g_assert_cmpint(tmp->i, ==, 42);
     qapi_free_UserDefAlternate(tmp);
+    tmp = NULL;
+
+    v = visitor_input_test_init(data, "'string'");
+
+    visit_type_UserDefAlternate(v, &tmp, NULL, &error_abort);
+    g_assert_cmpint(tmp->kind, ==, USER_DEF_ALTERNATE_KIND_S);
+    g_assert_cmpstr(tmp->s, ==, "string");
+    qapi_free_UserDefAlternate(tmp);
+    tmp = NULL;
+
+    v = visitor_input_test_init(data, "false");
+
+    visit_type_UserDefAlternate(v, &tmp, NULL, &err);
+    g_assert(err);
+    error_free(err);
+    err = NULL;
+    qapi_free_UserDefAlternate(tmp);
+}
+
+static void test_visitor_in_alternate_number(TestInputVisitorData *data,
+                                             const void *unused)
+{
+    Visitor *v;
+    Error *err = NULL;
+    AltOne *one = NULL;
+    AltTwo *two = NULL;
+    AltThree *three = NULL;
+    AltFour *four = NULL;
+    AltFive *five = NULL;
+    AltSix *six = NULL;
+
+    /* Parsing an int */
+
+    v = visitor_input_test_init(data, "42");
+    visit_type_AltOne(v, &one, NULL, &err);
+    g_assert(err);
+    qapi_free_AltOne(one);
+    one = NULL;
+
+    /* FIXME: Integers should parse as numbers */
+    v = visitor_input_test_init(data, "42");
+    visit_type_AltTwo(v, &two, NULL, &err);
+    g_assert(err);
+    error_free(err);
+    err = NULL;
+    qapi_free_AltTwo(two);
+    one = NULL;
+
+    /* FIXME: Order of alternate should not affect semantics */
+    v = visitor_input_test_init(data, "42");
+    visit_type_AltThree(v, &three, NULL, &error_abort);
+    g_assert_cmpint(three->kind, ==, ALT_THREE_KIND_N);
+    g_assert_cmpfloat(three->n, ==, 42);
+    qapi_free_AltThree(three);
+    one = NULL;
+
+    v = visitor_input_test_init(data, "42");
+    visit_type_AltFour(v, &four, NULL, &error_abort);
+    g_assert_cmpint(four->kind, ==, ALT_FOUR_KIND_I);
+    g_assert_cmpint(four->i, ==, 42);
+    qapi_free_AltFour(four);
+    one = NULL;
+
+    v = visitor_input_test_init(data, "42");
+    visit_type_AltFive(v, &five, NULL, &error_abort);
+    g_assert_cmpint(five->kind, ==, ALT_FIVE_KIND_I);
+    g_assert_cmpint(five->i, ==, 42);
+    qapi_free_AltFive(five);
+    one = NULL;
+
+    v = visitor_input_test_init(data, "42");
+    visit_type_AltSix(v, &six, NULL, &error_abort);
+    g_assert_cmpint(six->kind, ==, ALT_SIX_KIND_I);
+    g_assert_cmpint(six->i, ==, 42);
+    qapi_free_AltSix(six);
+    one = NULL;
+
+    /* Parsing a double */
+
+    v = visitor_input_test_init(data, "42.5");
+    visit_type_AltOne(v, &one, NULL, &err);
+    g_assert(err);
+    error_free(err);
+    err = NULL;
+    qapi_free_AltOne(one);
+    one = NULL;
+
+    v = visitor_input_test_init(data, "42.5");
+    visit_type_AltTwo(v, &two, NULL, &error_abort);
+    g_assert_cmpint(two->kind, ==, ALT_TWO_KIND_N);
+    g_assert_cmpfloat(two->n, ==, 42.5);
+    qapi_free_AltTwo(two);
+    two = NULL;
+
+    v = visitor_input_test_init(data, "42.5");
+    visit_type_AltThree(v, &three, NULL, &error_abort);
+    g_assert_cmpint(three->kind, ==, ALT_THREE_KIND_N);
+    g_assert_cmpfloat(three->n, ==, 42.5);
+    qapi_free_AltThree(three);
+    three = NULL;
+
+    v = visitor_input_test_init(data, "42.5");
+    visit_type_AltFour(v, &four, NULL, &err);
+    g_assert(err);
+    error_free(err);
+    err = NULL;
+    qapi_free_AltFour(four);
+    four = NULL;
+
+    v = visitor_input_test_init(data, "42.5");
+    visit_type_AltFive(v, &five, NULL, &error_abort);
+    g_assert_cmpint(five->kind, ==, ALT_FIVE_KIND_N);
+    g_assert_cmpfloat(five->n, ==, 42.5);
+    qapi_free_AltFive(five);
+    five = NULL;
+
+    v = visitor_input_test_init(data, "42.5");
+    visit_type_AltSix(v, &six, NULL, &error_abort);
+    g_assert_cmpint(six->kind, ==, ALT_SIX_KIND_N);
+    g_assert_cmpint(six->n, ==, 42.5);
+    qapi_free_AltSix(six);
+    six = NULL;
 }

 static void test_native_list_integer_helper(TestInputVisitorData *data,
@@ -720,6 +841,8 @@ int main(int argc, char **argv)
                            &in_visitor_data, test_visitor_in_alternate);
     input_visitor_test_add("/visitor/input/errors",
                            &in_visitor_data, test_visitor_in_errors);
+    input_visitor_test_add("/visitor/input/alternate-number",
+                           &in_visitor_data, test_visitor_in_alternate_number);
     input_visitor_test_add("/visitor/input/native_list/int",
                            &in_visitor_data,
                            test_visitor_in_native_list_int);
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 06/46] qapi: Improve 'include' error message
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (4 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 05/46] qapi: Test use of 'number' within alternates Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-24 14:39   ` Markus Armbruster
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call Eric Blake
                   ` (40 subsequent siblings)
  46 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Use of '"...%s" % include' to print non-strings can lead to
ugly messages, such as this (if the .json change is applied
without the qapi.py change):
 Expected a file name (string), got: OrderedDict()

Better is to just omit the actual non-string value in the
message.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi.py                         | 4 ++--
 tests/qapi-schema/include-non-file.err  | 2 +-
 tests/qapi-schema/include-non-file.json | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 31c4bcc..007349e 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -138,8 +138,8 @@ class QAPISchemaParser(object):
                                         "Invalid 'include' directive")
                 include = expr["include"]
                 if not isinstance(include, str):
-                    raise QAPIExprError(expr_info, 'Expected a file name '
-                                        '(string), got: %s' % include)
+                    raise QAPIExprError(expr_info,
+                                        "Expected a string for 'include'")
                 incl_abs_fname = os.path.join(os.path.dirname(abs_fname),
                                               include)
                 # catch inclusion cycle
diff --git a/tests/qapi-schema/include-non-file.err b/tests/qapi-schema/include-non-file.err
index 9658c78..079120b 100644
--- a/tests/qapi-schema/include-non-file.err
+++ b/tests/qapi-schema/include-non-file.err
@@ -1 +1 @@
-tests/qapi-schema/include-non-file.json:1: Expected a file name (string), got: ['foo', 'bar']
+tests/qapi-schema/include-non-file.json:1: Expected a string for 'include'
diff --git a/tests/qapi-schema/include-non-file.json b/tests/qapi-schema/include-non-file.json
index cd43c3f..4711aa4 100644
--- a/tests/qapi-schema/include-non-file.json
+++ b/tests/qapi-schema/include-non-file.json
@@ -1 +1 @@
-{ 'include': [ 'foo', 'bar' ] }
+{ 'include': {} }
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (5 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 06/46] qapi: Improve 'include' error message Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-24 14:58   ` Markus Armbruster
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 08/46] qapi: Reuse code for flat union base validation Eric Blake
                   ` (39 subsequent siblings)
  46 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Due to the existing semantics of the error_set() family,
generated sequences in the qapi visitors such as:

    visit_start_implicit_struct(m, (void **)obj, sizeof(FOO), &err);
        if (!err) {
            visit_type_FOO_fields(m, obj, errp);
            visit_end_implicit_struct(m, &err);
        }
    error_propagate(errp, err);

are risky: if visit_type_FOO_fields() sets errp, and then
visit_end_implicit_struct() also encounters an error, the
attempt to overwrite the first error will cause an abort().
Obviously, we weren't triggering this situation (none of the
existing callbacks for visit_end_implicit_struct() currently
try to set an error), but it is better to not even cause the
problem in the first place.

Meanwhile, in spite of the poor documentation of the qapi
visitors, we want to guarantee that if a visit_start_*()
succeeds, then the matching visit_end_*() will be called.
The options are to either propagate and clear a local err
multiple times:

    visit_start_implicit_struct(m, (void **)obj, sizeof(FOO), &err);
        if (!err) {
            visit_type_FOO_fields(m, obj, &err);
            if (err) {
                error_propagate(errp, err);
                err = NULL;
            }
            visit_end_implicit_struct(m, &err);
        }
    error_propagate(errp, err);

or, as this patch does, just pass in NULL to ignore further
errors once an error has occurred.

    visit_start_implicit_struct(m, (void **)obj, sizeof(FOO), &err);
        if (!err) {
            visit_type_FOO_fields(m, obj, &err);
            visit_end_implicit_struct(m, err ? NULL : &err);
        }
    error_propagate(errp, err);

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi-visit.py | 20 +++++++-------------
 1 file changed, 7 insertions(+), 13 deletions(-)

diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 97343cf..d911b20 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -51,8 +51,8 @@ static void visit_type_implicit_%(c_type)s(Visitor *m, %(c_type)s **obj, Error *

     visit_start_implicit_struct(m, (void **)obj, sizeof(%(c_type)s), &err);
     if (!err) {
-        visit_type_%(c_type)s_fields(m, obj, errp);
-        visit_end_implicit_struct(m, &err);
+        visit_type_%(c_type)s_fields(m, obj, &err);
+        visit_end_implicit_struct(m, err ? NULL : &err);
     }
     error_propagate(errp, err);
 }
@@ -143,9 +143,9 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error
     visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err);
     if (!err) {
         if (*obj) {
-            visit_type_%(c_name)s_fields(m, obj, errp);
+            visit_type_%(c_name)s_fields(m, obj, &err);
         }
-        visit_end_struct(m, &err);
+        visit_end_struct(m, err ? NULL : &err);
     }
     error_propagate(errp, err);
 }
@@ -175,9 +175,7 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error
         visit_type_%(c_elt_type)s(m, &native_i->value, NULL, &err);
     }

-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_list(m, &err);
+    visit_end_list(m, err ? NULL : &err);
 out:
     error_propagate(errp, err);
 }
@@ -231,9 +229,7 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error
         abort();
     }
 out_end:
-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_implicit_struct(m, &err);
+    visit_end_implicit_struct(m, err ? NULL : &err);
 out:
     error_propagate(errp, err);
 }
@@ -330,10 +326,8 @@ out_obj:
         error_propagate(errp, err);
         err = NULL;
         visit_end_union(m, !!(*obj)->data, &err);
-        error_propagate(errp, err);
-        err = NULL;
     }
-    visit_end_struct(m, &err);
+    visit_end_struct(m, err ? NULL : &err);
 out:
     error_propagate(errp, err);
 }
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 08/46] qapi: Reuse code for flat union base validation
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (6 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-25 16:30   ` Markus Armbruster
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 09/46] qapi: Use consistent generated code patterns Eric Blake
                   ` (38 subsequent siblings)
  46 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Rather than open-code the check for a valid base type, we
should reuse the common functionality. This allows for
consistent error messages, and also makes it easier for a
later patch to turn on support for inline anonymous base
structures.

Test flat-union-inline is updated to test only one feature
(anonymous branch dictionaries), which can be implemented
independently (test flat-union-bad-base already covers the
idea of an anonymous base dictionary).

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi.py                             | 11 +++++------
 tests/qapi-schema/flat-union-bad-base.err   |  2 +-
 tests/qapi-schema/flat-union-base-any.err   |  2 +-
 tests/qapi-schema/flat-union-base-union.err |  2 +-
 tests/qapi-schema/flat-union-inline.err     |  2 +-
 tests/qapi-schema/flat-union-inline.json    |  4 ++--
 tests/qapi-schema/flat-union-no-base.err    |  2 +-
 tests/qapi-schema/union-invalid-base.err    |  2 +-
 8 files changed, 13 insertions(+), 14 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 007349e..6f4e471 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -560,15 +560,14 @@ def check_union(expr, expr_info):
     # Else, it's a flat union.
     else:
         # The object must have a string member 'base'.
-        if not isinstance(base, str):
+        check_type(expr_info, "'base' for union '%s'" % name,
+                   base, allow_metas=['struct'])
+        if not base:
             raise QAPIExprError(expr_info,
-                                "Flat union '%s' must have a string base field"
+                                "Flat union '%s' must have a valid base"
                                 % name)
         base_fields = find_base_fields(base)
-        if not base_fields:
-            raise QAPIExprError(expr_info,
-                                "Base '%s' is not a valid struct"
-                                % base)
+        assert base_fields

         # The value of member 'discriminator' must name a non-optional
         # member of the base struct.
diff --git a/tests/qapi-schema/flat-union-bad-base.err b/tests/qapi-schema/flat-union-bad-base.err
index f9c31b2..79b8a71 100644
--- a/tests/qapi-schema/flat-union-bad-base.err
+++ b/tests/qapi-schema/flat-union-bad-base.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-bad-base.json:9: Flat union 'TestUnion' must have a string base field
+tests/qapi-schema/flat-union-bad-base.json:9: 'base' for union 'TestUnion' should be a type name
diff --git a/tests/qapi-schema/flat-union-base-any.err b/tests/qapi-schema/flat-union-base-any.err
index ad4d629..646f1c9 100644
--- a/tests/qapi-schema/flat-union-base-any.err
+++ b/tests/qapi-schema/flat-union-base-any.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-base-any.json:8: Base 'any' is not a valid struct
+tests/qapi-schema/flat-union-base-any.json:8: 'base' for union 'TestUnion' cannot use built-in type 'any'
diff --git a/tests/qapi-schema/flat-union-base-union.err b/tests/qapi-schema/flat-union-base-union.err
index ede9859..d50e687 100644
--- a/tests/qapi-schema/flat-union-base-union.err
+++ b/tests/qapi-schema/flat-union-base-union.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-base-union.json:11: Base 'UnionBase' is not a valid struct
+tests/qapi-schema/flat-union-base-union.json:11: 'base' for union 'TestUnion' cannot use union type 'UnionBase'
diff --git a/tests/qapi-schema/flat-union-inline.err b/tests/qapi-schema/flat-union-inline.err
index ec58627..2333358 100644
--- a/tests/qapi-schema/flat-union-inline.err
+++ b/tests/qapi-schema/flat-union-inline.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-inline.json:7: Flat union 'TestUnion' must have a string base field
+tests/qapi-schema/flat-union-inline.json:7: Member 'value1' of union 'TestUnion' should be a type name
diff --git a/tests/qapi-schema/flat-union-inline.json b/tests/qapi-schema/flat-union-inline.json
index 6bfdd65..62c7cda 100644
--- a/tests/qapi-schema/flat-union-inline.json
+++ b/tests/qapi-schema/flat-union-inline.json
@@ -1,11 +1,11 @@
 # we require branches to be a struct name
-# TODO: should we allow anonymous inline types?
+# TODO: should we allow anonymous inline branch types?
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
 { 'struct': 'Base',
   'data': { 'enum1': 'TestEnum', 'kind': 'str' } }
 { 'union': 'TestUnion',
-  'base': { 'enum1': 'TestEnum', 'kind': 'str' },
+  'base': 'Base',
   'discriminator': 'enum1',
   'data': { 'value1': { 'string': 'str' },
             'value2': { 'integer': 'int' } } }
diff --git a/tests/qapi-schema/flat-union-no-base.err b/tests/qapi-schema/flat-union-no-base.err
index bb3f708..253e251 100644
--- a/tests/qapi-schema/flat-union-no-base.err
+++ b/tests/qapi-schema/flat-union-no-base.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-no-base.json:9: Flat union 'TestUnion' must have a string base field
+tests/qapi-schema/flat-union-no-base.json:9: Flat union 'TestUnion' must have a valid base
diff --git a/tests/qapi-schema/union-invalid-base.err b/tests/qapi-schema/union-invalid-base.err
index 9f63796..03d7b97 100644
--- a/tests/qapi-schema/union-invalid-base.err
+++ b/tests/qapi-schema/union-invalid-base.err
@@ -1 +1 @@
-tests/qapi-schema/union-invalid-base.json:8: Base 'int' is not a valid struct
+tests/qapi-schema/union-invalid-base.json:8: 'base' for union 'TestUnion' cannot use built-in type 'int'
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 09/46] qapi: Use consistent generated code patterns
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (7 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 08/46] qapi: Reuse code for flat union base validation Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-25 16:54   ` Markus Armbruster
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 10/46] qapi: Merge generation of per-member visits Eric Blake
                   ` (37 subsequent siblings)
  46 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

We had some pointless differences in the generated code for visit,
command marshalling, and events; unifying them makes it easier for
future patches to consolidate to common helper functions.
- Consistently name the error variable 'err'
- Consistently use the labels 'out' and 'out_obj'
- If allocation fails, jump to the right label rather than
indenting everything else

No change in semantics to the generated code.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi-commands.py |  22 +++++-----
 scripts/qapi-event.py    |  26 +++++------
 scripts/qapi-visit.py    | 111 ++++++++++++++++++++++++-----------------------
 3 files changed, 80 insertions(+), 79 deletions(-)

diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
index 810a897..2151120 100644
--- a/scripts/qapi-commands.py
+++ b/scripts/qapi-commands.py
@@ -53,14 +53,14 @@ def gen_call(name, arg_type, ret_type):
     push_indent()
     ret = mcgen('''

-%(lhs)sqmp_%(c_name)s(%(args)s&local_err);
+%(lhs)sqmp_%(c_name)s(%(args)s&err);
 ''',
                 c_name=c_name(name), args=argstr, lhs=lhs)
     if ret_type:
-        ret += gen_err_check('local_err')
+        ret += gen_err_check('err')
         ret += mcgen('''

-qmp_marshal_output_%(c_name)s(retval, ret, &local_err);
+qmp_marshal_output_%(c_name)s(retval, ret, &err);
 ''',
                      c_name=ret_type.c_name())
     pop_indent()
@@ -69,7 +69,7 @@ qmp_marshal_output_%(c_name)s(retval, ret, &local_err);

 def gen_marshal_vars(arg_type, ret_type):
     ret = mcgen('''
-    Error *local_err = NULL;
+    Error *err = NULL;
 ''')

     push_indent()
@@ -127,8 +127,8 @@ md = qapi_dealloc_visitor_new();
 v = qapi_dealloc_get_visitor(md);
 ''')
     else:
-        errparg = '&local_err'
-        errarg = 'local_err'
+        errparg = '&err'
+        errarg = 'err'
         ret += mcgen('''
 v = qmp_input_get_visitor(mi);
 ''')
@@ -171,20 +171,20 @@ def gen_marshal_output(ret_type):

 static void qmp_marshal_output_%(c_name)s(%(c_type)s ret_in, QObject **ret_out, Error **errp)
 {
-    Error *local_err = NULL;
+    Error *err = NULL;
     QmpOutputVisitor *mo = qmp_output_visitor_new();
     QapiDeallocVisitor *md;
     Visitor *v;

     v = qmp_output_get_visitor(mo);
-    visit_type_%(c_name)s(v, &ret_in, "unused", &local_err);
-    if (local_err) {
+    visit_type_%(c_name)s(v, &ret_in, "unused", &err);
+    if (err) {
         goto out;
     }
     *ret_out = qmp_output_get_qobject(mo);

 out:
-    error_propagate(errp, local_err);
+    error_propagate(errp, err);
     qmp_output_visitor_cleanup(mo);
     md = qapi_dealloc_visitor_new();
     v = qapi_dealloc_get_visitor(md);
@@ -227,7 +227,7 @@ def gen_marshal(name, arg_type, ret_type):
 out:
 ''')
     ret += mcgen('''
-    error_propagate(errp, local_err);
+    error_propagate(errp, err);
 ''')
     ret += gen_marshal_input_visit(arg_type, dealloc=True)
     ret += mcgen('''
diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py
index d15fad9..b43bbc2 100644
--- a/scripts/qapi-event.py
+++ b/scripts/qapi-event.py
@@ -34,7 +34,7 @@ def gen_event_send(name, arg_type):
 %(proto)s
 {
     QDict *qmp;
-    Error *local_err = NULL;
+    Error *err = NULL;
     QMPEventFuncEmit emit;
 ''',
                 proto=gen_event_send_proto(name, arg_type))
@@ -67,9 +67,9 @@ def gen_event_send(name, arg_type):
     g_assert(v);

     /* Fake visit, as if all members are under a structure */
-    visit_start_struct(v, NULL, "", "%(name)s", 0, &local_err);
-    if (local_err) {
-        goto clean;
+    visit_start_struct(v, NULL, "", "%(name)s", 0, &err);
+    if (err) {
+        goto out;
     }

 ''',
@@ -90,9 +90,9 @@ def gen_event_send(name, arg_type):
                 cast = ''

             ret += mcgen('''
-    visit_type_%(c_type)s(v, %(cast)s&%(c_name)s, "%(name)s", &local_err);
-    if (local_err) {
-        goto clean;
+    visit_type_%(c_type)s(v, %(cast)s&%(c_name)s, "%(name)s", &err);
+    if (err) {
+        goto out;
     }
 ''',
                          cast=cast,
@@ -108,9 +108,9 @@ def gen_event_send(name, arg_type):

         ret += mcgen('''

-    visit_end_struct(v, &local_err);
-    if (local_err) {
-        goto clean;
+    visit_end_struct(v, &err);
+    if (err) {
+        goto out;
     }

     obj = qmp_output_get_qobject(qov);
@@ -120,18 +120,18 @@ def gen_event_send(name, arg_type):
 ''')

     ret += mcgen('''
-    emit(%(c_enum)s, qmp, &local_err);
+    emit(%(c_enum)s, qmp, &err);

 ''',
                  c_enum=c_enum_const(event_enum_name, name))

     if arg_type and arg_type.members:
         ret += mcgen('''
- clean:
+ out:
     qmp_output_visitor_cleanup(qov);
 ''')
     ret += mcgen('''
-    error_propagate(errp, local_err);
+    error_propagate(errp, err);
     QDECREF(qmp);
 }
 ''')
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index d911b20..9c0328d 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -24,7 +24,7 @@ def gen_visit_decl(name, scalar=False):
     if not scalar:
         c_type += '*'
     return mcgen('''
-void visit_type_%(c_name)s(Visitor *m, %(c_type)sobj, const char *name, Error **errp);
+void visit_type_%(c_name)s(Visitor *v, %(c_type)sobj, const char *name, Error **errp);
 ''',
                  c_name=c_name(name), c_type=c_type)

@@ -39,20 +39,20 @@ def gen_visit_implicit_struct(typ):
         # Need a forward declaration
         ret += mcgen('''

-static void visit_type_%(c_type)s_fields(Visitor *m, %(c_type)s **obj, Error **errp);
+static void visit_type_%(c_type)s_fields(Visitor *v, %(c_type)s **obj, Error **errp);
 ''',
                      c_type=typ.c_name())

     ret += mcgen('''

-static void visit_type_implicit_%(c_type)s(Visitor *m, %(c_type)s **obj, Error **errp)
+static void visit_type_implicit_%(c_type)s(Visitor *v, %(c_type)s **obj, Error **errp)
 {
     Error *err = NULL;

-    visit_start_implicit_struct(m, (void **)obj, sizeof(%(c_type)s), &err);
+    visit_start_implicit_struct(v, (void **)obj, sizeof(%(c_type)s), &err);
     if (!err) {
-        visit_type_%(c_type)s_fields(m, obj, &err);
-        visit_end_implicit_struct(m, err ? NULL : &err);
+        visit_type_%(c_type)s_fields(v, obj, &err);
+        visit_end_implicit_struct(v, err ? NULL : &err);
     }
     error_propagate(errp, err);
 }
@@ -71,7 +71,7 @@ def gen_visit_struct_fields(name, base, members):

     ret += mcgen('''

-static void visit_type_%(c_name)s_fields(Visitor *m, %(c_name)s **obj, Error **errp)
+static void visit_type_%(c_name)s_fields(Visitor *v, %(c_name)s **obj, Error **errp)
 {
     Error *err = NULL;

@@ -81,7 +81,7 @@ static void visit_type_%(c_name)s_fields(Visitor *m, %(c_name)s **obj, Error **e

     if base:
         ret += mcgen('''
-visit_type_implicit_%(c_type)s(m, &(*obj)->%(c_name)s, &err);
+visit_type_implicit_%(c_type)s(v, &(*obj)->%(c_name)s, &err);
 if (err) {
     goto out;
 }
@@ -91,14 +91,14 @@ if (err) {
     for memb in members:
         if memb.optional:
             ret += mcgen('''
-visit_optional(m, &(*obj)->has_%(c_name)s, "%(name)s", &err);
+visit_optional(v, &(*obj)->has_%(c_name)s, "%(name)s", &err);
 if (!err && (*obj)->has_%(c_name)s) {
 ''',
                          c_name=c_name(memb.name), name=memb.name)
             push_indent()

         ret += mcgen('''
-visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "%(name)s", &err);
+visit_type_%(c_type)s(v, &(*obj)->%(c_name)s, "%(name)s", &err);
 ''',
                      c_type=memb.type.c_name(), c_name=c_name(memb.name),
                      name=memb.name)
@@ -136,16 +136,16 @@ def gen_visit_struct(name, base, members):
     # call qapi_free_FOO() to avoid a memory leak of the partial FOO.
     ret += mcgen('''

-void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error **errp)
+void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error **errp)
 {
     Error *err = NULL;

-    visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err);
+    visit_start_struct(v, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err);
     if (!err) {
         if (*obj) {
-            visit_type_%(c_name)s_fields(m, obj, &err);
+            visit_type_%(c_name)s_fields(v, obj, &err);
         }
-        visit_end_struct(m, err ? NULL : &err);
+        visit_end_struct(v, err ? NULL : &err);
     }
     error_propagate(errp, err);
 }
@@ -158,24 +158,24 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error
 def gen_visit_list(name, element_type):
     return mcgen('''

-void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error **errp)
+void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error **errp)
 {
     Error *err = NULL;
     GenericList *i, **prev;

-    visit_start_list(m, name, &err);
+    visit_start_list(v, name, &err);
     if (err) {
         goto out;
     }

     for (prev = (GenericList **)obj;
-         !err && (i = visit_next_list(m, prev, &err)) != NULL;
+         !err && (i = visit_next_list(v, prev, &err)) != NULL;
          prev = &i) {
         %(c_name)s *native_i = (%(c_name)s *)i;
-        visit_type_%(c_elt_type)s(m, &native_i->value, NULL, &err);
+        visit_type_%(c_elt_type)s(v, &native_i->value, NULL, &err);
     }

-    visit_end_list(m, err ? NULL : &err);
+    visit_end_list(v, err ? NULL : &err);
 out:
     error_propagate(errp, err);
 }
@@ -186,9 +186,9 @@ out:
 def gen_visit_enum(name):
     return mcgen('''

-void visit_type_%(c_name)s(Visitor *m, %(c_name)s *obj, const char *name, Error **errp)
+void visit_type_%(c_name)s(Visitor *v, %(c_name)s *obj, const char *name, Error **errp)
 {
-    visit_type_enum(m, (int *)obj, %(c_name)s_lookup, "%(name)s", name, errp);
+    visit_type_enum(v, (int *)obj, %(c_name)s_lookup, "%(name)s", name, errp);
 }
 ''',
                  c_name=c_name(name), name=name)
@@ -197,17 +197,17 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s *obj, const char *name, Error
 def gen_visit_alternate(name, variants):
     ret = mcgen('''

-void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error **errp)
+void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error **errp)
 {
     Error *err = NULL;

-    visit_start_implicit_struct(m, (void**) obj, sizeof(%(c_name)s), &err);
+    visit_start_implicit_struct(v, (void**) obj, sizeof(%(c_name)s), &err);
     if (err) {
         goto out;
     }
-    visit_get_next_type(m, (int*) &(*obj)->kind, %(c_name)s_qtypes, name, &err);
+    visit_get_next_type(v, (int*) &(*obj)->kind, %(c_name)s_qtypes, name, &err);
     if (err) {
-        goto out_end;
+        goto out_obj;
     }
     switch ((*obj)->kind) {
 ''',
@@ -216,7 +216,7 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error
     for var in variants.variants:
         ret += mcgen('''
     case %(case)s:
-        visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, name, &err);
+        visit_type_%(c_type)s(v, &(*obj)->%(c_name)s, name, &err);
         break;
 ''',
                      case=c_enum_const(variants.tag_member.type.name,
@@ -228,8 +228,8 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error
     default:
         abort();
     }
-out_end:
-    visit_end_implicit_struct(m, err ? NULL : &err);
+out_obj:
+    visit_end_implicit_struct(v, err ? NULL : &err);
 out:
     error_propagate(errp, err);
 }
@@ -252,24 +252,26 @@ def gen_visit_union(name, base, variants):

     ret += mcgen('''

-void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error **errp)
+void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error **errp)
 {
     Error *err = NULL;

-    visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err);
+    visit_start_struct(v, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err);
     if (err) {
         goto out;
     }
-    if (*obj) {
+    if (!*obj) {
+        goto out_obj;
+    }
 ''',
                  c_name=c_name(name), name=name)

     if base:
         ret += mcgen('''
-        visit_type_%(c_name)s_fields(m, obj, &err);
-        if (err) {
-            goto out_obj;
-        }
+    visit_type_%(c_name)s_fields(v, obj, &err);
+    if (err) {
+        goto out_obj;
+    }
 ''',
                      c_name=c_name(name))

@@ -278,14 +280,14 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error
         # we pointlessly use a different key for simple unions
         tag_key = 'type'
     ret += mcgen('''
-        visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "%(name)s", &err);
-        if (err) {
-            goto out_obj;
-        }
-        if (!visit_start_union(m, !!(*obj)->data, &err) || err) {
-            goto out_obj;
-        }
-        switch ((*obj)->%(c_name)s) {
+    visit_type_%(c_type)s(v, &(*obj)->%(c_name)s, "%(name)s", &err);
+    if (err) {
+        goto out_obj;
+    }
+    if (!visit_start_union(v, !!(*obj)->data, &err) || err) {
+        goto out_obj;
+    }
+    switch ((*obj)->%(c_name)s) {
 ''',
                  c_type=variants.tag_member.type.c_name(),
                  # TODO ugly special case for simple union
@@ -298,36 +300,35 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error
         # TODO ugly special case for simple union
         simple_union_type = var.simple_union_type()
         ret += mcgen('''
-        case %(case)s:
+    case %(case)s:
 ''',
                      case=c_enum_const(variants.tag_member.type.name,
                                        var.name))
         if simple_union_type:
             ret += mcgen('''
-            visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "data", &err);
+        visit_type_%(c_type)s(v, &(*obj)->%(c_name)s, "data", &err);
 ''',
                          c_type=simple_union_type.c_name(),
                          c_name=c_name(var.name))
         else:
             ret += mcgen('''
-            visit_type_implicit_%(c_type)s(m, &(*obj)->%(c_name)s, &err);
+        visit_type_implicit_%(c_type)s(v, &(*obj)->%(c_name)s, &err);
 ''',
                          c_type=var.type.c_name(),
                          c_name=c_name(var.name))
         ret += mcgen('''
-            break;
+        break;
 ''')

     ret += mcgen('''
-        default:
-            abort();
-        }
-out_obj:
-        error_propagate(errp, err);
-        err = NULL;
-        visit_end_union(m, !!(*obj)->data, &err);
+    default:
+        abort();
     }
-    visit_end_struct(m, err ? NULL : &err);
+out_obj:
+    error_propagate(errp, err);
+    err = NULL;
+    visit_end_union(v, !!(*obj)->data, &err);
+    visit_end_struct(v, err ? NULL : &err);
 out:
     error_propagate(errp, err);
 }
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 10/46] qapi: Merge generation of per-member visits
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (8 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 09/46] qapi: Use consistent generated code patterns Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-28  6:17   ` Markus Armbruster
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 11/46] qapi: Don't use info as witness of implicit object type Eric Blake
                   ` (36 subsequent siblings)
  46 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Consolidate the code between visit, command marshalling, and
event generation that iterates over the members of a struct.
It reduces code duplication in the generator, with no change to
generated marshal code, slightly more verbose visit code:

|     visit_optional(v, &(*obj)->has_device, "device", &err);
|-    if (!err && (*obj)->has_device) {
|-        visit_type_str(v, &(*obj)->device, "device", &err);
|-    }
|     if (err) {
|         goto out;
|     }
|+    if ((*obj)->has_device) {
|+        visit_type_str(v, &(*obj)->device, "device", &err);
|+        if (err) {
|+            goto out;
|+        }
|+    }

and slightly more verbose event code (recall that the qmp
output visitor has a no-op visit_optional()):

|+    visit_optional(v, &has_offset, "offset", &err);
|+    if (err) {
|+        goto out;
|+    }
|     if (has_offset) {
|         visit_type_int(v, &offset, "offset", &err);

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi-commands.py | 38 +---------------------------------
 scripts/qapi-event.py    | 35 +++-----------------------------
 scripts/qapi-visit.py    | 26 +-----------------------
 scripts/qapi.py          | 53 ++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 58 insertions(+), 94 deletions(-)

diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
index 2151120..55287b1 100644
--- a/scripts/qapi-commands.py
+++ b/scripts/qapi-commands.py
@@ -25,17 +25,6 @@ def gen_command_decl(name, arg_type, ret_type):
                  params=gen_params(arg_type, 'Error **errp'))


-def gen_err_check(err):
-    if not err:
-        return ''
-    return mcgen('''
-if (%(err)s) {
-    goto out;
-}
-''',
-                 err=err)
-
-
 def gen_call(name, arg_type, ret_type):
     ret = ''

@@ -119,7 +108,6 @@ def gen_marshal_input_visit(arg_type, dealloc=False):
     push_indent()

     if dealloc:
-        errparg = 'NULL'
         errarg = None
         ret += mcgen('''
 qmp_input_visitor_cleanup(mi);
@@ -127,36 +115,12 @@ md = qapi_dealloc_visitor_new();
 v = qapi_dealloc_get_visitor(md);
 ''')
     else:
-        errparg = '&err'
         errarg = 'err'
         ret += mcgen('''
 v = qmp_input_get_visitor(mi);
 ''')

-    for memb in arg_type.members:
-        if memb.optional:
-            ret += mcgen('''
-visit_optional(v, &has_%(c_name)s, "%(name)s", %(errp)s);
-''',
-                         c_name=c_name(memb.name), name=memb.name,
-                         errp=errparg)
-            ret += gen_err_check(errarg)
-            ret += mcgen('''
-if (has_%(c_name)s) {
-''',
-                         c_name=c_name(memb.name))
-            push_indent()
-        ret += mcgen('''
-visit_type_%(c_type)s(v, &%(c_name)s, "%(name)s", %(errp)s);
-''',
-                     c_name=c_name(memb.name), name=memb.name,
-                     c_type=memb.type.c_name(), errp=errparg)
-        ret += gen_err_check(errarg)
-        if memb.optional:
-            pop_indent()
-            ret += mcgen('''
-}
-''')
+    ret += gen_visit_fields(arg_type.members, '', False, errarg)

     if dealloc:
         ret += mcgen('''
diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py
index b43bbc2..6c70a06 100644
--- a/scripts/qapi-event.py
+++ b/scripts/qapi-event.py
@@ -74,38 +74,9 @@ def gen_event_send(name, arg_type):

 ''',
                      name=name)
-
-        for memb in arg_type.members:
-            if memb.optional:
-                ret += mcgen('''
-    if (has_%(c_name)s) {
-''',
-                             c_name=c_name(memb.name))
-                push_indent()
-
-            # Ugly: need to cast away the const
-            if memb.type.name == "str":
-                cast = '(char **)'
-            else:
-                cast = ''
-
-            ret += mcgen('''
-    visit_type_%(c_type)s(v, %(cast)s&%(c_name)s, "%(name)s", &err);
-    if (err) {
-        goto out;
-    }
-''',
-                         cast=cast,
-                         c_name=c_name(memb.name),
-                         c_type=memb.type.c_name(),
-                         name=memb.name)
-
-            if memb.optional:
-                pop_indent()
-                ret += mcgen('''
-    }
-''')
-
+        push_indent()
+        ret += gen_visit_fields(arg_type.members, '', True, 'err')
+        pop_indent()
         ret += mcgen('''

     visit_end_struct(v, &err);
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 9c0328d..1f287ba 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -88,31 +88,7 @@ if (err) {
 ''',
                      c_type=base.c_name(), c_name=c_name('base'))

-    for memb in members:
-        if memb.optional:
-            ret += mcgen('''
-visit_optional(v, &(*obj)->has_%(c_name)s, "%(name)s", &err);
-if (!err && (*obj)->has_%(c_name)s) {
-''',
-                         c_name=c_name(memb.name), name=memb.name)
-            push_indent()
-
-        ret += mcgen('''
-visit_type_%(c_type)s(v, &(*obj)->%(c_name)s, "%(name)s", &err);
-''',
-                     c_type=memb.type.c_name(), c_name=c_name(memb.name),
-                     name=memb.name)
-
-        if memb.optional:
-            pop_indent()
-            ret += mcgen('''
-}
-''')
-        ret += mcgen('''
-if (err) {
-    goto out;
-}
-''')
+    ret += gen_visit_fields(members, '(*obj)->', False, 'err')

     pop_indent()
     if re.search('^ *goto out;', ret, re.MULTILINE):
diff --git a/scripts/qapi.py b/scripts/qapi.py
index 6f4e471..7275daa 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -1531,6 +1531,59 @@ def gen_params(arg_type, extra):
         ret += sep + extra
     return ret

+
+def gen_err_check(err):
+    if not err:
+        return ''
+    return mcgen('''
+if (%(err)s) {
+    goto out;
+}
+''',
+                 err=err)
+
+
+def gen_visit_fields(members, prefix, need_cast, errarg):
+    ret = ''
+    if errarg:
+        errparg = '&' + errarg
+    else:
+        errparg = 'NULL'
+    for memb in members:
+        if memb.optional:
+            ret += mcgen('''
+visit_optional(v, &%(prefix)shas_%(c_name)s, "%(name)s", %(errp)s);
+''',
+                         prefix=prefix, c_name=c_name(memb.name),
+                         name=memb.name, errp=errparg)
+            ret += gen_err_check(errarg)
+            ret += mcgen('''
+if (%(prefix)shas_%(c_name)s) {
+''',
+                         prefix=prefix, c_name=c_name(memb.name))
+            push_indent()
+
+        # Ugly: sometimes we need to cast away const
+        if need_cast and memb.type.name == 'str':
+            cast = '(char **)'
+        else:
+            cast = ''
+
+        ret += mcgen('''
+visit_type_%(c_type)s(v, %(cast)s&%(prefix)s%(c_name)s, "%(name)s", %(errp)s);
+''',
+                     c_type=memb.type.c_name(), prefix=prefix, cast=cast,
+                     c_name=c_name(memb.name), name=memb.name,
+                     errp=errparg)
+        ret += gen_err_check(errarg)
+
+        if memb.optional:
+            pop_indent()
+            ret += mcgen('''
+}
+''')
+    return ret
+
 #
 # Common command line parsing
 #
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 11/46] qapi: Don't use info as witness of implicit object type
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (9 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 10/46] qapi: Merge generation of per-member visits Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-28 12:43   ` Markus Armbruster
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 12/46] qapi: Track location that created an implicit type Eric Blake
                   ` (35 subsequent siblings)
  46 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

A future patch will enable error reporting from the various
check() methods.  But to report an error on an implicit type,
we'll need to associate a location with the type (the same
location as the top-level entity that is causing the creation
of the implicit type), and once we do that, keying off of
whether foo.info exists is no longer a viable way to determine
if foo is an implicit type.

Rename the info member to _info (so that sub-classes can still
use it, but external code should not), add an is_implicit()
method to QAPISchemaObjectType, and adjust the visitor to pass
another parameter about whether the type is implicit.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi-types.py          |  4 ++--
 scripts/qapi-visit.py          |  4 ++--
 scripts/qapi.py                | 33 +++++++++++++++++++--------------
 tests/qapi-schema/test-qapi.py |  2 +-
 4 files changed, 24 insertions(+), 19 deletions(-)

diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
index b292682..aa25e03 100644
--- a/scripts/qapi-types.py
+++ b/scripts/qapi-types.py
@@ -253,8 +253,8 @@ class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
             self.decl += gen_array(name, element_type)
             self._gen_type_cleanup(name)

-    def visit_object_type(self, name, info, base, members, variants):
-        if info:
+    def visit_object_type(self, name, info, base, members, variants, implicit):
+        if not implicit:
             self._fwdecl += gen_fwd_object_or_array(name)
             if variants:
                 assert not members      # not implemented
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 1f287ba..62a47fa 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -348,8 +348,8 @@ class QAPISchemaGenVisitVisitor(QAPISchemaVisitor):
             self.decl += decl
             self.defn += defn

-    def visit_object_type(self, name, info, base, members, variants):
-        if info:
+    def visit_object_type(self, name, info, base, members, variants, implicit):
+        if not implicit:
             self.decl += gen_visit_decl(name)
             if variants:
                 assert not members      # not implemented
diff --git a/scripts/qapi.py b/scripts/qapi.py
index 7275daa..1dc7641 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -786,7 +786,7 @@ class QAPISchemaEntity(object):
     def __init__(self, name, info):
         assert isinstance(name, str)
         self.name = name
-        self.info = info
+        self._info = info

     def c_name(self):
         return c_name(self.name)
@@ -814,7 +814,7 @@ class QAPISchemaVisitor(object):
     def visit_array_type(self, name, info, element_type):
         pass

-    def visit_object_type(self, name, info, base, members, variants):
+    def visit_object_type(self, name, info, base, members, variants, implicit):
         pass

     def visit_object_type_flat(self, name, info, members, variants):
@@ -877,7 +877,7 @@ class QAPISchemaBuiltinType(QAPISchemaType):
         return self._json_type_name

     def visit(self, visitor):
-        visitor.visit_builtin_type(self.name, self.info, self.json_type())
+        visitor.visit_builtin_type(self.name, self._info, self.json_type())


 class QAPISchemaEnumType(QAPISchemaType):
@@ -903,7 +903,7 @@ class QAPISchemaEnumType(QAPISchemaType):
         return 'string'

     def visit(self, visitor):
-        visitor.visit_enum_type(self.name, self.info,
+        visitor.visit_enum_type(self.name, self._info,
                                 self.values, self.prefix)


@@ -922,7 +922,7 @@ class QAPISchemaArrayType(QAPISchemaType):
         return 'array'

     def visit(self, visitor):
-        visitor.visit_array_type(self.name, self.info, self.element_type)
+        visitor.visit_array_type(self.name, self._info, self.element_type)


 class QAPISchemaObjectType(QAPISchemaType):
@@ -961,21 +961,25 @@ class QAPISchemaObjectType(QAPISchemaType):
             self.variants.check(schema, members, seen)
         self.members = members

+    def is_implicit(self):
+        return self.name[0] == ':'
+
     def c_name(self):
-        assert self.info
+        assert not self.is_implicit()
         return QAPISchemaType.c_name(self)

     def c_type(self, is_param=False):
-        assert self.info
+        assert not self.is_implicit()
         return QAPISchemaType.c_type(self)

     def json_type(self):
         return 'object'

     def visit(self, visitor):
-        visitor.visit_object_type(self.name, self.info,
-                                  self.base, self.local_members, self.variants)
-        visitor.visit_object_type_flat(self.name, self.info,
+        visitor.visit_object_type(self.name, self._info,
+                                  self.base, self.local_members, self.variants,
+                                  self.is_implicit())
+        visitor.visit_object_type_flat(self.name, self._info,
                                        self.members, self.variants)


@@ -1034,7 +1038,8 @@ class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
     # This function exists to support ugly simple union special cases
     # TODO get rid of them, and drop the function
     def simple_union_type(self):
-        if isinstance(self.type, QAPISchemaObjectType) and not self.type.info:
+        if isinstance(self.type, QAPISchemaObjectType) and \
+           self.type.is_implicit():
             assert len(self.type.members) == 1
             assert not self.type.variants
             return self.type.members[0].type
@@ -1055,7 +1060,7 @@ class QAPISchemaAlternateType(QAPISchemaType):
         return 'value'

     def visit(self, visitor):
-        visitor.visit_alternate_type(self.name, self.info, self.variants)
+        visitor.visit_alternate_type(self.name, self._info, self.variants)


 class QAPISchemaCommand(QAPISchemaEntity):
@@ -1080,7 +1085,7 @@ class QAPISchemaCommand(QAPISchemaEntity):
             assert isinstance(self.ret_type, QAPISchemaType)

     def visit(self, visitor):
-        visitor.visit_command(self.name, self.info,
+        visitor.visit_command(self.name, self._info,
                               self.arg_type, self.ret_type,
                               self.gen, self.success_response)

@@ -1099,7 +1104,7 @@ class QAPISchemaEvent(QAPISchemaEntity):
             assert not self.arg_type.variants   # not implemented

     def visit(self, visitor):
-        visitor.visit_event(self.name, self.info, self.arg_type)
+        visitor.visit_event(self.name, self._info, self.arg_type)


 class QAPISchema(object):
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index 649677e..f2cce64 100644
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -22,7 +22,7 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
         if prefix:
             print '    prefix %s' % prefix

-    def visit_object_type(self, name, info, base, members, variants):
+    def visit_object_type(self, name, info, base, members, variants, implicit):
         print 'object %s' % name
         if base:
             print '    base %s' % base.name
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 12/46] qapi: Track location that created an implicit type
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (10 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 11/46] qapi: Don't use info as witness of implicit object type Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-28 12:56   ` Markus Armbruster
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 13/46] qapi: Track owner of each object member Eric Blake
                   ` (34 subsequent siblings)
  46 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

A future patch will enable error detection in the various
QapiSchema check() methods.  But since all errors have to
have an associated 'info' location, we need a location to
be associated with all implicit types.  Easiest is to reuse
the location of the enclosing entity that includes the
dictionary defining the implicit type.

While at it, we were always passing None as the location of
array types, making that parameter useless; sharing the
location (if any) of the underlying element type makes sense.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi.py | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 1dc7641..e982970 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -908,8 +908,8 @@ class QAPISchemaEnumType(QAPISchemaType):


 class QAPISchemaArrayType(QAPISchemaType):
-    def __init__(self, name, info, element_type):
-        QAPISchemaType.__init__(self, name, info)
+    def __init__(self, name, element_type):
+        QAPISchemaType.__init__(self, name, None)
         assert isinstance(element_type, str)
         self._element_type_name = element_type
         self.element_type = None
@@ -917,6 +917,7 @@ class QAPISchemaArrayType(QAPISchemaType):
     def check(self, schema):
         self.element_type = schema.lookup_type(self._element_type_name)
         assert self.element_type
+        self._info = self.element_type._info

     def json_type(self):
         return 'array'
@@ -928,6 +929,7 @@ class QAPISchemaArrayType(QAPISchemaType):
 class QAPISchemaObjectType(QAPISchemaType):
     def __init__(self, name, info, base, local_members, variants):
         QAPISchemaType.__init__(self, name, info)
+        assert info or name == ':empty'
         assert base is None or isinstance(base, str)
         for m in local_members:
             assert isinstance(m, QAPISchemaObjectTypeMember)
@@ -1165,15 +1167,15 @@ class QAPISchema(object):
     def _make_array_type(self, element_type):
         name = element_type + 'List'
         if not self.lookup_type(name):
-            self._def_entity(QAPISchemaArrayType(name, None, element_type))
+            self._def_entity(QAPISchemaArrayType(name, element_type))
         return name

-    def _make_implicit_object_type(self, name, role, members):
+    def _make_implicit_object_type(self, name, info, role, members):
         if not members:
             return None
         name = ':obj-%s-%s' % (name, role)
         if not self.lookup_entity(name, QAPISchemaObjectType):
-            self._def_entity(QAPISchemaObjectType(name, None, None,
+            self._def_entity(QAPISchemaObjectType(name, info, None,
                                                   members, None))
         return name

@@ -1210,11 +1212,11 @@ class QAPISchema(object):
     def _make_variant(self, case, typ):
         return QAPISchemaObjectTypeVariant(case, typ)

-    def _make_simple_variant(self, case, typ):
+    def _make_simple_variant(self, info, case, typ):
         if isinstance(typ, list):
             assert len(typ) == 1
             typ = self._make_array_type(typ[0])
-        typ = self._make_implicit_object_type(typ, 'wrapper',
+        typ = self._make_implicit_object_type(typ, info, 'wrapper',
                                               [self._make_member('data', typ)])
         return QAPISchemaObjectTypeVariant(case, typ)

@@ -1232,7 +1234,7 @@ class QAPISchema(object):
             variants = [self._make_variant(key, value)
                         for (key, value) in data.iteritems()]
         else:
-            variants = [self._make_simple_variant(key, value)
+            variants = [self._make_simple_variant(info, key, value)
                         for (key, value) in data.iteritems()]
             tag_enum = self._make_tag_enum(name, variants)
         self._def_entity(
@@ -1263,7 +1265,7 @@ class QAPISchema(object):
         gen = expr.get('gen', True)
         success_response = expr.get('success-response', True)
         if isinstance(data, OrderedDict):
-            data = self._make_implicit_object_type(name, 'arg',
+            data = self._make_implicit_object_type(name, info, 'arg',
                                                    self._make_members(data))
         if isinstance(rets, list):
             assert len(rets) == 1
@@ -1275,7 +1277,7 @@ class QAPISchema(object):
         name = expr['event']
         data = expr.get('data')
         if isinstance(data, OrderedDict):
-            data = self._make_implicit_object_type(name, 'arg',
+            data = self._make_implicit_object_type(name, info, 'arg',
                                                    self._make_members(data))
         self._def_entity(QAPISchemaEvent(name, info, data))

-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 13/46] qapi: Track owner of each object member
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (11 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 12/46] qapi: Track location that created an implicit type Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-30 16:06   ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 14/46] qapi: Detect collisions in C member names Eric Blake
                   ` (33 subsequent siblings)
  46 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods.  But to
report an error message about an incorrect semantic use of a
member of an object type, we need to know which type, command,
or event owns the member.  Rather than making all the check()
methods have to pass around additional information, it is easier
to have each member track who owns it in the first place.

The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information.  For example, given the qapi:
 { 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
 arg_type.members[0].describe()
will see "'string' (member of foo arguments)".

I intentionally chose the terminology "member" for struct
fields, and "branch" for variants; this is because we have two
different types of collisions that future patches will detect:
duplicated keys in the QMP wire format (members common to a base
type and its descendent), and collisions in the generated C
struct (a union branch colliding with a common member, even
though the QMP wire code never passes the branch name as a key).

Where implicit types are involved, the code intentionally tries
to pick the name of the owner of that implicit type, rather than
the type name itself (a user reading the error message should be
able to grep for the problem in their original file, but will not
be able to locate a generated implicit name).  For simple unions,
I chose not to pass the union name to the variants constructor;
but this should be okay because in practice nothing should ever
conflict with the implicit 'type' of a simple union.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi.py | 54 ++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index e982970..6bc13f1 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -986,14 +986,16 @@ class QAPISchemaObjectType(QAPISchemaType):


 class QAPISchemaObjectTypeMember(object):
-    def __init__(self, name, typ, optional):
+    def __init__(self, name, typ, optional, owner):
         assert isinstance(name, str)
         assert isinstance(typ, str)
         assert isinstance(optional, bool)
+        assert isinstance(owner, str) and owner[0] != ':'
         self.name = name
         self._type_name = typ
         self.type = None
         self.optional = optional
+        self._owner = owner

     def check(self, schema, all_members, seen):
         assert self.name not in seen
@@ -1002,6 +1004,9 @@ class QAPISchemaObjectTypeMember(object):
         all_members.append(self)
         seen[self.name] = self

+    def describe(self):
+        return "'%s' (member of %s)" % (self.name, self._owner)
+

 class QAPISchemaObjectTypeVariants(object):
     def __init__(self, tag_name, tag_enum, variants):
@@ -1015,7 +1020,8 @@ class QAPISchemaObjectTypeVariants(object):
             self.tag_member = None
         else:
             self.tag_member = QAPISchemaObjectTypeMember('type', tag_enum,
-                                                         False)
+                                                         False,
+                                                         '(implicit)')
         self.variants = variants

     def check(self, schema, members, seen):
@@ -1030,13 +1036,16 @@ class QAPISchemaObjectTypeVariants(object):


 class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
-    def __init__(self, name, typ):
-        QAPISchemaObjectTypeMember.__init__(self, name, typ, False)
+    def __init__(self, name, typ, owner):
+        QAPISchemaObjectTypeMember.__init__(self, name, typ, False, owner)

     def check(self, schema, tag_type, seen):
         QAPISchemaObjectTypeMember.check(self, schema, [], seen)
         assert self.name in tag_type.values

+    def describe(self):
+        return "'%s' (branch of %s)'" % (self.name, self._owner)
+
     # This function exists to support ugly simple union special cases
     # TODO get rid of them, and drop the function
     def simple_union_type(self):
@@ -1186,7 +1195,7 @@ class QAPISchema(object):
         self._def_entity(QAPISchemaEnumType(name, info, data, prefix))
         self._make_array_type(name)     # TODO really needed?

-    def _make_member(self, name, typ):
+    def _make_member(self, name, typ, owner):
         optional = False
         if name.startswith('*'):
             name = name[1:]
@@ -1194,10 +1203,10 @@ class QAPISchema(object):
         if isinstance(typ, list):
             assert len(typ) == 1
             typ = self._make_array_type(typ[0])
-        return QAPISchemaObjectTypeMember(name, typ, optional)
+        return QAPISchemaObjectTypeMember(name, typ, optional, owner)

-    def _make_members(self, data):
-        return [self._make_member(key, value)
+    def _make_members(self, data, owner):
+        return [self._make_member(key, value, owner)
                 for (key, value) in data.iteritems()]

     def _def_struct_type(self, expr, info):
@@ -1205,20 +1214,21 @@ class QAPISchema(object):
         base = expr.get('base')
         data = expr['data']
         self._def_entity(QAPISchemaObjectType(name, info, base,
-                                              self._make_members(data),
+                                              self._make_members(data, name),
                                               None))
         self._make_array_type(name)     # TODO really needed?

-    def _make_variant(self, case, typ):
-        return QAPISchemaObjectTypeVariant(case, typ)
+    def _make_variant(self, case, typ, owner):
+        return QAPISchemaObjectTypeVariant(case, typ, owner)

-    def _make_simple_variant(self, info, case, typ):
+    def _make_simple_variant(self, info, case, typ, owner):
         if isinstance(typ, list):
             assert len(typ) == 1
             typ = self._make_array_type(typ[0])
         typ = self._make_implicit_object_type(typ, info, 'wrapper',
-                                              [self._make_member('data', typ)])
-        return QAPISchemaObjectTypeVariant(case, typ)
+                                              [self._make_member('data', typ,
+                                                                 owner)])
+        return QAPISchemaObjectTypeVariant(case, typ, owner)

     def _make_tag_enum(self, type_name, variants):
         return self._make_implicit_enum_type(type_name,
@@ -1231,15 +1241,15 @@ class QAPISchema(object):
         tag_name = expr.get('discriminator')
         tag_enum = None
         if tag_name:
-            variants = [self._make_variant(key, value)
+            variants = [self._make_variant(key, value, name)
                         for (key, value) in data.iteritems()]
         else:
-            variants = [self._make_simple_variant(info, key, value)
+            variants = [self._make_simple_variant(info, key, value, name)
                         for (key, value) in data.iteritems()]
             tag_enum = self._make_tag_enum(name, variants)
         self._def_entity(
             QAPISchemaObjectType(name, info, base,
-                                 self._make_members(OrderedDict()),
+                                 self._make_members(OrderedDict(), name),
                                  QAPISchemaObjectTypeVariants(tag_name,
                                                               tag_enum,
                                                               variants)))
@@ -1248,7 +1258,7 @@ class QAPISchema(object):
     def _def_alternate_type(self, expr, info):
         name = expr['alternate']
         data = expr['data']
-        variants = [self._make_variant(key, value)
+        variants = [self._make_variant(key, value, name)
                     for (key, value) in data.iteritems()]
         tag_enum = self._make_tag_enum(name, variants)
         self._def_entity(
@@ -1265,8 +1275,10 @@ class QAPISchema(object):
         gen = expr.get('gen', True)
         success_response = expr.get('success-response', True)
         if isinstance(data, OrderedDict):
+            owner = name + ' arguments'
             data = self._make_implicit_object_type(name, info, 'arg',
-                                                   self._make_members(data))
+                                                   self._make_members(data,
+                                                                      owner))
         if isinstance(rets, list):
             assert len(rets) == 1
             rets = self._make_array_type(rets[0])
@@ -1277,8 +1289,10 @@ class QAPISchema(object):
         name = expr['event']
         data = expr.get('data')
         if isinstance(data, OrderedDict):
+            owner = name + ' data'
             data = self._make_implicit_object_type(name, info, 'arg',
-                                                   self._make_members(data))
+                                                   self._make_members(data,
+                                                                      owner))
         self._def_entity(QAPISchemaEvent(name, info, data))

     def _def_exprs(self):
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 14/46] qapi: Detect collisions in C member names
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (12 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 13/46] qapi: Track owner of each object member Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 15/46] qapi: Defer duplicate member checks to schema check() Eric Blake
                   ` (32 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Detect attempts to declare two object members that would result
in the same C member name, by keying the 'seen' dictionary off
of the C name rather than the qapi name.  As this is the first
error raised within the QapiSchema check() methods, we also have
to pass 'info' around through the call stack, and fix the overall
'try' to check for errors detected during the check() phase.

The resulting error messages demonstrate the utility of the
.describe() method added previously.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi.py                                 | 44 +++++++++++++++----------
 tests/qapi-schema/args-name-clash.err           |  1 +
 tests/qapi-schema/args-name-clash.exit          |  2 +-
 tests/qapi-schema/args-name-clash.json          |  2 +-
 tests/qapi-schema/args-name-clash.out           |  6 ----
 tests/qapi-schema/flat-union-branch-clash2.err  |  1 +
 tests/qapi-schema/flat-union-branch-clash2.exit |  2 +-
 tests/qapi-schema/flat-union-branch-clash2.json |  2 +-
 tests/qapi-schema/flat-union-branch-clash2.out  | 14 --------
 9 files changed, 33 insertions(+), 41 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 6bc13f1..0a0ac90 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -103,6 +103,7 @@ class QAPISchemaError(Exception):
 class QAPIExprError(Exception):
     def __init__(self, expr_info, msg):
         Exception.__init__(self)
+        assert expr_info
         self.info = expr_info
         self.msg = msg

@@ -956,11 +957,12 @@ class QAPISchemaObjectType(QAPISchemaType):
             members = []
         seen = {}
         for m in members:
-            seen[m.name] = m
+            assert m.c_name() not in seen
+            seen[m.c_name()] = m
         for m in self.local_members:
-            m.check(schema, members, seen)
+            m.check(schema, self._info, members, seen)
         if self.variants:
-            self.variants.check(schema, members, seen)
+            self.variants.check(schema, self._info, members, seen)
         self.members = members

     def is_implicit(self):
@@ -997,12 +999,19 @@ class QAPISchemaObjectTypeMember(object):
         self.optional = optional
         self._owner = owner

-    def check(self, schema, all_members, seen):
-        assert self.name not in seen
+    def check(self, schema, info, all_members, seen):
+        if self.c_name() in seen:
+            raise QAPIExprError(info,
+                                "%s collides with %s"
+                                % (self.describe(),
+                                   seen[self.c_name()].describe()))
         self.type = schema.lookup_type(self._type_name)
         assert self.type
         all_members.append(self)
-        seen[self.name] = self
+        seen[self.c_name()] = self
+
+    def c_name(self):
+        return c_name(self.name)

     def describe(self):
         return "'%s' (member of %s)" % (self.name, self._owner)
@@ -1024,23 +1033,24 @@ class QAPISchemaObjectTypeVariants(object):
                                                          '(implicit)')
         self.variants = variants

-    def check(self, schema, members, seen):
+    def check(self, schema, info, members, seen):
         if self.tag_name:
-            self.tag_member = seen[self.tag_name]
+            self.tag_member = seen[c_name(self.tag_name)]
+            assert self.tag_name == self.tag_member.name
         else:
-            self.tag_member.check(schema, members, seen)
+            self.tag_member.check(schema, info, members, seen)
         assert isinstance(self.tag_member.type, QAPISchemaEnumType)
         for v in self.variants:
             vseen = dict(seen)
-            v.check(schema, self.tag_member.type, vseen)
+            v.check(schema, info, self.tag_member.type, vseen)


 class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
     def __init__(self, name, typ, owner):
         QAPISchemaObjectTypeMember.__init__(self, name, typ, False, owner)

-    def check(self, schema, tag_type, seen):
-        QAPISchemaObjectTypeMember.check(self, schema, [], seen)
+    def check(self, schema, info, tag_type, seen):
+        QAPISchemaObjectTypeMember.check(self, schema, info, [], seen)
         assert self.name in tag_type.values

     def describe(self):
@@ -1065,7 +1075,7 @@ class QAPISchemaAlternateType(QAPISchemaType):
         self.variants = variants

     def check(self, schema):
-        self.variants.check(schema, [], {})
+        self.variants.check(schema, self._info, [], {})

     def json_type(self):
         return 'value'
@@ -1122,13 +1132,13 @@ class QAPISchema(object):
     def __init__(self, fname):
         try:
             self.exprs = check_exprs(QAPISchemaParser(open(fname, "r")).exprs)
+            self._entity_dict = {}
+            self._def_predefineds()
+            self._def_exprs()
+            self.check()
         except (QAPISchemaError, QAPIExprError), err:
             print >>sys.stderr, err
             exit(1)
-        self._entity_dict = {}
-        self._def_predefineds()
-        self._def_exprs()
-        self.check()

     def _def_entity(self, ent):
         assert ent.name not in self._entity_dict
diff --git a/tests/qapi-schema/args-name-clash.err b/tests/qapi-schema/args-name-clash.err
index e69de29..743afdb 100644
--- a/tests/qapi-schema/args-name-clash.err
+++ b/tests/qapi-schema/args-name-clash.err
@@ -0,0 +1 @@
+tests/qapi-schema/args-name-clash.json:2: 'a_b' (member of oops arguments) collides with 'a-b' (member of oops arguments)
diff --git a/tests/qapi-schema/args-name-clash.exit b/tests/qapi-schema/args-name-clash.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/args-name-clash.exit
+++ b/tests/qapi-schema/args-name-clash.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/args-name-clash.json b/tests/qapi-schema/args-name-clash.json
index 19bf792..602db6a 100644
--- a/tests/qapi-schema/args-name-clash.json
+++ b/tests/qapi-schema/args-name-clash.json
@@ -1,2 +1,2 @@
-# FIXME - we should reject data with members that clash when mapped to C names
+# we reject data with members that clash when mapped to C names
 { 'command': 'oops', 'data': { 'a-b': 'str', 'a_b': 'str' } }
diff --git a/tests/qapi-schema/args-name-clash.out b/tests/qapi-schema/args-name-clash.out
index 9b2f6e4..e69de29 100644
--- a/tests/qapi-schema/args-name-clash.out
+++ b/tests/qapi-schema/args-name-clash.out
@@ -1,6 +0,0 @@
-object :empty
-object :obj-oops-arg
-    member a-b: str optional=False
-    member a_b: str optional=False
-command oops :obj-oops-arg -> None
-   gen=True success_response=True
diff --git a/tests/qapi-schema/flat-union-branch-clash2.err b/tests/qapi-schema/flat-union-branch-clash2.err
index e69de29..99eacd2 100644
--- a/tests/qapi-schema/flat-union-branch-clash2.err
+++ b/tests/qapi-schema/flat-union-branch-clash2.err
@@ -0,0 +1 @@
+tests/qapi-schema/flat-union-branch-clash2.json:10: 'c-d' (branch of TestUnion)' collides with 'c_d' (member of Base)
diff --git a/tests/qapi-schema/flat-union-branch-clash2.exit b/tests/qapi-schema/flat-union-branch-clash2.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/flat-union-branch-clash2.exit
+++ b/tests/qapi-schema/flat-union-branch-clash2.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/flat-union-branch-clash2.json b/tests/qapi-schema/flat-union-branch-clash2.json
index b3eefb3..b0dd85e 100644
--- a/tests/qapi-schema/flat-union-branch-clash2.json
+++ b/tests/qapi-schema/flat-union-branch-clash2.json
@@ -1,4 +1,4 @@
-# FIXME: we should check for no duplicate C names between branches and base
+# we check for no duplicate C names between branches and base
 { 'enum': 'TestEnum',
   'data': [ 'base', 'c-d' ] }
 { 'struct': 'Base',
diff --git a/tests/qapi-schema/flat-union-branch-clash2.out b/tests/qapi-schema/flat-union-branch-clash2.out
index 8e0da73..e69de29 100644
--- a/tests/qapi-schema/flat-union-branch-clash2.out
+++ b/tests/qapi-schema/flat-union-branch-clash2.out
@@ -1,14 +0,0 @@
-object :empty
-object Base
-    member enum1: TestEnum optional=False
-    member c_d: str optional=True
-object Branch1
-    member string: str optional=False
-object Branch2
-    member value: int optional=False
-enum TestEnum ['base', 'c-d']
-object TestUnion
-    base Base
-    tag enum1
-    case base: Branch1
-    case c-d: Branch2
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 15/46] qapi: Defer duplicate member checks to schema check()
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (13 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 14/46] qapi: Detect collisions in C member names Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 16/46] qapi: Detect base class loops Eric Blake
                   ` (31 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

With the previous commit, we have two different locations for
detecting member name clashes - one at parse time, and another
at schema check() time.  Consolidate the checks into a single
place, which is also in line with our TODO to eventually defer
all of the parse time semantic checking into the newer schema
code.  The check_member_clash() function is no longer needed.

The wording of the error messages has changed, but should still
convey enough information to not be termed a regression.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi.py                               | 45 ++++++++++-----------------
 tests/qapi-schema/flat-union-branch-clash.err |  2 +-
 tests/qapi-schema/flat-union-cycle.err        |  2 +-
 tests/qapi-schema/struct-base-clash-deep.err  |  2 +-
 tests/qapi-schema/struct-base-clash.err       |  2 +-
 5 files changed, 21 insertions(+), 32 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 0a0ac90..f5f1c60 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -499,21 +499,6 @@ def check_type(expr_info, source, value, allow_array=False,
                                 'enum'])


-def check_member_clash(expr_info, base_name, data, source=""):
-    base = find_struct(base_name)
-    assert base
-    base_members = base['data']
-    for key in data.keys():
-        if key.startswith('*'):
-            key = key[1:]
-        if key in base_members or "*" + key in base_members:
-            raise QAPIExprError(expr_info,
-                                "Member name '%s'%s clashes with base '%s'"
-                                % (key, source, base_name))
-    if base.get('base'):
-        check_member_clash(expr_info, base['base'], data, source)
-
-
 def check_command(expr, expr_info):
     name = expr['command']

@@ -592,15 +577,9 @@ def check_union(expr, expr_info):
     for (key, value) in members.items():
         check_name(expr_info, "Member of union '%s'" % name, key)

-        # Each value must name a known type; furthermore, in flat unions,
-        # branches must be a struct with no overlapping member names
+        # Each value must name a known type
         check_type(expr_info, "Member '%s' of union '%s'" % (key, name),
                    value, allow_array=not base, allow_metas=allow_metas)
-        if base:
-            branch_struct = find_struct(value)
-            assert branch_struct
-            check_member_clash(expr_info, base, branch_struct['data'],
-                               " of branch '%s'" % key)

         # If the discriminator names an enum type, then all members
         # of 'data' must also be members of the enum type.
@@ -684,8 +663,6 @@ def check_struct(expr, expr_info):
                allow_dict=True, allow_optional=True)
     check_type(expr_info, "'base' for struct '%s'" % name, expr.get('base'),
                allow_metas=['struct'])
-    if expr.get('base'):
-        check_member_clash(expr_info, expr['base'], expr['data'])


 def check_keys(expr_elem, meta, required, optional=[]):
@@ -999,7 +976,7 @@ class QAPISchemaObjectTypeMember(object):
         self.optional = optional
         self._owner = owner

-    def check(self, schema, info, all_members, seen):
+    def check(self, schema, info, all_members, seen, flat=False):
         if self.c_name() in seen:
             raise QAPIExprError(info,
                                 "%s collides with %s"
@@ -1007,6 +984,17 @@ class QAPISchemaObjectTypeMember(object):
                                    seen[self.c_name()].describe()))
         self.type = schema.lookup_type(self._type_name)
         assert self.type
+        if flat:
+            self.type.check(schema)
+            assert isinstance(self.type.members, list)
+            assert not self.type.variants    # not implemented
+            for m in self.type.members:
+                if m.c_name() in seen:
+                    raise QAPIExprError(info,
+                                        "Member '%s' of branch '%s' collides "
+                                        "with %s"
+                                        % (m.name, self.name,
+                                           seen[m.c_name()].describe()))
         all_members.append(self)
         seen[self.c_name()] = self

@@ -1042,15 +1030,16 @@ class QAPISchemaObjectTypeVariants(object):
         assert isinstance(self.tag_member.type, QAPISchemaEnumType)
         for v in self.variants:
             vseen = dict(seen)
-            v.check(schema, info, self.tag_member.type, vseen)
+            v.check(schema, info, self.tag_member.type, vseen,
+                    self.tag_name is not None)


 class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
     def __init__(self, name, typ, owner):
         QAPISchemaObjectTypeMember.__init__(self, name, typ, False, owner)

-    def check(self, schema, info, tag_type, seen):
-        QAPISchemaObjectTypeMember.check(self, schema, info, [], seen)
+    def check(self, schema, info, tag_type, seen, flat):
+        QAPISchemaObjectTypeMember.check(self, schema, info, [], seen, flat)
         assert self.name in tag_type.values

     def describe(self):
diff --git a/tests/qapi-schema/flat-union-branch-clash.err b/tests/qapi-schema/flat-union-branch-clash.err
index f112766..e220ea4 100644
--- a/tests/qapi-schema/flat-union-branch-clash.err
+++ b/tests/qapi-schema/flat-union-branch-clash.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-branch-clash.json:10: Member name 'name' of branch 'value1' clashes with base 'Base'
+tests/qapi-schema/flat-union-branch-clash.json:10: Member 'name' of branch 'value1' collides with 'name' (member of Base)
diff --git a/tests/qapi-schema/flat-union-cycle.err b/tests/qapi-schema/flat-union-cycle.err
index 152c6f0..5b431d7 100644
--- a/tests/qapi-schema/flat-union-cycle.err
+++ b/tests/qapi-schema/flat-union-cycle.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-cycle.json:5: Member name 'switch' of branch 'loop' clashes with base 'Base'
+tests/qapi-schema/flat-union-cycle.json:5: Member 'switch' of branch 'loop' collides with 'switch' (member of Base)
diff --git a/tests/qapi-schema/struct-base-clash-deep.err b/tests/qapi-schema/struct-base-clash-deep.err
index e3e9f8d..280fa03 100644
--- a/tests/qapi-schema/struct-base-clash-deep.err
+++ b/tests/qapi-schema/struct-base-clash-deep.err
@@ -1 +1 @@
-tests/qapi-schema/struct-base-clash-deep.json:7: Member name 'name' clashes with base 'Base'
+tests/qapi-schema/struct-base-clash-deep.json:7: 'name' (member of Sub) collides with 'name' (member of Base)
diff --git a/tests/qapi-schema/struct-base-clash.err b/tests/qapi-schema/struct-base-clash.err
index 3ac37fb..2d5ceb0 100644
--- a/tests/qapi-schema/struct-base-clash.err
+++ b/tests/qapi-schema/struct-base-clash.err
@@ -1 +1 @@
-tests/qapi-schema/struct-base-clash.json:4: Member name 'name' clashes with base 'Base'
+tests/qapi-schema/struct-base-clash.json:4: 'name' (member of Sub) collides with 'name' (member of Base)
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 16/46] qapi: Detect base class loops
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (14 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 15/46] qapi: Defer duplicate member checks to schema check() Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 17/46] qapi: Provide nicer array names in introspection Eric Blake
                   ` (30 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

It should be fairly obvious that qapi base classes need to
form an acyclic graph, since QMP cannot specify the same
key more than once, while base classes are included as flat
members alongside other members added by the child.  But prior
to Markus' introspection commits (such as commit 75ebcd7f),
the test in isolation would cause python to exit with a
complaint about unbounded nesting; and after his patches (in
particular ac88219a), it triggers an assertion failure. This
patch includes both the test and the fix, since the .err file
output for an assertion failure is too hard to rebase when
other patches cause line number changes.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
"Commit ac88219a" above assumes the commit ids from the pending
pull-qapi-2015-09-21 tag will be preserved
---
 scripts/qapi.py                   | 6 +++++-
 tests/Makefile                    | 1 +
 tests/qapi-schema/base-cycle.err  | 1 +
 tests/qapi-schema/base-cycle.exit | 1 +
 tests/qapi-schema/base-cycle.json | 3 +++
 tests/qapi-schema/base-cycle.out  | 0
 6 files changed, 11 insertions(+), 1 deletion(-)
 create mode 100644 tests/qapi-schema/base-cycle.err
 create mode 100644 tests/qapi-schema/base-cycle.exit
 create mode 100644 tests/qapi-schema/base-cycle.json
 create mode 100644 tests/qapi-schema/base-cycle.out

diff --git a/scripts/qapi.py b/scripts/qapi.py
index f5f1c60..c6a047b 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -920,7 +920,11 @@ class QAPISchemaObjectType(QAPISchemaType):
         self.members = None

     def check(self, schema):
-        assert self.members is not False        # not running in cycles
+        if self.members is False:               # check for cycles
+            assert self._base_name
+            raise QAPIExprError(self._info,
+                                "Object %s cyclically depends on %s"
+                                % (self.name, self._base_name))
         if self.members:
             return
         self.members = False                    # mark as being checked
diff --git a/tests/Makefile b/tests/Makefile
index df16c9c..20b84b5 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -248,6 +248,7 @@ qapi-schema += bad-ident.json
 qapi-schema += bad-type-bool.json
 qapi-schema += bad-type-dict.json
 qapi-schema += bad-type-int.json
+qapi-schema += base-cycle.json
 qapi-schema += command-int.json
 qapi-schema += comments.json
 qapi-schema += double-data.json
diff --git a/tests/qapi-schema/base-cycle.err b/tests/qapi-schema/base-cycle.err
new file mode 100644
index 0000000..e0221b5
--- /dev/null
+++ b/tests/qapi-schema/base-cycle.err
@@ -0,0 +1 @@
+tests/qapi-schema/base-cycle.json:2: Object Base1 cyclically depends on Base2
diff --git a/tests/qapi-schema/base-cycle.exit b/tests/qapi-schema/base-cycle.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/base-cycle.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/base-cycle.json b/tests/qapi-schema/base-cycle.json
new file mode 100644
index 0000000..2866772
--- /dev/null
+++ b/tests/qapi-schema/base-cycle.json
@@ -0,0 +1,3 @@
+# we reject a loop in base classes
+{ 'struct': 'Base1', 'base': 'Base2', 'data': {} }
+{ 'struct': 'Base2', 'base': 'Base1', 'data': {} }
diff --git a/tests/qapi-schema/base-cycle.out b/tests/qapi-schema/base-cycle.out
new file mode 100644
index 0000000..e69de29
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 17/46] qapi: Provide nicer array names in introspection
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (15 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 16/46] qapi: Detect base class loops Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 18/46] qapi-introspect: Guarantee particular sorting Eric Blake
                   ` (29 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

For the sake of humans reading introspection output, it is nice
to have the name of implicit array types be recognizable as
arrays of the underlying type.  However, while this patch allows
humans to skip from a command with return type "[123]" straight
to the definition of type "123" without having to first inspect
type "[123]", document that this shortcut should not be taken by
client apps.

This makes the resulting introspection string slightly larger by
default, but slightly smaller when -u is in use (as '[FOO]' is
nicer than 'FOOList' for expressing 'array of FOO').

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 docs/qapi-code-gen.txt     | 7 +++++--
 scripts/qapi-introspect.py | 8 +++++---
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index b1c8361..a27f614 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -658,11 +658,14 @@ Example: the SchemaInfo for BlockRef from section Alternate types

 The SchemaInfo for an array type has meta-type "array", and variant
 member "element-type", which names the array's element type.  Array
-types are implicitly defined.
+types are implicitly defined.  For convenience, the array's name may
+resemble the element type; however, clients should examine member
+"element-type" instead of making assumptions based on parsing member
+"name".

 Example: the SchemaInfo for ['str']

-    { "name": "strList", "meta-type": "array",
+    { "name": "[str]", "meta-type": "array",
       "element-type": "str" }

 The SchemaInfo for an enumeration type has meta-type "enum" and
diff --git a/scripts/qapi-introspect.py b/scripts/qapi-introspect.py
index 7d39320..7b098e9 100644
--- a/scripts/qapi-introspect.py
+++ b/scripts/qapi-introspect.py
@@ -104,10 +104,12 @@ const char %(c_name)s[] = %(c_string)s;
         # characters.
         if isinstance(typ, QAPISchemaBuiltinType):
             return typ.name
+        if isinstance(typ, QAPISchemaArrayType):
+            return '[' + self._use_type(typ.element_type) + ']'
         return self._name(typ.name)

     def _gen_json(self, name, mtype, obj):
-        if mtype != 'command' and mtype != 'event' and mtype != 'builtin':
+        if mtype not in ('command', 'event', 'builtin', 'array'):
             name = self._name(name)
         obj['name'] = name
         obj['meta-type'] = mtype
@@ -133,8 +135,8 @@ const char %(c_name)s[] = %(c_string)s;
         self._gen_json(name, 'enum', {'values': values})

     def visit_array_type(self, name, info, element_type):
-        self._gen_json(name, 'array',
-                       {'element-type': self._use_type(element_type)})
+        element = self._use_type(element_type)
+        self._gen_json('[' + element + ']', 'array', {'element-type': element})

     def visit_object_type_flat(self, name, info, members, variants):
         obj = {'members': [self._gen_member(m) for m in members]}
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 18/46] qapi-introspect: Guarantee particular sorting
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (16 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 17/46] qapi: Provide nicer array names in introspection Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 19/46] qapi: Simplify visiting of alternate types Eric Blake
                   ` (28 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Sorting the values of an enum makes it easier to look up whether
a particular value is present by binary rather than linear search
(probably most visible with QKeyCode, which has grown over
several releases).  Additionally, QMP clients need not know which
C value is associated with an enum name, so sorting is an
effective way to hide that non-ABI aspect of qapi.

While we are at it, it is also easy to sort the members and
variants of objects, to allow for a similar binary search, and
equally valid since JSON objects have no specific order in which
keys must appear.  There is no trivial or obvious way to sort
the types of an alternate, so that is left unchanged.

However, the overall SchemaInfo array remains unsorted.  It might
make sense to sort with 'meta-type' as a primary key and 'name'
as a secondary key, but it is not obvious that this will provide
benefits to end-user clients (we allow mutually recursive types,
so there is no posible topological sorting where a single pass
over the array could resolve all types, and while binary searches
could be made possible by sorting, it would be even more efficient
for clients to read the array into a hashtable for O(1) rather
than O(log n) random-access lookups, at which point pre-sorting is
wasted effort).

Document these conventions, so that clients will know what can
and cannot be relied on.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
TODO: should the documentation mention that the sorting is done
in the C locale? Is there anything required to ensure that python
sorts sanely (ie. that the choice of locale while building
doesn't cause inadvertent sorting differences such as turning on
case-insensitivity)?
---
 docs/qapi-code-gen.txt     | 21 +++++++++++++++++----
 qapi/introspect.json       | 22 +++++++++++++++++-----
 scripts/qapi-introspect.py |  9 ++++++---
 3 files changed, 40 insertions(+), 12 deletions(-)

diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index a27f614..dbdb7e3 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -512,6 +512,13 @@ query-qmp-schema.  QGA currently doesn't support introspection.

 query-qmp-schema returns a JSON array of SchemaInfo objects.  These
 objects together describe the wire ABI, as defined in the QAPI schema.
+There is no specified order to the SchemaInfo objects returned; a
+client must search for a particular name and meta-type throughout the
+entire array to learn more about that name.  For now, the name for
+each SchemaInfo is unique thanks to qapi naming conventions; however
+this may be changed in the future (such as allowing a command and an
+event with the same name), so it is important that the client check
+for the desired meta-type.

 However, the SchemaInfo can't reflect all the rules and restrictions
 that apply to QMP.  It's interface introspection (figuring out what's
@@ -592,7 +599,8 @@ any.  Each element is a JSON object with members "name" (the member's
 name), "type" (the name of its type), and optionally "default".  The
 member is optional if "default" is present.  Currently, "default" can
 only have value null.  Other values are reserved for future
-extensions.
+extensions.  The "members" array is sorted by "name", so that clients
+can use a binary search to see if a particular member is supported.

 Example: the SchemaInfo for MyType from section Struct types

@@ -606,7 +614,9 @@ Example: the SchemaInfo for MyType from section Struct types
 "variants" is a JSON array describing the object's variant members.
 Each element is a JSON object with members "case" (the value of type
 tag this element applies to) and "type" (the name of an object type
-that provides the variant members for this type tag value).
+that provides the variant members for this type tag value).  The
+"variants" array is sorted by "case", so it appears in the same
+order as the enum type matching "tag".

 Example: the SchemaInfo for flat union BlockdevOptions from section
 Union types
@@ -647,7 +657,8 @@ Union types
 The SchemaInfo for an alternate type has meta-type "alternate", and
 variant member "members".  "members" is a JSON array.  Each element is
 a JSON object with member "type", which names a type.  Values of the
-alternate type conform to exactly one of its member types.
+alternate type conform to exactly one of its member types.  There is
+no guarantee on the order in which "members" will be listed.

 Example: the SchemaInfo for BlockRef from section Alternate types

@@ -669,7 +680,9 @@ Example: the SchemaInfo for ['str']
       "element-type": "str" }

 The SchemaInfo for an enumeration type has meta-type "enum" and
-variant member "values".
+variant member "values".  The values are listed in sorted order,
+so clients can use a binary search to see if a particular value
+is present.

 Example: the SchemaInfo for MyEnum from section Enumeration types

diff --git a/qapi/introspect.json b/qapi/introspect.json
index cc50dc6..71632af 100644
--- a/qapi/introspect.json
+++ b/qapi/introspect.json
@@ -25,6 +25,11 @@
 # Returns: array of @SchemaInfo, where each element describes an
 # entity in the ABI: command, event, type, ...
 #
+# The order of the various SchemaInfo is unspecified.  For now, the
+# name of each SchemaInfo is unique regardless of meta-type, but to be
+# safe, clients should check that a given name has the expected
+# meta-type.
+#
 # Note: the QAPI schema is also used to help define *internal*
 # interfaces, by defining QAPI types.  These are not part of the QMP
 # wire ABI, and therefore not returned by this command.
@@ -78,7 +83,8 @@
 #        Commands and events have the name defined in the QAPI schema.
 #        Unlike command and event names, type names are not part of
 #        the wire ABI.  Consequently, type names are meaningless
-#        strings here.
+#        strings here.  Although all names are currently unique
+#        regardless of @meta-type, clients should not rely on this.
 #
 # All references to other SchemaInfo are by name.
 #
@@ -130,7 +136,9 @@
 #
 # Additional SchemaInfo members for meta-type 'enum'.
 #
-# @values: the enumeration type's values.
+# @values: the enumeration type's values.  The values are sorted, so
+#          clients can use a binary search to see if a particular value
+#          is present.
 #
 # Values of this type are JSON string on the wire.
 #
@@ -158,14 +166,18 @@
 #
 # Additional SchemaInfo members for meta-type 'object'.
 #
-# @members: the object type's (non-variant) members.
+# @members: the object type's (non-variant) members.  The members are
+#           sorted by name, so clients can use a binary search to see
+#           if a given member is present.
 #
 # @tag: #optional the name of the member serving as type tag.
 #       An element of @members with this name must exist.
 #
 # @variants: #optional variant members, i.e. additional members that
 #            depend on the type tag's value.  Present exactly when
-#            @tag is present.
+#            @tag is present.  The variants are sorted by case, which
+#            means they appear in the same order as the values of the
+#            enum type of the @tag.
 #
 # Values of this type are JSON object on the wire.
 #
@@ -219,7 +231,7 @@
 #
 # Additional SchemaInfo members for meta-type 'alternate'.
 #
-# @members: the alternate type's members.
+# @members: the alternate type's members, in no particular order.
 #           The members' wire encoding is distinct, see
 #           docs/qapi-code-gen.txt section Alternate types.
 #
diff --git a/scripts/qapi-introspect.py b/scripts/qapi-introspect.py
index 7b098e9..2adefdd 100644
--- a/scripts/qapi-introspect.py
+++ b/scripts/qapi-introspect.py
@@ -10,6 +10,7 @@
 # See the COPYING file in the top-level directory.

 from qapi import *
+from operator import attrgetter


 # Caveman's json.dumps() replacement (we're stuck at Python 2.4)
@@ -123,7 +124,8 @@ const char %(c_name)s[] = %(c_string)s;

     def _gen_variants(self, tag_name, variants):
         return {'tag': tag_name,
-                'variants': [self._gen_variant(v) for v in variants]}
+                'variants': [self._gen_variant(v) for v in
+                             sorted(variants, key=attrgetter('name'))]}

     def _gen_variant(self, variant):
         return {'case': variant.name, 'type': self._use_type(variant.type)}
@@ -132,14 +134,15 @@ const char %(c_name)s[] = %(c_string)s;
         self._gen_json(name, 'builtin', {'json-type': json_type})

     def visit_enum_type(self, name, info, values, prefix):
-        self._gen_json(name, 'enum', {'values': values})
+        self._gen_json(name, 'enum', {'values': sorted(values)})

     def visit_array_type(self, name, info, element_type):
         element = self._use_type(element_type)
         self._gen_json('[' + element + ']', 'array', {'element-type': element})

     def visit_object_type_flat(self, name, info, members, variants):
-        obj = {'members': [self._gen_member(m) for m in members]}
+        obj = {'members': [self._gen_member(m) for m in
+                           sorted(members, key=attrgetter('name'))]}
         if variants:
             obj.update(self._gen_variants(variants.tag_member.name,
                                           variants.variants))
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 19/46] qapi: Simplify visiting of alternate types
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (17 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 18/46] qapi-introspect: Guarantee particular sorting Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 20/46] qapi: Fix alternates that accept 'number' but not 'int' Eric Blake
                   ` (27 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Previously, working with alternates required two enums, and
some indirection: for type Foo, we created Foo_qtypes[] which
maps each qtype to a member of FooKind_lookup[], then use
FooKind_lookup[] like we do for other union types.

This has a subtle bug: since the values of FooKind_lookup
start at zero, all entries of Foo_qtypes that were not
explicitly initialized map to the same branch of the union as
the first member of the alternate, rather than triggering a
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
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, it IS observable in one case: the behavior of an
alternate that contains a 'number' member but no 'int' member
differs according to whether the 'number' was first in the
qapi definition, and when the input being parsed is an integer;
this is because the 'number' parser accepts QTYPE_QINT in
addition to the expected QTYPE_QFLOAT.  A later patch will worry
about fixing alternates to parse all inputs that a non-alternate
'number' would accept.

This patch fixes the validation bug by deleting the indirection,
and modifying get_next_type() to directly return a qtype code.
There is no longer a need to generate an implicit FooKind array
associated with the alternate type (since the QMP wire format
never uses the stringized counterparts of the C union member
names).  Next, the generated visitor is fixed to properly detect
unexpected qtypes in the switch statement.  Things got a bit
tricky with validating QAPISchemaObjectTypeVariants, which now
has three different initialization paths; but I didn't think it
was confusing enough to need to create different sub-classes.

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.  If that gets too confusing, we
could reintroduce FooKind, but initialize it 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->kind.  But without a current client, I
didn't see the point of doing it now.

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"}}
Now it fails with:
  {"error": {"class": "GenericError",
    "desc": "Invalid parameter type for 'file', expected: BlockdevRef"}}

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 docs/qapi-code-gen.txt                 |  3 ---
 include/qapi/visitor-impl.h            |  3 ++-
 include/qapi/visitor.h                 |  8 +++++-
 qapi/qapi-visit-core.c                 |  4 +--
 qapi/qmp-input-visitor.c               |  4 +--
 scripts/qapi-types.py                  | 49 ++++++++--------------------------
 scripts/qapi-visit.py                  | 14 +++++-----
 scripts/qapi.py                        | 24 ++++++++++-------
 tests/qapi-schema/alternate-empty.out  |  1 -
 tests/qapi-schema/alternate-good.out   |  1 -
 tests/qapi-schema/qapi-schema-test.out |  8 ------
 tests/test-qmp-input-visitor.c         | 26 +++++++++---------
 tests/test-qmp-output-visitor.c        | 21 +++++++++++----
 13 files changed, 75 insertions(+), 91 deletions(-)

diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index dbdb7e3..842be1b 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -379,9 +379,6 @@ where each branch of the union names a QAPI type.  For example:
    'data': { 'definition': 'BlockdevOptions',
              'reference': 'str' } }

-Just like for a simple union, an implicit C enum 'NameKind' is created
-to enumerate the branches for the alternate 'Name'.
-
 Unlike a union, the discriminator string is never passed on the wire
 for the Client JSON Protocol.  Instead, the value's JSON type serves
 as an implicit discriminator, which in turn means that an alternate
diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
index 8c0ba57..6d95b36 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -32,7 +32,8 @@ struct Visitor

     void (*type_enum)(Visitor *v, int *obj, const char * const strings[],
                       const char *kind, const char *name, Error **errp);
-    void (*get_next_type)(Visitor *v, int *kind, const int *qobjects,
+    /* May be NULL; most useful for input visitors. */
+    void (*get_next_type)(Visitor *v, qtype_code *type,
                           const char *name, Error **errp);

     void (*type_int)(Visitor *v, int64_t *obj, const char *name, Error **errp);
diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index cfc19a6..088d3fc 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -41,7 +41,13 @@ GenericList *visit_next_list(Visitor *v, GenericList **list, Error **errp);
 void visit_end_list(Visitor *v, Error **errp);
 void visit_optional(Visitor *v, bool *present, const char *name,
                     Error **errp);
-void visit_get_next_type(Visitor *v, int *obj, const int *qtypes,
+
+/**
+ * Determine the qtype of the item @name in the current QDict visit.
+ * For input visitors, set *@type to the correct qtype of a qapi
+ * alternate type; for other visitors, leave *@type unchanged.
+ */
+void visit_get_next_type(Visitor *v, qtype_code *type,
                          const char *name, Error **errp);
 void visit_type_enum(Visitor *v, int *obj, const char * const strings[],
                      const char *kind, const char *name, Error **errp);
diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index 59ed506..3f24daa 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -81,11 +81,11 @@ void visit_optional(Visitor *v, bool *present, const char *name,
     }
 }

-void visit_get_next_type(Visitor *v, int *obj, const int *qtypes,
+void visit_get_next_type(Visitor *v, qtype_code *type,
                          const char *name, Error **errp)
 {
     if (v->get_next_type) {
-        v->get_next_type(v, obj, qtypes, name, errp);
+        v->get_next_type(v, type, name, errp);
     }
 }

diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index 5dd9ed5..803ffad 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -208,7 +208,7 @@ static void qmp_input_end_list(Visitor *v, Error **errp)
     qmp_input_pop(qiv, errp);
 }

-static void qmp_input_get_next_type(Visitor *v, int *kind, const int *qobjects,
+static void qmp_input_get_next_type(Visitor *v, qtype_code *type,
                                     const char *name, Error **errp)
 {
     QmpInputVisitor *qiv = to_qiv(v);
@@ -218,7 +218,7 @@ static void qmp_input_get_next_type(Visitor *v, int *kind, const int *qobjects,
         error_setg(errp, QERR_MISSING_PARAMETER, name ? name : "null");
         return;
     }
-    *kind = qobjects[qobject_type(qobj)];
+    *type = qobject_type(qobj);
 }

 static void qmp_input_type_int(Visitor *v, int64_t *obj, const char *name,
diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
index aa25e03..fe998a1 100644
--- a/scripts/qapi-types.py
+++ b/scripts/qapi-types.py
@@ -87,38 +87,6 @@ struct %(c_name)s {
     return ret


-def gen_alternate_qtypes_decl(name):
-    return mcgen('''
-
-extern const int %(c_name)s_qtypes[];
-''',
-                 c_name=c_name(name))
-
-
-def gen_alternate_qtypes(name, variants):
-    ret = mcgen('''
-
-const int %(c_name)s_qtypes[QTYPE_MAX] = {
-''',
-                c_name=c_name(name))
-
-    for var in variants.variants:
-        qtype = var.type.alternate_qtype()
-        assert qtype
-
-        ret += mcgen('''
-    [%(qtype)s] = %(enum_const)s,
-''',
-                     qtype=qtype,
-                     enum_const=c_enum_const(variants.tag_member.type.name,
-                                             var.name))
-
-    ret += mcgen('''
-};
-''')
-    return ret
-
-
 def gen_union(name, base, variants):
     ret = mcgen('''

@@ -134,11 +102,21 @@ struct %(c_name)s {
         ret += mcgen('''
     /* Own members: */
 ''')
+        tag_name = variants.tag_member.name
+    elif not variants.tag_member:
+        ret += mcgen('''
+    qtype_code type;
+''')
+        tag_name = 'type'
     else:
         ret += mcgen('''
     %(c_type)s kind;
 ''',
                      c_type=c_name(variants.tag_member.type.name))
+        # TODO ugly special case for simple union
+        # Use same tag name in C as on the wire to get rid of
+        # it, then: tag_name = variants.tag_member.name
+        tag_name = 'kind'

     # FIXME: What purpose does data serve, besides preventing a union that
     # has a branch named 'data'? We use it in qapi-visit.py to decide
@@ -152,10 +130,7 @@ struct %(c_name)s {
     union { /* union tag is @%(c_name)s */
         void *data;
 ''',
-                 # TODO ugly special case for simple union
-                 # Use same tag name in C as on the wire to get rid of
-                 # it, then: c_name=c_name(variants.tag_member.name)
-                 c_name=c_name(variants.tag_name or 'kind'))
+                 c_name=c_name(tag_name))

     for var in variants.variants:
         # Ugly special case for simple union TODO get rid of it
@@ -265,9 +240,7 @@ class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):

     def visit_alternate_type(self, name, info, variants):
         self._fwdecl += gen_fwd_object_or_array(name)
-        self._fwdefn += gen_alternate_qtypes(name, variants)
         self.decl += gen_union(name, None, variants)
-        self.decl += gen_alternate_qtypes_decl(name)
         self._gen_type_cleanup(name)

 # If you link code generated from multiple schemata, you want only one
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 62a47fa..e58c7f9 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -181,11 +181,11 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
     if (err) {
         goto out;
     }
-    visit_get_next_type(v, (int*) &(*obj)->kind, %(c_name)s_qtypes, name, &err);
+    visit_get_next_type(v, &(*obj)->type, name, &err);
     if (err) {
         goto out_obj;
     }
-    switch ((*obj)->kind) {
+    switch ((*obj)->type) {
 ''',
                 c_name=c_name(name))

@@ -195,21 +195,22 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
         visit_type_%(c_type)s(v, &(*obj)->%(c_name)s, name, &err);
         break;
 ''',
-                     case=c_enum_const(variants.tag_member.type.name,
-                                       var.name),
+                     case=var.type.alternate_qtype(),
                      c_type=var.type.c_name(),
                      c_name=c_name(var.name))

     ret += mcgen('''
     default:
-        abort();
+        error_setg(&err, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
+                   "%(name)s");
     }
 out_obj:
     visit_end_implicit_struct(v, err ? NULL : &err);
 out:
     error_propagate(errp, err);
 }
-''')
+''',
+                 name=name)

     return ret

@@ -415,6 +416,7 @@ fdef.write(mcgen('''

 fdecl.write(mcgen('''
 #include "qapi/visitor.h"
+#include "qapi/qmp/qerror.h"
 #include "%(prefix)sqapi-types.h"

 ''',
diff --git a/scripts/qapi.py b/scripts/qapi.py
index c6a047b..46c55a9 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -1016,26 +1016,30 @@ class QAPISchemaObjectTypeVariants(object):
         for v in variants:
             assert isinstance(v, QAPISchemaObjectTypeVariant)
         self.tag_name = tag_name
-        if tag_name:
+        if tag_name:         # flat union
             assert not tag_enum
             self.tag_member = None
-        else:
+        elif tag_enum:       # simple union
             self.tag_member = QAPISchemaObjectTypeMember('type', tag_enum,
                                                          False,
                                                          '(implicit)')
+        else:                # alternate
+            self.tag_member = None
         self.variants = variants

     def check(self, schema, info, members, seen):
         if self.tag_name:
             self.tag_member = seen[c_name(self.tag_name)]
             assert self.tag_name == self.tag_member.name
-        else:
+        elif self.tag_member:
             self.tag_member.check(schema, info, members, seen)
-        assert isinstance(self.tag_member.type, QAPISchemaEnumType)
+        typ = None
+        if self.tag_member:
+            assert isinstance(self.tag_member.type, QAPISchemaEnumType)
+            typ = self.tag_member.type
         for v in self.variants:
             vseen = dict(seen)
-            v.check(schema, info, self.tag_member.type, vseen,
-                    self.tag_name is not None)
+            v.check(schema, info, typ, vseen, self.tag_member is not None)


 class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
@@ -1044,7 +1048,8 @@ class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):

     def check(self, schema, info, tag_type, seen, flat):
         QAPISchemaObjectTypeMember.check(self, schema, info, [], seen, flat)
-        assert self.name in tag_type.values
+        if tag_type:
+            assert self.name in tag_type.values

     def describe(self):
         return "'%s' (branch of %s)'" % (self.name, self._owner)
@@ -1064,7 +1069,7 @@ class QAPISchemaAlternateType(QAPISchemaType):
     def __init__(self, name, info, variants):
         QAPISchemaType.__init__(self, name, info)
         assert isinstance(variants, QAPISchemaObjectTypeVariants)
-        assert not variants.tag_name
+        assert not variants.tag_member
         self.variants = variants

     def check(self, schema):
@@ -1263,11 +1268,10 @@ class QAPISchema(object):
         data = expr['data']
         variants = [self._make_variant(key, value, name)
                     for (key, value) in data.iteritems()]
-        tag_enum = self._make_tag_enum(name, variants)
         self._def_entity(
             QAPISchemaAlternateType(name, info,
                                     QAPISchemaObjectTypeVariants(None,
-                                                                 tag_enum,
+                                                                 None,
                                                                  variants)))
         self._make_array_type(name)     # TODO really needed?

diff --git a/tests/qapi-schema/alternate-empty.out b/tests/qapi-schema/alternate-empty.out
index 0f153b6..9b010d8 100644
--- a/tests/qapi-schema/alternate-empty.out
+++ b/tests/qapi-schema/alternate-empty.out
@@ -1,4 +1,3 @@
 object :empty
 alternate Alt
     case i: int
-enum AltKind ['i']
diff --git a/tests/qapi-schema/alternate-good.out b/tests/qapi-schema/alternate-good.out
index 65af727..d211aba 100644
--- a/tests/qapi-schema/alternate-good.out
+++ b/tests/qapi-schema/alternate-good.out
@@ -3,7 +3,6 @@ alternate Alt
     case value: int
     case string: Enum
     case struct: Data
-enum AltKind ['value', 'string', 'struct']
 object Data
     member number: int optional=True
     member name: str optional=True
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index de29a45..4405658 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -56,27 +56,21 @@ object :obj-user_def_cmd3-arg
 alternate AltFive
     case i: int
     case n: number
-enum AltFiveKind ['i', 'n']
 alternate AltFour
     case s: str
     case i: int
-enum AltFourKind ['s', 'i']
 alternate AltOne
     case s: str
     case b: bool
-enum AltOneKind ['s', 'b']
 alternate AltSix
     case n: number
     case i: int
-enum AltSixKind ['n', 'i']
 alternate AltThree
     case n: number
     case s: str
-enum AltThreeKind ['n', 's']
 alternate AltTwo
     case s: str
     case n: number
-enum AltTwoKind ['s', 'n']
 event EVENT_A None
 event EVENT_B None
 event EVENT_C :obj-EVENT_C-arg
@@ -100,7 +94,6 @@ alternate UserDefAlternate
     case uda: UserDefA
     case s: str
     case i: int
-enum UserDefAlternateKind ['uda', 's', 'i']
 object UserDefB
     member intb: int optional=False
     member a-b: bool optional=True
@@ -165,7 +158,6 @@ event __ORG.QEMU_X-EVENT __org.qemu_x-Struct
 alternate __org.qemu_x-Alt
     case __org.qemu_x-branch: str
     case b: __org.qemu_x-Base
-enum __org.qemu_x-AltKind ['__org.qemu_x-branch', 'b']
 object __org.qemu_x-Base
     member __org.qemu_x-member1: __org.qemu_x-Enum optional=False
 enum __org.qemu_x-Enum ['__org.qemu_x-value']
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index cd41847..69fa98f 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -373,7 +373,7 @@ static void test_visitor_in_alternate(TestInputVisitorData *data,
     v = visitor_input_test_init(data, "42");

     visit_type_UserDefAlternate(v, &tmp, NULL, &error_abort);
-    g_assert_cmpint(tmp->kind, ==, USER_DEF_ALTERNATE_KIND_I);
+    g_assert_cmpint(tmp->type, ==, QTYPE_QINT);
     g_assert_cmpint(tmp->i, ==, 42);
     qapi_free_UserDefAlternate(tmp);
     tmp = NULL;
@@ -381,7 +381,7 @@ static void test_visitor_in_alternate(TestInputVisitorData *data,
     v = visitor_input_test_init(data, "'string'");

     visit_type_UserDefAlternate(v, &tmp, NULL, &error_abort);
-    g_assert_cmpint(tmp->kind, ==, USER_DEF_ALTERNATE_KIND_S);
+    g_assert_cmpint(tmp->type, ==, QTYPE_QSTRING);
     g_assert_cmpstr(tmp->s, ==, "string");
     qapi_free_UserDefAlternate(tmp);
     tmp = NULL;
@@ -424,31 +424,31 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,
     qapi_free_AltTwo(two);
     one = NULL;

-    /* FIXME: Order of alternate should not affect semantics */
     v = visitor_input_test_init(data, "42");
-    visit_type_AltThree(v, &three, NULL, &error_abort);
-    g_assert_cmpint(three->kind, ==, ALT_THREE_KIND_N);
-    g_assert_cmpfloat(three->n, ==, 42);
+    visit_type_AltThree(v, &three, NULL, &err);
+    g_assert(err);
+    error_free(err);
+    err = NULL;
     qapi_free_AltThree(three);
     one = NULL;

     v = visitor_input_test_init(data, "42");
     visit_type_AltFour(v, &four, NULL, &error_abort);
-    g_assert_cmpint(four->kind, ==, ALT_FOUR_KIND_I);
+    g_assert_cmpint(four->type, ==, QTYPE_QINT);
     g_assert_cmpint(four->i, ==, 42);
     qapi_free_AltFour(four);
     one = NULL;

     v = visitor_input_test_init(data, "42");
     visit_type_AltFive(v, &five, NULL, &error_abort);
-    g_assert_cmpint(five->kind, ==, ALT_FIVE_KIND_I);
+    g_assert_cmpint(five->type, ==, QTYPE_QINT);
     g_assert_cmpint(five->i, ==, 42);
     qapi_free_AltFive(five);
     one = NULL;

     v = visitor_input_test_init(data, "42");
     visit_type_AltSix(v, &six, NULL, &error_abort);
-    g_assert_cmpint(six->kind, ==, ALT_SIX_KIND_I);
+    g_assert_cmpint(six->type, ==, QTYPE_QINT);
     g_assert_cmpint(six->i, ==, 42);
     qapi_free_AltSix(six);
     one = NULL;
@@ -465,14 +465,14 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,

     v = visitor_input_test_init(data, "42.5");
     visit_type_AltTwo(v, &two, NULL, &error_abort);
-    g_assert_cmpint(two->kind, ==, ALT_TWO_KIND_N);
+    g_assert_cmpint(two->type, ==, QTYPE_QFLOAT);
     g_assert_cmpfloat(two->n, ==, 42.5);
     qapi_free_AltTwo(two);
     two = NULL;

     v = visitor_input_test_init(data, "42.5");
     visit_type_AltThree(v, &three, NULL, &error_abort);
-    g_assert_cmpint(three->kind, ==, ALT_THREE_KIND_N);
+    g_assert_cmpint(three->type, ==, QTYPE_QFLOAT);
     g_assert_cmpfloat(three->n, ==, 42.5);
     qapi_free_AltThree(three);
     three = NULL;
@@ -487,14 +487,14 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,

     v = visitor_input_test_init(data, "42.5");
     visit_type_AltFive(v, &five, NULL, &error_abort);
-    g_assert_cmpint(five->kind, ==, ALT_FIVE_KIND_N);
+    g_assert_cmpint(five->type, ==, QTYPE_QFLOAT);
     g_assert_cmpfloat(five->n, ==, 42.5);
     qapi_free_AltFive(five);
     five = NULL;

     v = visitor_input_test_init(data, "42.5");
     visit_type_AltSix(v, &six, NULL, &error_abort);
-    g_assert_cmpint(six->kind, ==, ALT_SIX_KIND_N);
+    g_assert_cmpint(six->type, ==, QTYPE_QFLOAT);
     g_assert_cmpint(six->n, ==, 42.5);
     qapi_free_AltSix(six);
     six = NULL;
diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c
index c84002e..d13e0e0 100644
--- a/tests/test-qmp-output-visitor.c
+++ b/tests/test-qmp-output-visitor.c
@@ -514,20 +514,31 @@ static void test_visitor_out_alternate(TestOutputVisitorData *data,
                                        const void *unused)
 {
     QObject *arg;
-    Error *err = NULL;
+    UserDefAlternate *tmp;

-    UserDefAlternate *tmp = g_malloc0(sizeof(UserDefAlternate));
-    tmp->kind = USER_DEF_ALTERNATE_KIND_I;
+    tmp = g_malloc0(sizeof(UserDefAlternate));
+    tmp->type = QTYPE_QINT;
     tmp->i = 42;

-    visit_type_UserDefAlternate(data->ov, &tmp, NULL, &err);
-    g_assert(err == NULL);
+    visit_type_UserDefAlternate(data->ov, &tmp, NULL, &error_abort);
     arg = qmp_output_get_qobject(data->qov);

     g_assert(qobject_type(arg) == QTYPE_QINT);
     g_assert_cmpint(qint_get_int(qobject_to_qint(arg)), ==, 42);

     qapi_free_UserDefAlternate(tmp);
+
+    tmp = g_malloc0(sizeof(UserDefAlternate));
+    tmp->type = QTYPE_QSTRING;
+    tmp->s = g_strdup("hello");
+
+    visit_type_UserDefAlternate(data->ov, &tmp, NULL, &error_abort);
+    arg = qmp_output_get_qobject(data->qov);
+
+    g_assert(qobject_type(arg) == QTYPE_QSTRING);
+    g_assert_cmpstr(qstring_get_str(qobject_to_qstring(arg)), ==, "hello");
+
+    qapi_free_UserDefAlternate(tmp);
 }

 static void test_visitor_out_empty(TestOutputVisitorData *data,
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 20/46] qapi: Fix alternates that accept 'number' but not 'int'
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (18 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 19/46] qapi: Simplify visiting of alternate types Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 21/46] qmp: Fix reference-counting of qnull on empty output visit Eric Blake
                   ` (26 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

The QMP input visitor allows integral values to be assigned by
promotion to a QTYPE_QFLOAT.  However, when parsing an alternate,
we did not take this into account, such that an alternate that
accepts 'number' but not 'int' would reject integral values.

With this patch, we now have the following desirable table:

    alternate has      case selected for
    'int'  'number'    QTYPE_QINT  QTYPE_QFLOAT
      no        no     error       error
      no       yes     'number'    'number'
     yes        no     'int'       error
     yes       yes     'int'       'number'

While it is unlikely that we will ever use 'number' in an
alternate other than in the testsuite, it never hurts to be
more precise in what we allow.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 include/qapi/visitor-impl.h    |  2 +-
 include/qapi/visitor.h         |  3 ++-
 qapi/qapi-visit-core.c         |  4 ++--
 qapi/qmp-input-visitor.c       |  4 ++++
 scripts/qapi-visit.py          |  9 +++++++--
 tests/test-qmp-input-visitor.c | 15 ++++++---------
 6 files changed, 22 insertions(+), 15 deletions(-)

diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
index 6d95b36..1d09b7b 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -33,7 +33,7 @@ struct Visitor
     void (*type_enum)(Visitor *v, int *obj, const char * const strings[],
                       const char *kind, const char *name, Error **errp);
     /* May be NULL; most useful for input visitors. */
-    void (*get_next_type)(Visitor *v, qtype_code *type,
+    void (*get_next_type)(Visitor *v, qtype_code *type, bool promote_int,
                           const char *name, Error **errp);

     void (*type_int)(Visitor *v, int64_t *obj, const char *name, Error **errp);
diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index 088d3fc..1e73752 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -46,8 +46,9 @@ void visit_optional(Visitor *v, bool *present, const char *name,
  * Determine the qtype of the item @name in the current QDict visit.
  * For input visitors, set *@type to the correct qtype of a qapi
  * alternate type; for other visitors, leave *@type unchanged.
+ * If @promote_int, treat integers as QTYPE_FLOAT.
  */
-void visit_get_next_type(Visitor *v, qtype_code *type,
+void visit_get_next_type(Visitor *v, qtype_code *type, bool promote_int,
                          const char *name, Error **errp);
 void visit_type_enum(Visitor *v, int *obj, const char * const strings[],
                      const char *kind, const char *name, Error **errp);
diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index 3f24daa..884fe94 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -81,11 +81,11 @@ void visit_optional(Visitor *v, bool *present, const char *name,
     }
 }

-void visit_get_next_type(Visitor *v, qtype_code *type,
+void visit_get_next_type(Visitor *v, qtype_code *type, bool promote_int,
                          const char *name, Error **errp)
 {
     if (v->get_next_type) {
-        v->get_next_type(v, type, name, errp);
+        v->get_next_type(v, type, promote_int, name, errp);
     }
 }

diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index 803ffad..5310db5 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -209,6 +209,7 @@ static void qmp_input_end_list(Visitor *v, Error **errp)
 }

 static void qmp_input_get_next_type(Visitor *v, qtype_code *type,
+                                    bool promote_int,
                                     const char *name, Error **errp)
 {
     QmpInputVisitor *qiv = to_qiv(v);
@@ -219,6 +220,9 @@ static void qmp_input_get_next_type(Visitor *v, qtype_code *type,
         return;
     }
     *type = qobject_type(qobj);
+    if (promote_int && *type == QTYPE_QINT) {
+        *type = QTYPE_QFLOAT;
+    }
 }

 static void qmp_input_type_int(Visitor *v, int64_t *obj, const char *name,
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index e58c7f9..6ca7c7c 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -171,6 +171,11 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s *obj, const char *name, Error


 def gen_visit_alternate(name, variants):
+    promote_int = 'true'
+    for var in variants.variants:
+        if var.type.alternate_qtype() == 'QTYPE_QINT':
+            promote_int = 'false'
+
     ret = mcgen('''

 void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error **errp)
@@ -181,13 +186,13 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
     if (err) {
         goto out;
     }
-    visit_get_next_type(v, &(*obj)->type, name, &err);
+    visit_get_next_type(v, &(*obj)->type, %(promote_int)s, name, &err);
     if (err) {
         goto out_obj;
     }
     switch ((*obj)->type) {
 ''',
-                c_name=c_name(name))
+                c_name=c_name(name), promote_int=promote_int)

     for var in variants.variants:
         ret += mcgen('''
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index 69fa98f..54be9b4 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -415,20 +415,17 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,
     qapi_free_AltOne(one);
     one = NULL;

-    /* FIXME: Integers should parse as numbers */
     v = visitor_input_test_init(data, "42");
-    visit_type_AltTwo(v, &two, NULL, &err);
-    g_assert(err);
-    error_free(err);
-    err = NULL;
+    visit_type_AltTwo(v, &two, NULL, &error_abort);
+    g_assert_cmpint(two->type, ==, QTYPE_QFLOAT);
+    g_assert_cmpfloat(two->n, ==, 42.0);
     qapi_free_AltTwo(two);
     one = NULL;

     v = visitor_input_test_init(data, "42");
-    visit_type_AltThree(v, &three, NULL, &err);
-    g_assert(err);
-    error_free(err);
-    err = NULL;
+    visit_type_AltThree(v, &three, NULL, &error_abort);
+    g_assert_cmpint(three->type, ==, QTYPE_QFLOAT);
+    g_assert_cmpfloat(three->n, ==, 42.0);
     qapi_free_AltThree(three);
     one = NULL;

-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 21/46] qmp: Fix reference-counting of qnull on empty output visit
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (19 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 20/46] qapi: Fix alternates that accept 'number' but not 'int' Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 22/46] qapi: Don't abuse stack to track qmp-output root Eric Blake
                   ` (25 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Commit 6c2f9a15 ensured that we would not return NULL when the
caller used an output visitor but had nothing to visit. But
in doing so, it added a FIXME about a reference count leak
that could abort qemu in the (unlikely) case of SIZE_MAX such
visits (more plausible on 32-bit).

This fixes things by documenting the internal contracts, and
explaining why the internal function can return NULL and only
the public facing interface needs to worry about qnull(),
thus avoiding over-referencing the qnull_ global object.

It does not, however, fix the stupidity of the stack mixing
up two separate pieces of information; add a FIXME to explain
that issue.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
"Commit 6c2f9a15" above assumes the commit ids from the pending
pull-qapi-2015-09-21 tag will be preserved
---
 qapi/qmp-output-visitor.c       | 30 ++++++++++++++++++++++++++++--
 tests/test-qmp-output-visitor.c |  2 ++
 2 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c
index 29899ac..850c83d 100644
--- a/qapi/qmp-output-visitor.c
+++ b/qapi/qmp-output-visitor.c
@@ -29,6 +29,12 @@ typedef QTAILQ_HEAD(QStack, QStackEntry) QStack;
 struct QmpOutputVisitor
 {
     Visitor visitor;
+    /* FIXME: we are abusing stack to hold two separate pieces of
+     * information: the current root object, and the stack of objects
+     * still being built.  Worse, our behavior is inconsistent:
+     * visiting two top-level scalars in a row discards the first in
+     * favor of the second, but visiting two top-level objects in a
+     * row tries to append the second object into the first.  */
     QStack stack;
 };

@@ -41,10 +47,12 @@ static QmpOutputVisitor *to_qov(Visitor *v)
     return container_of(v, QmpOutputVisitor, visitor);
 }

+/* Push @value onto the stack of current QObjects being built */
 static void qmp_output_push_obj(QmpOutputVisitor *qov, QObject *value)
 {
     QStackEntry *e = g_malloc0(sizeof(*e));

+    assert(value);
     e->value = value;
     if (qobject_type(e->value) == QTYPE_QLIST) {
         e->is_list_head = true;
@@ -52,16 +60,20 @@ static void qmp_output_push_obj(QmpOutputVisitor *qov, QObject *value)
     QTAILQ_INSERT_HEAD(&qov->stack, e, node);
 }

+/* Grab and remove the most recent QObject from the stack */
 static QObject *qmp_output_pop(QmpOutputVisitor *qov)
 {
     QStackEntry *e = QTAILQ_FIRST(&qov->stack);
     QObject *value;
+
+    assert(e);
     QTAILQ_REMOVE(&qov->stack, e, node);
     value = e->value;
     g_free(e);
     return value;
 }

+/* Grab the root QObject, if any, in preparation to empty the stack */
 static QObject *qmp_output_first(QmpOutputVisitor *qov)
 {
     QStackEntry *e = QTAILQ_LAST(&qov->stack, QStack);
@@ -72,24 +84,32 @@ static QObject *qmp_output_first(QmpOutputVisitor *qov)
      * handle null.
      */
     if (!e) {
-        return qnull();
+        /* No root */
+        return NULL;
     }
-
+    assert(e->value);
     return e->value;
 }

+/* Grab the most recent QObject from the stack, which must exist */
 static QObject *qmp_output_last(QmpOutputVisitor *qov)
 {
     QStackEntry *e = QTAILQ_FIRST(&qov->stack);
+
+    assert(e);
     return e->value;
 }

+/* Add @value to the current QObject being built.
+ * If the stack is visiting a dictionary or list, @value is now owned
+ * by that container. Otherwise, @value is now the root.  */
 static void qmp_output_add_obj(QmpOutputVisitor *qov, const char *name,
                                QObject *value)
 {
     QObject *cur;

     if (QTAILQ_EMPTY(&qov->stack)) {
+        /* Stack was empty, track this object as root */
         qmp_output_push_obj(qov, value);
         return;
     }
@@ -98,13 +118,16 @@ static void qmp_output_add_obj(QmpOutputVisitor *qov, const char *name,

     switch (qobject_type(cur)) {
     case QTYPE_QDICT:
+        assert(name);
         qdict_put_obj(qobject_to_qdict(cur), name, value);
         break;
     case QTYPE_QLIST:
         qlist_append_obj(qobject_to_qlist(cur), value);
         break;
     default:
+        /* The previous root was a scalar, replace it with a new root */
         qobject_decref(qmp_output_pop(qov));
+        assert(QTAILQ_EMPTY(&qov->stack));
         qmp_output_push_obj(qov, value);
         break;
     }
@@ -198,11 +221,14 @@ static void qmp_output_type_any(Visitor *v, QObject **obj, const char *name,
     qmp_output_add_obj(qov, name, *obj);
 }

+/* Finish building, and return the root object. Will not be NULL. */
 QObject *qmp_output_get_qobject(QmpOutputVisitor *qov)
 {
     QObject *obj = qmp_output_first(qov);
     if (obj) {
         qobject_incref(obj);
+    } else {
+        obj = qnull();
     }
     return obj;
 }
diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c
index d13e0e0..0f72b5d 100644
--- a/tests/test-qmp-output-visitor.c
+++ b/tests/test-qmp-output-visitor.c
@@ -548,6 +548,8 @@ static void test_visitor_out_empty(TestOutputVisitorData *data,

     arg = qmp_output_get_qobject(data->qov);
     g_assert(qobject_type(arg) == QTYPE_QNULL);
+    /* Check that qnull reference counting is sane */
+    g_assert(arg->refcnt == 2);
     qobject_decref(arg);
 }

-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 22/46] qapi: Don't abuse stack to track qmp-output root
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (20 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 21/46] qmp: Fix reference-counting of qnull on empty output visit Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 23/46] qapi: Remove dead visitor code Eric Blake
                   ` (24 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

The previous commit documented an inconsistency in how we are
using the stack of qmp-output-visitor.  Normally, pushing a
single top-level object puts the object on the stack twice:
once as the root, and once as the current container being
appended to; but popping that struct only pops once.  However,
qmp_ouput_add() was trying to either set up the added object
as the new root (works if you parse two top-level scalars in a
row: the second replaces the first as the root) or as a member
of the current container (works as long as you have an open
container on the stack; but if you have popped the first
top-level container, it then resolves to the root and still
tries to add into that existing container).

Fix the stupidity by not tracking two separate things in the
stack.

Not done here: maybe qmp_output_get_object() should assert that
the stack is empty, rather than letting users look at the current
root even while the root is still being visited.
---
 qapi/qmp-output-visitor.c | 70 +++++++++++++++--------------------------------
 1 file changed, 22 insertions(+), 48 deletions(-)

diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c
index 850c83d..f891e72 100644
--- a/qapi/qmp-output-visitor.c
+++ b/qapi/qmp-output-visitor.c
@@ -29,13 +29,8 @@ typedef QTAILQ_HEAD(QStack, QStackEntry) QStack;
 struct QmpOutputVisitor
 {
     Visitor visitor;
-    /* FIXME: we are abusing stack to hold two separate pieces of
-     * information: the current root object, and the stack of objects
-     * still being built.  Worse, our behavior is inconsistent:
-     * visiting two top-level scalars in a row discards the first in
-     * favor of the second, but visiting two top-level objects in a
-     * row tries to append the second object into the first.  */
-    QStack stack;
+    QStack stack; /* Stack of containers still growing */
+    QObject *root; /* Root of the output visit */
 };

 #define qmp_output_add(qov, name, value) \
@@ -52,6 +47,7 @@ static void qmp_output_push_obj(QmpOutputVisitor *qov, QObject *value)
 {
     QStackEntry *e = g_malloc0(sizeof(*e));

+    assert(qov->root);
     assert(value);
     e->value = value;
     if (qobject_type(e->value) == QTYPE_QLIST) {
@@ -76,28 +72,15 @@ static QObject *qmp_output_pop(QmpOutputVisitor *qov)
 /* Grab the root QObject, if any, in preparation to empty the stack */
 static QObject *qmp_output_first(QmpOutputVisitor *qov)
 {
-    QStackEntry *e = QTAILQ_LAST(&qov->stack, QStack);
-
-    /*
-     * FIXME Wrong, because qmp_output_get_qobject() will increment
-     * the refcnt *again*.  We need to think through how visitors
-     * handle null.
-     */
-    if (!e) {
-        /* No root */
-        return NULL;
-    }
-    assert(e->value);
-    return e->value;
+    return qov->root;
 }

-/* Grab the most recent QObject from the stack, which must exist */
+/* Grab the most recent QObject from the stack, if any */
 static QObject *qmp_output_last(QmpOutputVisitor *qov)
 {
     QStackEntry *e = QTAILQ_FIRST(&qov->stack);

-    assert(e);
-    return e->value;
+    return e ? e->value : NULL;
 }

 /* Add @value to the current QObject being built.
@@ -108,28 +91,23 @@ static void qmp_output_add_obj(QmpOutputVisitor *qov, const char *name,
 {
     QObject *cur;

-    if (QTAILQ_EMPTY(&qov->stack)) {
-        /* Stack was empty, track this object as root */
-        qmp_output_push_obj(qov, value);
-        return;
-    }
-
     cur = qmp_output_last(qov);

-    switch (qobject_type(cur)) {
-    case QTYPE_QDICT:
-        assert(name);
-        qdict_put_obj(qobject_to_qdict(cur), name, value);
-        break;
-    case QTYPE_QLIST:
-        qlist_append_obj(qobject_to_qlist(cur), value);
-        break;
-    default:
-        /* The previous root was a scalar, replace it with a new root */
-        qobject_decref(qmp_output_pop(qov));
-        assert(QTAILQ_EMPTY(&qov->stack));
-        qmp_output_push_obj(qov, value);
-        break;
+    if (!cur) {
+        qobject_decref(qov->root);
+        qov->root = value;
+    } else {
+        switch (qobject_type(cur)) {
+        case QTYPE_QDICT:
+            assert(name);
+            qdict_put_obj(qobject_to_qdict(cur), name, value);
+            break;
+        case QTYPE_QLIST:
+            qlist_append_obj(qobject_to_qlist(cur), value);
+            break;
+        default:
+            g_assert_not_reached();
+        }
     }
 }

@@ -242,16 +220,12 @@ void qmp_output_visitor_cleanup(QmpOutputVisitor *v)
 {
     QStackEntry *e, *tmp;

-    /* The bottom QStackEntry, if any, owns the root QObject. See the
-     * qmp_output_push_obj() invocations in qmp_output_add_obj(). */
-    QObject *root = QTAILQ_EMPTY(&v->stack) ? NULL : qmp_output_first(v);
-
     QTAILQ_FOREACH_SAFE(e, &v->stack, node, tmp) {
         QTAILQ_REMOVE(&v->stack, e, node);
         g_free(e);
     }

-    qobject_decref(root);
+    qobject_decref(v->root);
     g_free(v);
 }

-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 23/46] qapi: Remove dead visitor code
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (21 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 22/46] qapi: Don't abuse stack to track qmp-output root Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 24/46] qapi: Document visitor interfaces Eric Blake
                   ` (23 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Commit cbc95538 removed unused start_handle() and end_handle(),
but forgot got remove their declarations.

Commit 4e27e819 introduced optional visitor callbacks for all
sorts of int types, but except for type_uint64 and type_size,
none of them have ever been supplied (the generic implementation
based on using type_int then bounds-checking works just fine).
In the interest of simplicity, it's easier to make the visitor
callback interface not have to worry about the other sizes.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 include/qapi/visitor-impl.h |  19 +++----
 include/qapi/visitor.h      |   3 -
 qapi/qapi-visit-core.c      | 131 +++++++++++++++++---------------------------
 3 files changed, 58 insertions(+), 95 deletions(-)

diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
index 1d09b7b..370935a 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -1,7 +1,7 @@
 /*
  * Core Definitions for QAPI Visitor implementations
  *
- * Copyright (C) 2012 Red Hat, Inc.
+ * Copyright (C) 2012, 2015 Red Hat, Inc.
  *
  * Author: Paolo Bonizni <pbonzini@redhat.com>
  *
@@ -48,18 +48,15 @@ struct Visitor
     void (*optional)(Visitor *v, bool *present, const char *name,
                      Error **errp);

-    void (*type_uint8)(Visitor *v, uint8_t *obj, const char *name, Error **errp);
-    void (*type_uint16)(Visitor *v, uint16_t *obj, const char *name, Error **errp);
-    void (*type_uint32)(Visitor *v, uint32_t *obj, const char *name, Error **errp);
-    void (*type_uint64)(Visitor *v, uint64_t *obj, const char *name, Error **errp);
-    void (*type_int8)(Visitor *v, int8_t *obj, const char *name, Error **errp);
-    void (*type_int16)(Visitor *v, int16_t *obj, const char *name, Error **errp);
-    void (*type_int32)(Visitor *v, int32_t *obj, const char *name, Error **errp);
-    void (*type_int64)(Visitor *v, int64_t *obj, const char *name, Error **errp);
-    /* visit_type_size() falls back to (*type_uint64)() if type_size is unset */
-    void (*type_size)(Visitor *v, uint64_t *obj, const char *name, Error **errp);
     bool (*start_union)(Visitor *v, bool data_present, Error **errp);
     void (*end_union)(Visitor *v, bool data_present, Error **errp);
+
+    /* Only required to visit uint64 differently than (*type_int)().  */
+    void (*type_uint64)(Visitor *v, uint64_t *obj, const char *name,
+                        Error **errp);
+    /* Only required to visit sizes differently than (*type_uint64)().  */
+    void (*type_size)(Visitor *v, uint64_t *obj, const char *name,
+                      Error **errp);
 };

 void input_type_enum(Visitor *v, int *obj, const char * const strings[],
diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index 1e73752..a5ec44e 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -27,9 +27,6 @@ typedef struct GenericList
     struct GenericList *next;
 } GenericList;

-void visit_start_handle(Visitor *v, void **obj, const char *kind,
-                        const char *name, Error **errp);
-void visit_end_handle(Visitor *v, Error **errp);
 void visit_start_struct(Visitor *v, void **obj, const char *kind,
                         const char *name, size_t size, Error **errp);
 void visit_end_struct(Visitor *v, Error **errp);
diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index 884fe94..cbf7780 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -104,57 +104,48 @@ void visit_type_uint8(Visitor *v, uint8_t *obj, const char *name, Error **errp)
 {
     int64_t value;

-    if (v->type_uint8) {
-        v->type_uint8(v, obj, name, errp);
-    } else {
-        value = *obj;
-        v->type_int(v, &value, name, errp);
-        if (value < 0 || value > UINT8_MAX) {
-            error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                       name ? name : "null", "uint8_t");
-            return;
-        }
-        *obj = value;
+    value = *obj;
+    v->type_int(v, &value, name, errp);
+    if (value < 0 || value > UINT8_MAX) {
+        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
+                   name ? name : "null", "uint8_t");
+        return;
     }
+    *obj = value;
 }

-void visit_type_uint16(Visitor *v, uint16_t *obj, const char *name, Error **errp)
+void visit_type_uint16(Visitor *v, uint16_t *obj, const char *name,
+                       Error **errp)
 {
     int64_t value;

-    if (v->type_uint16) {
-        v->type_uint16(v, obj, name, errp);
-    } else {
-        value = *obj;
-        v->type_int(v, &value, name, errp);
-        if (value < 0 || value > UINT16_MAX) {
-            error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                       name ? name : "null", "uint16_t");
-            return;
-        }
-        *obj = value;
+    value = *obj;
+    v->type_int(v, &value, name, errp);
+    if (value < 0 || value > UINT16_MAX) {
+        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
+                   name ? name : "null", "uint16_t");
+        return;
     }
+    *obj = value;
 }

-void visit_type_uint32(Visitor *v, uint32_t *obj, const char *name, Error **errp)
+void visit_type_uint32(Visitor *v, uint32_t *obj, const char *name,
+                       Error **errp)
 {
     int64_t value;

-    if (v->type_uint32) {
-        v->type_uint32(v, obj, name, errp);
-    } else {
-        value = *obj;
-        v->type_int(v, &value, name, errp);
-        if (value < 0 || value > UINT32_MAX) {
-            error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                       name ? name : "null", "uint32_t");
-            return;
-        }
-        *obj = value;
+    value = *obj;
+    v->type_int(v, &value, name, errp);
+    if (value < 0 || value > UINT32_MAX) {
+        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
+                   name ? name : "null", "uint32_t");
+        return;
     }
+    *obj = value;
 }

-void visit_type_uint64(Visitor *v, uint64_t *obj, const char *name, Error **errp)
+void visit_type_uint64(Visitor *v, uint64_t *obj, const char *name,
+                       Error **errp)
 {
     int64_t value;

@@ -171,77 +162,55 @@ void visit_type_int8(Visitor *v, int8_t *obj, const char *name, Error **errp)
 {
     int64_t value;

-    if (v->type_int8) {
-        v->type_int8(v, obj, name, errp);
-    } else {
-        value = *obj;
-        v->type_int(v, &value, name, errp);
-        if (value < INT8_MIN || value > INT8_MAX) {
-            error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                       name ? name : "null", "int8_t");
-            return;
-        }
-        *obj = value;
+    value = *obj;
+    v->type_int(v, &value, name, errp);
+    if (value < INT8_MIN || value > INT8_MAX) {
+        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
+                   name ? name : "null", "int8_t");
+        return;
     }
+    *obj = value;
 }

 void visit_type_int16(Visitor *v, int16_t *obj, const char *name, Error **errp)
 {
     int64_t value;

-    if (v->type_int16) {
-        v->type_int16(v, obj, name, errp);
-    } else {
-        value = *obj;
-        v->type_int(v, &value, name, errp);
-        if (value < INT16_MIN || value > INT16_MAX) {
-            error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                       name ? name : "null", "int16_t");
-            return;
-        }
-        *obj = value;
+    value = *obj;
+    v->type_int(v, &value, name, errp);
+    if (value < INT16_MIN || value > INT16_MAX) {
+        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
+                   name ? name : "null", "int16_t");
+        return;
     }
+    *obj = value;
 }

 void visit_type_int32(Visitor *v, int32_t *obj, const char *name, Error **errp)
 {
     int64_t value;

-    if (v->type_int32) {
-        v->type_int32(v, obj, name, errp);
-    } else {
-        value = *obj;
-        v->type_int(v, &value, name, errp);
-        if (value < INT32_MIN || value > INT32_MAX) {
-            error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                       name ? name : "null", "int32_t");
-            return;
-        }
-        *obj = value;
+    value = *obj;
+    v->type_int(v, &value, name, errp);
+    if (value < INT32_MIN || value > INT32_MAX) {
+        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
+                   name ? name : "null", "int32_t");
+        return;
     }
+    *obj = value;
 }

 void visit_type_int64(Visitor *v, int64_t *obj, const char *name, Error **errp)
 {
-    if (v->type_int64) {
-        v->type_int64(v, obj, name, errp);
-    } else {
-        v->type_int(v, obj, name, errp);
-    }
+    v->type_int(v, obj, name, errp);
 }

 void visit_type_size(Visitor *v, uint64_t *obj, const char *name, Error **errp)
 {
-    int64_t value;
-
     if (v->type_size) {
         v->type_size(v, obj, name, errp);
-    } else if (v->type_uint64) {
-        v->type_uint64(v, obj, name, errp);
     } else {
-        value = *obj;
-        v->type_int(v, &value, name, errp);
-        *obj = value;
+        visit_type_uint64(v, obj, name, errp);
     }
 }

-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 24/46] qapi: Document visitor interfaces
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (22 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 23/46] qapi: Remove dead visitor code Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 25/46] qapi: Plug leaks in test-qmp-input-visitor Eric Blake
                   ` (22 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

The visitor interface for mapping between QObject and qapi
has formerly been documented only by reading source code,
making it difficult to propose changes to either
scripts/qapi*.py or to clients without knowing whether those
changes would be safe.  This tries to add documentation,
including mentioning when parameters can be NULL, and where
there are still some interface warts that would be nice
to remove.  In particular, I have plans to remove
visit_start_union() in a future patch.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 include/qapi/visitor-impl.h |  36 +++++++-
 include/qapi/visitor.h      | 195 ++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 220 insertions(+), 11 deletions(-)

diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
index 370935a..090b19b 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -15,40 +15,66 @@
 #include "qapi/error.h"
 #include "qapi/visitor.h"

+/* This file describes the callback interface for implementing a
+ * QObject visitor.  For the client interface, see visitor.h.  When
+ * implementing the callbacks, it is easiest to declare a struct with
+ * 'Visitor visitor;' as the first member.  Semantics for the
+ * callbacks are generally similar to the counterpart public
+ * interface.  */
+
 struct Visitor
 {
-    /* Must be set */
+    /* Must be provided to visit structs (the string visitors do not
+     * currently visit structs). */
     void (*start_struct)(Visitor *v, void **obj, const char *kind,
                          const char *name, size_t size, Error **errp);
+    /* Must be provided if start_struct is present. */
     void (*end_struct)(Visitor *v, Error **errp);

+    /* May be NULL; most useful for input visitors. */
     void (*start_implicit_struct)(Visitor *v, void **obj, size_t size,
                                   Error **errp);
+    /* May be NULL */
     void (*end_implicit_struct)(Visitor *v, Error **errp);

+    /* Must be set */
     void (*start_list)(Visitor *v, const char *name, Error **errp);
+    /* Must be set */
     GenericList *(*next_list)(Visitor *v, GenericList **list, Error **errp);
+    /* Must be set */
     void (*end_list)(Visitor *v, Error **errp);

+    /* Must be set, although the helpers input_type_enum() and
+     * output_type_enum() can be used.  */
     void (*type_enum)(Visitor *v, int *obj, const char * const strings[],
                       const char *kind, const char *name, Error **errp);
     /* May be NULL; most useful for input visitors. */
     void (*get_next_type)(Visitor *v, qtype_code *type, bool promote_int,
                           const char *name, Error **errp);

+    /* Must be set */
     void (*type_int)(Visitor *v, int64_t *obj, const char *name, Error **errp);
+    /* Must be set */
     void (*type_bool)(Visitor *v, bool *obj, const char *name, Error **errp);
+    /* Must be set */
     void (*type_str)(Visitor *v, char **obj, const char *name, Error **errp);
+
+    /* Must be provided to visit numbers (the opts visitor does not
+     * currently visit non-integers). */
     void (*type_number)(Visitor *v, double *obj, const char *name,
                         Error **errp);
+    /* Must be provided to visit arbitrary QTypes (the opts and string
+     * visitors do not currently visit arbitrary types).  */
     void (*type_any)(Visitor *v, QObject **obj, const char *name,
                      Error **errp);

-    /* May be NULL */
+    /* May be NULL; most useful for input visitors. */
     void (*optional)(Visitor *v, bool *present, const char *name,
                      Error **errp);

+    /* FIXME - needs to be removed */
     bool (*start_union)(Visitor *v, bool data_present, Error **errp);
+    /* FIXME - needs to be removed */
     void (*end_union)(Visitor *v, bool data_present, Error **errp);

     /* Only required to visit uint64 differently than (*type_int)().  */
@@ -59,8 +85,14 @@ struct Visitor
                       Error **errp);
 };

+/**
+ * A generic visitor.type_enum suitable for input visitors.
+ */
 void input_type_enum(Visitor *v, int *obj, const char * const strings[],
                      const char *kind, const char *name, Error **errp);
+/**
+ * A generic visitor.type_enum suitable for output visitors.
+ */
 void output_type_enum(Visitor *v, int *obj, const char * const strings[],
                       const char *kind, const char *name, Error **errp);

diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index a5ec44e..2883cf4 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -18,6 +18,17 @@
 #include "qapi/error.h"
 #include <stdlib.h>

+/* This file describes the client view for visiting a map between
+ * QObjects and another representation (command line options, strings,
+ * or generated QAPI C structs).  An input visitor converts from
+ * QObject to another form; an output visitor converts from the other
+ * form back into QObjects.  These functions seldom need to be called
+ * directly, but are instead used by code generated by
+ * scripts/qapi-visit.py.  For the visitor callback contracts, see
+ * visitor-impl.h. */
+
+/* This struct is layout-compatible with all other *List structs
+ * created by the qapi generator. */
 typedef struct GenericList
 {
     union {
@@ -27,15 +38,77 @@ typedef struct GenericList
     struct GenericList *next;
 } GenericList;

+/**
+ * Prepare to visit a QDict with C type @kind tied to QDict key @name.
+ * @name will be NULL if this is visited as part of a QList.
+ * The caller then makes a series of visit calls for each key expected
+ * in the dictionary, followed by a call to visit_end_struct(). For an
+ * input visitor, @obj can be NULL to validate that the visit will
+ * succeed; otherwise, *@obj is assigned with an allocation of @size
+ * bytes. For other visitors, *@obj is the object to visit. Set *@errp
+ * on failure.
+ *
+ * FIXME: *@obj can be modified even on error; this can lead to
+ * memory leaks if clients aren't careful.
+ */
 void visit_start_struct(Visitor *v, void **obj, const char *kind,
                         const char *name, size_t size, Error **errp);
+/**
+ * Complete a struct started earlier.
+ * Must be called after any successful use of visit_start_struct(),
+ * even if intermediate processing was skipped due to errors.
+ */
 void visit_end_struct(Visitor *v, Error **errp);
+
+/**
+ * Prepare to visit an implicit struct.
+ * Similar to visit_start_struct(), except that this will visit a
+ * C pointer pointing to @size bytes, and where the QDict fields are
+ * part of the parent object.
+ *
+ * FIXME: *@obj can be modified even on error; this can lead to
+ * memory leaks if clients aren't careful.
+ */
 void visit_start_implicit_struct(Visitor *v, void **obj, size_t size,
                                  Error **errp);
+/**
+ * Complete an implicit struct started earlier.
+ * Must be called after any successful use of visit_start_implicit_struct(),
+ * even if intermediate processing was skipped due to errors.
+ */
 void visit_end_implicit_struct(Visitor *v, Error **errp);
+
+/**
+ * Prepare to visit a QList tied to QDict key @name.
+ * @name will be NULL if this is visited as part of a QList.
+ * After calling this, the elements must be collected until
+ * visit_next_list() returns NULL, then visit_end_list() must be
+ * used to complete the visit.
+ */
 void visit_start_list(Visitor *v, const char *name, Error **errp);
+/**
+ * Collect the next list member and append it to *@list.
+ * Start with *@list of NULL, then subsequent iterations should pass
+ * *@list pointing to the previous return value.  Must be called in a
+ * loop until a NULL return or error occurs; for each non-NULL return,
+ * the caller must then call the appropriate visit_type_*() for the
+ * element type of the list, with that function's name parameter set
+ * to NULL.
+ */
 GenericList *visit_next_list(Visitor *v, GenericList **list, Error **errp);
+/**
+ * Complete the list started earlier.
+ * Must be called after any successful use of visit_start_list(),
+ * even if intermediate processing was skipped due to errors.
+ */
 void visit_end_list(Visitor *v, Error **errp);
+
+/**
+ * Check if an optional member @name of a QDict needs visiting.
+ * For input visitors, set *@present according to whether the
+ * corresponding visit_type_*() needs calling; for other visitors,
+ * leave *@present unchanged.
+ */
 void visit_optional(Visitor *v, bool *present, const char *name,
                     Error **errp);

@@ -47,23 +120,127 @@ void visit_optional(Visitor *v, bool *present, const char *name,
  */
 void visit_get_next_type(Visitor *v, qtype_code *type, bool promote_int,
                          const char *name, Error **errp);
+
+/**
+ * Visit an enum value tied to @name in the current QDict visit.
+ * @name will be NULL if this is visited as part of a QList.
+ * For input visitors, parse a string and set *@obj to the numeric value
+ * of the enum type @kind using @strings as the mapping; for output
+ * visitors, reverse the mapping and visit the output string determined
+ * by *@obj.
+ */
 void visit_type_enum(Visitor *v, int *obj, const char * const strings[],
                      const char *kind, const char *name, Error **errp);
+
+/**
+ * Visit an integer value tied to @name in the current QDict visit.
+ * @name will be NULL if this is visited as part of a QList.
+ * For input visitors, set *@obj to the parsed value; for other visitors,
+ * leave *@obj unchanged.
+ */
 void visit_type_int(Visitor *v, int64_t *obj, const char *name, Error **errp);
-void visit_type_uint8(Visitor *v, uint8_t *obj, const char *name, Error **errp);
-void visit_type_uint16(Visitor *v, uint16_t *obj, const char *name, Error **errp);
-void visit_type_uint32(Visitor *v, uint32_t *obj, const char *name, Error **errp);
-void visit_type_uint64(Visitor *v, uint64_t *obj, const char *name, Error **errp);
+/**
+ * Visit a uint8_t value tied to @name in the current QDict visit.
+ * Like visit_type_int(), except clamps the value to uint8_t range.
+ */
+void visit_type_uint8(Visitor *v, uint8_t *obj, const char *name,
+                      Error **errp);
+/**
+ * Visit a uint16_t value tied to @name in the current QDict visit.
+ * Like visit_type_int(), except clamps the value to uint16_t range.
+ */
+void visit_type_uint16(Visitor *v, uint16_t *obj, const char *name,
+                       Error **errp);
+/**
+ * Visit a uint32_t value tied to @name in the current QDict visit.
+ * Like visit_type_int(), except clamps the value to uint32_t range.
+ */
+void visit_type_uint32(Visitor *v, uint32_t *obj, const char *name,
+                       Error **errp);
+/**
+ * Visit a uint64_t value tied to @name in the current QDict visit.
+ * Like visit_type_int(), except clamps the value to uint64_t range
+ * (that is, ensures it is unsigned).
+ */
+void visit_type_uint64(Visitor *v, uint64_t *obj, const char *name,
+                       Error **errp);
+/**
+ * Visit an int8_t value tied to @name in the current QDict visit.
+ * Like visit_type_int(), except clamps the value to int8_t range.
+ */
 void visit_type_int8(Visitor *v, int8_t *obj, const char *name, Error **errp);
-void visit_type_int16(Visitor *v, int16_t *obj, const char *name, Error **errp);
-void visit_type_int32(Visitor *v, int32_t *obj, const char *name, Error **errp);
-void visit_type_int64(Visitor *v, int64_t *obj, const char *name, Error **errp);
-void visit_type_size(Visitor *v, uint64_t *obj, const char *name, Error **errp);
+/**
+ * Visit an int16_t value tied to @name in the current QDict visit.
+ * Like visit_type_int(), except clamps the value to int16_t range.
+ */
+void visit_type_int16(Visitor *v, int16_t *obj, const char *name,
+                      Error **errp);
+/**
+ * Visit an uint32_t value tied to @name in the current QDict visit.
+ * Like visit_type_int(), except clamps the value to int32_t range.
+ */
+void visit_type_int32(Visitor *v, int32_t *obj, const char *name,
+                      Error **errp);
+/**
+ * Visit an int64_t value tied to @name in the current QDict visit.
+ * Like visit_type_int(), except clamps the value to int64_t range.
+ */
+void visit_type_int64(Visitor *v, int64_t *obj, const char *name,
+                      Error **errp);
+/**
+ * Visit a uint64_t value tied to @name in the current QDict visit.
+ * Like visit_type_uint64(), except that some visitors may choose to
+ * recognize additional suffixes for easily scaling input values.
+ */
+void visit_type_size(Visitor *v, uint64_t *obj, const char *name,
+                     Error **errp);
+
+/**
+ * Visit a boolean value tied to @name in the current QDict visit.
+ * @name will be NULL if this is visited as part of a QList.
+ * Input visitors set *@obj to the value; other visitors will leave
+ * *@obj unchanged.
+ */
 void visit_type_bool(Visitor *v, bool *obj, const char *name, Error **errp);
+
+/**
+ * Visit a string value tied to @name in the current QDict visit.
+ * @name will be NULL if this is visited as part of a QList.
+ * @obj must be non-NULL.  Input visitors set *@obj to the parsed string;
+ * while output visitors leave *@obj unchanged, except that a NULL *@obj
+ * must be treated the same as "".
+ *
+ * FIXME: Unfortunately not const-correct for output visitors.
+ */
 void visit_type_str(Visitor *v, char **obj, const char *name, Error **errp);
-void visit_type_number(Visitor *v, double *obj, const char *name, Error **errp);
+
+/**
+ * Visit a number value tied to @name in the current QDict visit.
+ * @name will be NULL if this is visited as part of a QList.
+ * Input visitors set *@obj to the value; other visitors will leave
+ * *@obj unchanged.
+ */
+void visit_type_number(Visitor *v, double *obj, const char *name,
+                       Error **errp);
+
+/**
+ * Visit an arbitrary qtype value tied to @name in the current QDict visit.
+ * @name will be NULL if this is visited as part of a QList.
+ * Input visitors set *@obj to the value; other visitors will leave
+ * *@obj unchanged.
+ */
 void visit_type_any(Visitor *v, QObject **obj, const char *name, Error **errp);
+
+/**
+ * Mark the start of visiting the branches of a union. Return true if
+ * @data_present.
+ * FIXME: Should not be needed
+ */
 bool visit_start_union(Visitor *v, bool data_present, Error **errp);
+/**
+ * Mark the end of union branches, after visit_start_union().
+ * FIXME: Should not be needed
+ */
 void visit_end_union(Visitor *v, bool data_present, Error **errp);

 #endif
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 25/46] qapi: Plug leaks in test-qmp-input-visitor
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (23 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 24/46] qapi: Document visitor interfaces Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 26/46] qapi: Test failure in middle of array parse Eric Blake
                   ` (21 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Make valgrind happy with the current state of the test, so that
it is easier to see if future patches introduce new memory
problems without being drowned in noise.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 tests/test-qmp-input-visitor.c | 34 +++++++++++++++++++++++++++++++---
 1 file changed, 31 insertions(+), 3 deletions(-)

diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index 54be9b4..0c236f0 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -98,6 +98,7 @@ static void test_visitor_in_int(TestInputVisitorData *data,
     visit_type_int(v, &res, NULL, &err);
     g_assert(!err);
     g_assert_cmpint(res, ==, value);
+    visitor_input_teardown(data, NULL);
 }

 static void test_visitor_in_int_overflow(TestInputVisitorData *data,
@@ -116,6 +117,7 @@ static void test_visitor_in_int_overflow(TestInputVisitorData *data,
     visit_type_int(v, &res, NULL, &err);
     g_assert(err);
     error_free(err);
+    visitor_input_teardown(data, NULL);
 }

 static void test_visitor_in_bool(TestInputVisitorData *data,
@@ -130,6 +132,7 @@ static void test_visitor_in_bool(TestInputVisitorData *data,
     visit_type_bool(v, &res, NULL, &err);
     g_assert(!err);
     g_assert_cmpint(res, ==, true);
+    visitor_input_teardown(data, NULL);
 }

 static void test_visitor_in_number(TestInputVisitorData *data,
@@ -144,6 +147,7 @@ static void test_visitor_in_number(TestInputVisitorData *data,
     visit_type_number(v, &res, NULL, &err);
     g_assert(!err);
     g_assert_cmpfloat(res, ==, value);
+    visitor_input_teardown(data, NULL);
 }

 static void test_visitor_in_string(TestInputVisitorData *data,
@@ -160,6 +164,7 @@ static void test_visitor_in_string(TestInputVisitorData *data,
     g_assert_cmpstr(res, ==, value);

     g_free(res);
+    visitor_input_teardown(data, NULL);
 }

 static void test_visitor_in_enum(TestInputVisitorData *data,
@@ -237,6 +242,7 @@ static void test_visitor_in_struct(TestInputVisitorData *data,

     g_free(p->string);
     g_free(p);
+    visitor_input_teardown(data, NULL);
 }

 static void check_and_free_str(char *str, const char *cmp)
@@ -271,6 +277,7 @@ static void test_visitor_in_struct_nested(TestInputVisitorData *data,
     g_free(udp->dict1->dict2);
     g_free(udp->dict1);
     g_free(udp);
+    visitor_input_teardown(data, NULL);
 }

 static void test_visitor_in_list(TestInputVisitorData *data,
@@ -296,6 +303,7 @@ static void test_visitor_in_list(TestInputVisitorData *data,
     }

     qapi_free_UserDefOneList(head);
+    visitor_input_teardown(data, NULL);
 }

 static void test_visitor_in_any(TestInputVisitorData *data,
@@ -317,6 +325,7 @@ static void test_visitor_in_any(TestInputVisitorData *data,
     g_assert(qint);
     g_assert_cmpint(qint_get_int(qint), ==, -42);
     qobject_decref(res);
+    visitor_input_teardown(data, NULL);

     v = visitor_input_test_init(data, "{ 'integer': -42, 'boolean': true, 'string': 'foo' }");
     visit_type_any(v, &res, NULL, &err);
@@ -339,6 +348,7 @@ static void test_visitor_in_any(TestInputVisitorData *data,
     g_assert(qstring);
     g_assert_cmpstr(qstring_get_str(qstring), ==, "foo");
     qobject_decref(res);
+    visitor_input_teardown(data, NULL);
 }

 static void test_visitor_in_union_flat(TestInputVisitorData *data,
@@ -361,6 +371,7 @@ static void test_visitor_in_union_flat(TestInputVisitorData *data,
     g_assert_cmpint(tmp->integer, ==, 41);
     g_assert_cmpint(tmp->value1->boolean, ==, true);
     qapi_free_UserDefFlatUnion(tmp);
+    visitor_input_teardown(data, NULL);
 }

 static void test_visitor_in_alternate(TestInputVisitorData *data,
@@ -371,28 +382,28 @@ static void test_visitor_in_alternate(TestInputVisitorData *data,
     UserDefAlternate *tmp = NULL;

     v = visitor_input_test_init(data, "42");
-
     visit_type_UserDefAlternate(v, &tmp, NULL, &error_abort);
     g_assert_cmpint(tmp->type, ==, QTYPE_QINT);
     g_assert_cmpint(tmp->i, ==, 42);
     qapi_free_UserDefAlternate(tmp);
     tmp = NULL;
+    visitor_input_teardown(data, NULL);

     v = visitor_input_test_init(data, "'string'");
-
     visit_type_UserDefAlternate(v, &tmp, NULL, &error_abort);
     g_assert_cmpint(tmp->type, ==, QTYPE_QSTRING);
     g_assert_cmpstr(tmp->s, ==, "string");
     qapi_free_UserDefAlternate(tmp);
     tmp = NULL;
+    visitor_input_teardown(data, NULL);

     v = visitor_input_test_init(data, "false");
-
     visit_type_UserDefAlternate(v, &tmp, NULL, &err);
     g_assert(err);
     error_free(err);
     err = NULL;
     qapi_free_UserDefAlternate(tmp);
+    visitor_input_teardown(data, NULL);
 }

 static void test_visitor_in_alternate_number(TestInputVisitorData *data,
@@ -414,6 +425,7 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,
     g_assert(err);
     qapi_free_AltOne(one);
     one = NULL;
+    visitor_input_teardown(data, NULL);

     v = visitor_input_test_init(data, "42");
     visit_type_AltTwo(v, &two, NULL, &error_abort);
@@ -421,6 +433,7 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,
     g_assert_cmpfloat(two->n, ==, 42.0);
     qapi_free_AltTwo(two);
     one = NULL;
+    visitor_input_teardown(data, NULL);

     v = visitor_input_test_init(data, "42");
     visit_type_AltThree(v, &three, NULL, &error_abort);
@@ -428,6 +441,7 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,
     g_assert_cmpfloat(three->n, ==, 42.0);
     qapi_free_AltThree(three);
     one = NULL;
+    visitor_input_teardown(data, NULL);

     v = visitor_input_test_init(data, "42");
     visit_type_AltFour(v, &four, NULL, &error_abort);
@@ -435,6 +449,7 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,
     g_assert_cmpint(four->i, ==, 42);
     qapi_free_AltFour(four);
     one = NULL;
+    visitor_input_teardown(data, NULL);

     v = visitor_input_test_init(data, "42");
     visit_type_AltFive(v, &five, NULL, &error_abort);
@@ -442,6 +457,7 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,
     g_assert_cmpint(five->i, ==, 42);
     qapi_free_AltFive(five);
     one = NULL;
+    visitor_input_teardown(data, NULL);

     v = visitor_input_test_init(data, "42");
     visit_type_AltSix(v, &six, NULL, &error_abort);
@@ -449,6 +465,7 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,
     g_assert_cmpint(six->i, ==, 42);
     qapi_free_AltSix(six);
     one = NULL;
+    visitor_input_teardown(data, NULL);

     /* Parsing a double */

@@ -459,6 +476,7 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,
     err = NULL;
     qapi_free_AltOne(one);
     one = NULL;
+    visitor_input_teardown(data, NULL);

     v = visitor_input_test_init(data, "42.5");
     visit_type_AltTwo(v, &two, NULL, &error_abort);
@@ -466,6 +484,7 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,
     g_assert_cmpfloat(two->n, ==, 42.5);
     qapi_free_AltTwo(two);
     two = NULL;
+    visitor_input_teardown(data, NULL);

     v = visitor_input_test_init(data, "42.5");
     visit_type_AltThree(v, &three, NULL, &error_abort);
@@ -473,6 +492,7 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,
     g_assert_cmpfloat(three->n, ==, 42.5);
     qapi_free_AltThree(three);
     three = NULL;
+    visitor_input_teardown(data, NULL);

     v = visitor_input_test_init(data, "42.5");
     visit_type_AltFour(v, &four, NULL, &err);
@@ -481,6 +501,7 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,
     err = NULL;
     qapi_free_AltFour(four);
     four = NULL;
+    visitor_input_teardown(data, NULL);

     v = visitor_input_test_init(data, "42.5");
     visit_type_AltFive(v, &five, NULL, &error_abort);
@@ -488,6 +509,7 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,
     g_assert_cmpfloat(five->n, ==, 42.5);
     qapi_free_AltFive(five);
     five = NULL;
+    visitor_input_teardown(data, NULL);

     v = visitor_input_test_init(data, "42.5");
     visit_type_AltSix(v, &six, NULL, &error_abort);
@@ -495,6 +517,7 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,
     g_assert_cmpint(six->n, ==, 42.5);
     qapi_free_AltSix(six);
     six = NULL;
+    visitor_input_teardown(data, NULL);
 }

 static void test_native_list_integer_helper(TestInputVisitorData *data,
@@ -595,6 +618,7 @@ static void test_native_list_integer_helper(TestInputVisitorData *data,
     g_string_free(gstr_union, true);
     g_string_free(gstr_list, true);
     qapi_free_UserDefNativeListUnion(cvalue);
+    visitor_input_teardown(data, NULL);
 }

 static void test_visitor_in_native_list_int(TestInputVisitorData *data,
@@ -694,6 +718,7 @@ static void test_visitor_in_native_list_bool(TestInputVisitorData *data,
     g_string_free(gstr_union, true);
     g_string_free(gstr_list, true);
     qapi_free_UserDefNativeListUnion(cvalue);
+    visitor_input_teardown(data, NULL);
 }

 static void test_visitor_in_native_list_string(TestInputVisitorData *data,
@@ -731,6 +756,7 @@ static void test_visitor_in_native_list_string(TestInputVisitorData *data,
     g_string_free(gstr_union, true);
     g_string_free(gstr_list, true);
     qapi_free_UserDefNativeListUnion(cvalue);
+    visitor_input_teardown(data, NULL);
 }

 #define DOUBLE_STR_MAX 16
@@ -776,6 +802,7 @@ static void test_visitor_in_native_list_number(TestInputVisitorData *data,
     g_string_free(gstr_union, true);
     g_string_free(gstr_list, true);
     qapi_free_UserDefNativeListUnion(cvalue);
+    visitor_input_teardown(data, NULL);
 }

 static void input_visitor_test_add(const char *testpath,
@@ -804,6 +831,7 @@ static void test_visitor_in_errors(TestInputVisitorData *data,
     error_free(err);
     g_free(p->string);
     g_free(p);
+    visitor_input_teardown(data, NULL);
 }

 int main(int argc, char **argv)
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 26/46] qapi: Test failure in middle of array parse
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (24 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 25/46] qapi: Plug leaks in test-qmp-input-visitor Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 27/46] qapi: Simplify visits of optional fields Eric Blake
                   ` (20 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Our generated list visitors have the same problem as has been
mentioned elsewhere (see commit 2f52e20): they allocate data
even on failure. An upcoming patch will correct things to
provide saner guarantees, but first we need to expose the
behavior in the testsuite to ensure we aren't introducing any
memory usage bugs.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi-visit.py          |  4 ++++
 tests/test-qmp-input-visitor.c | 13 +++++++++++++
 2 files changed, 17 insertions(+)

diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 6ca7c7c..55f6430 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -132,6 +132,10 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error


 def gen_visit_list(name, element_type):
+    # FIXME: if *obj is NULL on entry, and the first visit_next_list()
+    # assigns to *obj, while a later one fails, we should clean up *obj
+    # rather than leaving it non-NULL. As currently written, the caller must
+    # call qapi_free_FOOList() to avoid a memory leak of the partial FOOList.
     return mcgen('''

 void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error **errp)
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index 0c236f0..667301c 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -819,6 +819,7 @@ static void test_visitor_in_errors(TestInputVisitorData *data,
     TestStruct *p = NULL;
     Error *err = NULL;
     Visitor *v;
+    strList *q = NULL;

     v = visitor_input_test_init(data, "{ 'integer': false, 'boolean': 'foo', 'string': -42 }");

@@ -829,9 +830,21 @@ static void test_visitor_in_errors(TestInputVisitorData *data,
     g_assert(p->string == NULL);

     error_free(err);
+    err = NULL;
     g_free(p->string);
     g_free(p);
     visitor_input_teardown(data, NULL);
+
+    v = visitor_input_test_init(data, "[ '1', '2', false, '3' ]");
+    /* FIXME - a failed parse should not leave a partially-allocated
+     * array for us to clean up; this could cause callers to leak
+     * memory. */
+    visit_type_strList(v, &q, NULL, &err);
+    assert(q);
+    assert(err);
+    qapi_free_strList(q);
+    error_free(err);
+    visitor_input_teardown(data, NULL);
 }

 int main(int argc, char **argv)
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 27/46] qapi: Simplify visits of optional fields
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (25 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 26/46] qapi: Test failure in middle of array parse Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 28/46] qapi: Rework deallocation of partial struct Eric Blake
                   ` (19 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

None of the visitor callbacks would set an error when testing
if an optional field was present; make this part of the interface
contract by eliminating the errp argument.  Then, for less code,
reflect the determined boolean value back to the caller instead
of making the caller read the boolean after the fact.

The resulting generated code has a nice diff:

|-    visit_optional(v, &has_fdset_id, "fdset-id", &err);
|-    if (err) {
|-        goto out;
|-    }
|-    if (has_fdset_id) {
|+    if (visit_optional(v, &has_fdset_id, "fdset-id")) {
|         visit_type_int(v, &fdset_id, "fdset-id", &err);
|         if (err) {
|             goto out;
|         }
|     }

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 include/qapi/visitor-impl.h | 3 +--
 include/qapi/visitor.h      | 5 ++---
 qapi/opts-visitor.c         | 2 +-
 qapi/qapi-visit-core.c      | 6 +++---
 qapi/qmp-input-visitor.c    | 3 +--
 qapi/string-input-visitor.c | 3 +--
 scripts/qapi.py             | 9 ++-------
 7 files changed, 11 insertions(+), 20 deletions(-)

diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
index 090b19b..590b067 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -69,8 +69,7 @@ struct Visitor
                      Error **errp);

     /* May be NULL; most useful for input visitors. */
-    void (*optional)(Visitor *v, bool *present, const char *name,
-                     Error **errp);
+    void (*optional)(Visitor *v, bool *present, const char *name);

     /* FIXME - needs to be removed */
     bool (*start_union)(Visitor *v, bool data_present, Error **errp);
diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index 2883cf4..2deeb7f 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -107,10 +107,9 @@ void visit_end_list(Visitor *v, Error **errp);
  * Check if an optional member @name of a QDict needs visiting.
  * For input visitors, set *@present according to whether the
  * corresponding visit_type_*() needs calling; for other visitors,
- * leave *@present unchanged.
+ * leave *@present unchanged.  Return *@present for convenience.
  */
-void visit_optional(Visitor *v, bool *present, const char *name,
-                    Error **errp);
+bool visit_optional(Visitor *v, bool *present, const char *name);

 /**
  * Determine the qtype of the item @name in the current QDict visit.
diff --git a/qapi/opts-visitor.c b/qapi/opts-visitor.c
index 7ae33b3..b2cd5b3 100644
--- a/qapi/opts-visitor.c
+++ b/qapi/opts-visitor.c
@@ -488,7 +488,7 @@ opts_type_size(Visitor *v, uint64_t *obj, const char *name, Error **errp)


 static void
-opts_optional(Visitor *v, bool *present, const char *name, Error **errp)
+opts_optional(Visitor *v, bool *present, const char *name)
 {
     OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);

diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index cbf7780..2594147 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -73,12 +73,12 @@ void visit_end_union(Visitor *v, bool data_present, Error **errp)
     }
 }

-void visit_optional(Visitor *v, bool *present, const char *name,
-                    Error **errp)
+bool visit_optional(Visitor *v, bool *present, const char *name)
 {
     if (v->optional) {
-        v->optional(v, present, name, errp);
+        v->optional(v, present, name);
     }
+    return *present;
 }

 void visit_get_next_type(Visitor *v, qtype_code *type, bool promote_int,
diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index 5310db5..f714dfc 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -300,8 +300,7 @@ static void qmp_input_type_any(Visitor *v, QObject **obj, const char *name,
     *obj = qobj;
 }

-static void qmp_input_optional(Visitor *v, bool *present, const char *name,
-                               Error **errp)
+static void qmp_input_optional(Visitor *v, bool *present, const char *name)
 {
     QmpInputVisitor *qiv = to_qiv(v);
     QObject *qobj = qmp_input_get_object(qiv, name, true);
diff --git a/qapi/string-input-visitor.c b/qapi/string-input-visitor.c
index bbd6a54..dee780a 100644
--- a/qapi/string-input-visitor.c
+++ b/qapi/string-input-visitor.c
@@ -299,8 +299,7 @@ static void parse_type_number(Visitor *v, double *obj, const char *name,
     *obj = val;
 }

-static void parse_optional(Visitor *v, bool *present, const char *name,
-                           Error **errp)
+static void parse_optional(Visitor *v, bool *present, const char *name)
 {
     StringInputVisitor *siv = DO_UPCAST(StringInputVisitor, visitor, v);

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 46c55a9..a91bdf2 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -1580,15 +1580,10 @@ def gen_visit_fields(members, prefix, need_cast, errarg):
     for memb in members:
         if memb.optional:
             ret += mcgen('''
-visit_optional(v, &%(prefix)shas_%(c_name)s, "%(name)s", %(errp)s);
+if (visit_optional(v, &%(prefix)shas_%(c_name)s, "%(name)s")) {
 ''',
                          prefix=prefix, c_name=c_name(memb.name),
-                         name=memb.name, errp=errparg)
-            ret += gen_err_check(errarg)
-            ret += mcgen('''
-if (%(prefix)shas_%(c_name)s) {
-''',
-                         prefix=prefix, c_name=c_name(memb.name))
+                         name=memb.name)
             push_indent()

         # Ugly: sometimes we need to cast away const
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 28/46] qapi: Rework deallocation of partial struct
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (26 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 27/46] qapi: Simplify visits of optional fields Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 29/46] qapi: Change visit_type_FOO() to no longer return partial objects Eric Blake
                   ` (18 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Commit cee2dedb noticed that if you have a partial flat union
(such as if an input parse failed due to a missing
discriminator), calling the dealloc visitor could result in
trying to dereference the NULL pointer. But the fix it proposed
requires the use of a 'data' member in the union, which may or
may not be the same size as other branches of the union
(consider a 32-bit platform where one of the branches is an
int64), so it feels fairly dirty.  A better, and much shorter,
fix is to tweak all of the generated visit_type_implicit_FOO()
functions to avoid dereferencing NULL in the first place, to
not visit the fields if the struct pointer itself is not present,
at which point we no longer even need visit_start_union(), and
no one was implementing visit_end_union() callbacks. The change
keeps the contract that any successful use of
visit_start_implicit_struct() will be paired with a matching
visit_end_implicit_struct(), even if intermediate processing is
skipped.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 include/qapi/visitor-impl.h |  5 -----
 include/qapi/visitor.h      | 12 ------------
 qapi/qapi-dealloc-visitor.c | 26 --------------------------
 qapi/qapi-visit-core.c      | 15 ---------------
 scripts/qapi-visit.py       | 10 +++-------
 5 files changed, 3 insertions(+), 65 deletions(-)

diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
index 590b067..b87d9a3 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -71,11 +71,6 @@ struct Visitor
     /* May be NULL; most useful for input visitors. */
     void (*optional)(Visitor *v, bool *present, const char *name);

-    /* FIXME - needs to be removed */
-    bool (*start_union)(Visitor *v, bool data_present, Error **errp);
-    /* FIXME - needs to be removed */
-    void (*end_union)(Visitor *v, bool data_present, Error **errp);
-
     /* Only required to visit uint64 differently than (*type_int)().  */
     void (*type_uint64)(Visitor *v, uint64_t *obj, const char *name,
                         Error **errp);
diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index 2deeb7f..284a978 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -230,16 +230,4 @@ void visit_type_number(Visitor *v, double *obj, const char *name,
  */
 void visit_type_any(Visitor *v, QObject **obj, const char *name, Error **errp);

-/**
- * Mark the start of visiting the branches of a union. Return true if
- * @data_present.
- * FIXME: Should not be needed
- */
-bool visit_start_union(Visitor *v, bool data_present, Error **errp);
-/**
- * Mark the end of union branches, after visit_start_union().
- * FIXME: Should not be needed
- */
-void visit_end_union(Visitor *v, bool data_present, Error **errp);
-
 #endif
diff --git a/qapi/qapi-dealloc-visitor.c b/qapi/qapi-dealloc-visitor.c
index 737deab..4989f50 100644
--- a/qapi/qapi-dealloc-visitor.c
+++ b/qapi/qapi-dealloc-visitor.c
@@ -171,31 +171,6 @@ static void qapi_dealloc_type_enum(Visitor *v, int *obj,
 {
 }

-/* If there's no data present, the dealloc visitor has nothing to free.
- * Thus, indicate to visitor code that the subsequent union fields can
- * be skipped. This is not an error condition, since the cleanup of the
- * rest of an object can continue unhindered, so leave errp unset in
- * these cases.
- *
- * NOTE: In cases where we're attempting to deallocate an object that
- * may have missing fields, the field indicating the union type may
- * be missing. In such a case, it's possible we don't have enough
- * information to differentiate data_present == false from a case where
- * data *is* present but happens to be a scalar with a value of 0.
- * This is okay, since in the case of the dealloc visitor there's no
- * work that needs to done in either situation.
- *
- * The current inability in QAPI code to more thoroughly verify a union
- * type in such cases will likely need to be addressed if we wish to
- * implement this interface for other types of visitors in the future,
- * however.
- */
-static bool qapi_dealloc_start_union(Visitor *v, bool data_present,
-                                     Error **errp)
-{
-    return data_present;
-}
-
 Visitor *qapi_dealloc_get_visitor(QapiDeallocVisitor *v)
 {
     return &v->visitor;
@@ -226,7 +201,6 @@ QapiDeallocVisitor *qapi_dealloc_visitor_new(void)
     v->visitor.type_number = qapi_dealloc_type_number;
     v->visitor.type_any = qapi_dealloc_type_anything;
     v->visitor.type_size = qapi_dealloc_type_size;
-    v->visitor.start_union = qapi_dealloc_start_union;

     QTAILQ_INIT(&v->stack);

diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index 2594147..020ea34 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -58,21 +58,6 @@ void visit_end_list(Visitor *v, Error **errp)
     v->end_list(v, errp);
 }

-bool visit_start_union(Visitor *v, bool data_present, Error **errp)
-{
-    if (v->start_union) {
-        return v->start_union(v, data_present, errp);
-    }
-    return true;
-}
-
-void visit_end_union(Visitor *v, bool data_present, Error **errp)
-{
-    if (v->end_union) {
-        v->end_union(v, data_present, errp);
-    }
-}
-
 bool visit_optional(Visitor *v, bool *present, const char *name)
 {
     if (v->optional) {
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 55f6430..d94b34e 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -51,7 +51,9 @@ static void visit_type_implicit_%(c_type)s(Visitor *v, %(c_type)s **obj, Error *

     visit_start_implicit_struct(v, (void **)obj, sizeof(%(c_type)s), &err);
     if (!err) {
-        visit_type_%(c_type)s_fields(v, obj, &err);
+        if (!obj || *obj) {
+            visit_type_%(c_type)s_fields(v, obj, &err);
+        }
         visit_end_implicit_struct(v, err ? NULL : &err);
     }
     error_propagate(errp, err);
@@ -270,9 +272,6 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
     if (err) {
         goto out_obj;
     }
-    if (!visit_start_union(v, !!(*obj)->data, &err) || err) {
-        goto out_obj;
-    }
     switch ((*obj)->%(c_name)s) {
 ''',
                  c_type=variants.tag_member.type.c_name(),
@@ -311,9 +310,6 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
         abort();
     }
 out_obj:
-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_union(v, !!(*obj)->data, &err);
     visit_end_struct(v, err ? NULL : &err);
 out:
     error_propagate(errp, err);
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 29/46] qapi: Change visit_type_FOO() to no longer return partial objects
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (27 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 28/46] qapi: Rework deallocation of partial struct Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 30/46] net: use Netdev instead of NetClientOptions in client init Eric Blake
                   ` (17 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Returning a partial object on error is an invitation for a careless
caller to leak memory.  As no one outside the testsuite was actually
relying on these semantics, it is cleaner to just document and
guarantee that ALL visit_type_FOO() functions do not alter *obj
when an error is encountered during an input visitor.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 include/qapi/visitor.h         | 45 +++++++++++++++++++++++++++++------------
 qapi/qapi-visit-core.c         |  8 ++++++--
 scripts/qapi-visit.py          | 46 +++++++++++++++++++++++++++++-------------
 tests/test-qmp-commands.c      | 15 +++++++-------
 tests/test-qmp-input-visitor.c | 29 +++++++++++---------------
 5 files changed, 89 insertions(+), 54 deletions(-)

diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index 284a978..69935fa 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -27,6 +27,26 @@
  * scripts/qapi-visit.py.  For the visitor callback contracts, see
  * visitor-impl.h. */

+/* All qapi types have a corresponding function with a signature
+ * compatible with this:
+ *
+ * void visit_type_FOO(Visitor *v, void *obj, const char *name, Error **errp);
+ *
+ * where *@obj is itself a pointer or a scalar.  (The visit functions for
+ * built-in types are declared here, while the functions for qapi-defined
+ * struct, union, enum, and list types are generated; see qapi-visit.h).
+ * Input visitors populate *@obj on success, and leave it unchanged on
+ * failure.
+ *
+ * Additionally, all qapi structs have a generated function compatible
+ * with this:
+ *
+ * void qapi_free_FOO(void *obj);
+ *
+ * which behaves like free(), even if @obj is NULL or was only partially
+ * allocated before encountering an error.
+ */
+
 /* This struct is layout-compatible with all other *List structs
  * created by the qapi generator. */
 typedef struct GenericList
@@ -46,12 +66,12 @@ typedef struct GenericList
  * input visitor, @obj can be NULL to validate that the visit will
  * succeed; otherwise, *@obj is assigned with an allocation of @size
  * bytes. For other visitors, *@obj is the object to visit. Set *@errp
- * on failure.
- *
- * FIXME: *@obj can be modified even on error; this can lead to
- * memory leaks if clients aren't careful.
+ * on failure.  Returns true if *@obj was allocated; if that happens,
+ * and an error occurs any time before the matching visit_end_struct(),
+ * then the caller (usually a visit_type_FOO() function) knows to undo
+ * the allocation before returning control further.
  */
-void visit_start_struct(Visitor *v, void **obj, const char *kind,
+bool visit_start_struct(Visitor *v, void **obj, const char *kind,
                         const char *name, size_t size, Error **errp);
 /**
  * Complete a struct started earlier.
@@ -62,14 +82,11 @@ void visit_end_struct(Visitor *v, Error **errp);

 /**
  * Prepare to visit an implicit struct.
- * Similar to visit_start_struct(), except that this will visit a
- * C pointer pointing to @size bytes, and where the QDict fields are
- * part of the parent object.
- *
- * FIXME: *@obj can be modified even on error; this can lead to
- * memory leaks if clients aren't careful.
+ * Similar to visit_start_struct(), including return semantics, except
+ * that this will visit a C pointer pointing to @size bytes, and where
+ * the QDict fields are part of the parent object.
  */
-void visit_start_implicit_struct(Visitor *v, void **obj, size_t size,
+bool visit_start_implicit_struct(Visitor *v, void **obj, size_t size,
                                  Error **errp);
 /**
  * Complete an implicit struct started earlier.
@@ -93,7 +110,9 @@ void visit_start_list(Visitor *v, const char *name, Error **errp);
  * loop until a NULL return or error occurs; for each non-NULL return,
  * the caller must then call the appropriate visit_type_*() for the
  * element type of the list, with that function's name parameter set
- * to NULL.
+ * to NULL.  If an error occurs, then the caller (usually a
+ * visit_type_FOO() function) knows to undo the list allocat04ion before
+ * returning control further.
  */
 GenericList *visit_next_list(Visitor *v, GenericList **list, Error **errp);
 /**
diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index 020ea34..d885347 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -17,10 +17,12 @@
 #include "qapi/visitor.h"
 #include "qapi/visitor-impl.h"

-void visit_start_struct(Visitor *v, void **obj, const char *kind,
+bool visit_start_struct(Visitor *v, void **obj, const char *kind,
                         const char *name, size_t size, Error **errp)
 {
+    bool track_allocation = obj && !*obj;
     v->start_struct(v, obj, kind, name, size, errp);
+    return track_allocation && *obj;
 }

 void visit_end_struct(Visitor *v, Error **errp)
@@ -28,12 +30,14 @@ void visit_end_struct(Visitor *v, Error **errp)
     v->end_struct(v, errp);
 }

-void visit_start_implicit_struct(Visitor *v, void **obj, size_t size,
+bool visit_start_implicit_struct(Visitor *v, void **obj, size_t size,
                                  Error **errp)
 {
+    bool track_allocation = obj && !*obj;
     if (v->start_implicit_struct) {
         v->start_implicit_struct(v, obj, size, errp);
     }
+    return track_allocation && *obj;
 }

 void visit_end_implicit_struct(Visitor *v, Error **errp)
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index d94b34e..898a32b 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -48,14 +48,19 @@ static void visit_type_%(c_type)s_fields(Visitor *v, %(c_type)s **obj, Error **e
 static void visit_type_implicit_%(c_type)s(Visitor *v, %(c_type)s **obj, Error **errp)
 {
     Error *err = NULL;
+    bool allocated;

-    visit_start_implicit_struct(v, (void **)obj, sizeof(%(c_type)s), &err);
+    allocated = visit_start_implicit_struct(v, (void **)obj, sizeof(%(c_type)s), &err);
     if (!err) {
         if (!obj || *obj) {
             visit_type_%(c_type)s_fields(v, obj, &err);
         }
         visit_end_implicit_struct(v, err ? NULL : &err);
     }
+    if (allocated && err) {
+        g_free(*obj);
+        *obj = NULL;
+    }
     error_propagate(errp, err);
 }
 ''',
@@ -108,23 +113,24 @@ out:
 def gen_visit_struct(name, base, members):
     ret = gen_visit_struct_fields(name, base, members)

-    # FIXME: if *obj is NULL on entry, and visit_start_struct() assigns to
-    # *obj, but then visit_type_FOO_fields() fails, we should clean up *obj
-    # rather than leaving it non-NULL. As currently written, the caller must
-    # call qapi_free_FOO() to avoid a memory leak of the partial FOO.
     ret += mcgen('''

 void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error **errp)
 {
     Error *err = NULL;
+    bool allocated;

-    visit_start_struct(v, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err);
+    allocated = visit_start_struct(v, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err);
     if (!err) {
         if (*obj) {
             visit_type_%(c_name)s_fields(v, obj, &err);
         }
         visit_end_struct(v, err ? NULL : &err);
     }
+    if (allocated && err) {
+        qapi_free_%(c_name)s(*obj);
+        *obj = NULL;
+    }
     error_propagate(errp, err);
 }
 ''',
@@ -134,16 +140,13 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error


 def gen_visit_list(name, element_type):
-    # FIXME: if *obj is NULL on entry, and the first visit_next_list()
-    # assigns to *obj, while a later one fails, we should clean up *obj
-    # rather than leaving it non-NULL. As currently written, the caller must
-    # call qapi_free_FOOList() to avoid a memory leak of the partial FOOList.
     return mcgen('''

 void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error **errp)
 {
     Error *err = NULL;
     GenericList *i, **prev;
+    bool allocated = obj && !*obj;

     visit_start_list(v, name, &err);
     if (err) {
@@ -159,6 +162,10 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error

     visit_end_list(v, err ? NULL : &err);
 out:
+    if (allocated && err) {
+        qapi_free_%(c_name)s(*obj);
+        *obj = NULL;
+    }
     error_propagate(errp, err);
 }
 ''',
@@ -187,8 +194,9 @@ def gen_visit_alternate(name, variants):
 void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error **errp)
 {
     Error *err = NULL;
+    bool allocated;

-    visit_start_implicit_struct(v, (void**) obj, sizeof(%(c_name)s), &err);
+    allocated = visit_start_implicit_struct(v, (void **)obj, sizeof(%(c_name)s), &err);
     if (err) {
         goto out;
     }
@@ -217,11 +225,15 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
     }
 out_obj:
     visit_end_implicit_struct(v, err ? NULL : &err);
+    if (allocated && err) {
+        qapi_free_%(c_name)s(*obj);
+        *obj = NULL;
+    }
 out:
     error_propagate(errp, err);
 }
 ''',
-                 name=name)
+                 name=name, c_name=c_name(name))

     return ret

@@ -243,8 +255,9 @@ def gen_visit_union(name, base, variants):
 void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error **errp)
 {
     Error *err = NULL;
+    bool allocated;

-    visit_start_struct(v, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err);
+    allocated = visit_start_struct(v, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err);
     if (err) {
         goto out;
     }
@@ -311,10 +324,15 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
     }
 out_obj:
     visit_end_struct(v, err ? NULL : &err);
+    if (allocated && err) {
+        qapi_free_%(c_name)s(*obj);
+        *obj = NULL;
+    }
 out:
     error_propagate(errp, err);
 }
-''')
+''',
+                 c_name=c_name(name))

     return ret

diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c
index 8d5249e..681ec65 100644
--- a/tests/test-qmp-commands.c
+++ b/tests/test-qmp-commands.c
@@ -223,15 +223,14 @@ static void test_dealloc_partial(void)
         QDECREF(ud2_dict);
     }

-    /* verify partial success */
-    assert(ud2 != NULL);
-    assert(ud2->string0 != NULL);
-    assert(strcmp(ud2->string0, text) == 0);
-    assert(ud2->dict1 == NULL);
-
-    /* confirm & release construction error */
-    assert(err != NULL);
+    /* verify that visit_type_XXX() cleans up properly on error */
+    assert(err);
     error_free(err);
+    assert(!ud2);
+
+    /* Manually create a partial object, leaving ud2->dict1 at NULL */
+    ud2 = g_new0(UserDefTwo, 1);
+    ud2->string0 = g_strdup(text);

     /* tear down partial object */
     qapi_free_UserDefTwo(ud2);
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index 667301c..bc9ed04 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -201,9 +201,10 @@ static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
                                   const char *name, Error **errp)
 {
     Error *err = NULL;
+    bool allocated;

-    visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct),
-                       &err);
+    allocated = visit_start_struct(v, (void **)obj, "TestStruct", name,
+                                   sizeof(TestStruct), &err);
     if (err) {
         goto out;
     }
@@ -218,9 +219,12 @@ static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
     visit_type_str(v, &(*obj)->string, "string", &err);

 out_end:
-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_struct(v, &err);
+    visit_end_struct(v, err ? NULL : &err);
+    if (allocated && err) {
+        g_free((*obj)->string);
+        g_free(*obj);
+        *obj = NULL;
+    }
 out:
     error_propagate(errp, err);
 }
@@ -825,24 +829,15 @@ static void test_visitor_in_errors(TestInputVisitorData *data,

     visit_type_TestStruct(v, &p, NULL, &err);
     g_assert(err);
-    /* FIXME - a failed parse should not leave a partially-allocated p
-     * for us to clean up; this could cause callers to leak memory. */
-    g_assert(p->string == NULL);
-
+    g_assert(!p);
     error_free(err);
     err = NULL;
-    g_free(p->string);
-    g_free(p);
     visitor_input_teardown(data, NULL);

     v = visitor_input_test_init(data, "[ '1', '2', false, '3' ]");
-    /* FIXME - a failed parse should not leave a partially-allocated
-     * array for us to clean up; this could cause callers to leak
-     * memory. */
     visit_type_strList(v, &q, NULL, &err);
-    assert(q);
-    assert(err);
-    qapi_free_strList(q);
+    g_assert(!q);
+    g_assert(err);
     error_free(err);
     visitor_input_teardown(data, NULL);
 }
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 30/46] net: use Netdev instead of NetClientOptions in client init
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (28 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 29/46] qapi: Change visit_type_FOO() to no longer return partial objects Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 31/46] qapi: use 'type' in generated C code to match QMP union wire form Eric Blake
                   ` (16 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel
  Cc: ehabkost, Michael S. Tsirkin, Jason Wang, armbru,
	Vincenzo Maffione, Kővágó, Zoltán,
	marcandre.lureau, Giuseppe Lettieri, Luigi Rizzo

From: Kővágó, Zoltán <dirty.ice.hu@gmail.com>

This way we no longer need NetClientOptions and can convert Netdev
into a flat union.

Signed-off-by: Kővágó, Zoltán <DirtY.iCE.hu@gmail.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-Id: <93ffdfed7054529635e6acb935150d95dc173a12.1441627176.git.DirtY.iCE.hu@gmail.com>

[rework net_client_init1() to pass Netdev by copying from NetdevLegacy,
rather than merging the two types.]
Signed-off-by: Eric Blake <eblake@redhat.com>
---
 net/clients.h    | 20 ++++++++++----------
 net/dump.c       |  6 +++---
 net/hub.c        |  6 +++---
 net/l2tpv3.c     |  6 +++---
 net/net.c        | 18 +++++++++++-------
 net/netmap.c     |  4 ++--
 net/slirp.c      |  6 +++---
 net/socket.c     |  6 +++---
 net/tap-win32.c  |  6 +++---
 net/tap.c        | 12 ++++++------
 net/vde.c        |  6 +++---
 net/vhost-user.c |  6 +++---
 12 files changed, 53 insertions(+), 49 deletions(-)

diff --git a/net/clients.h b/net/clients.h
index d47530e..5cae479 100644
--- a/net/clients.h
+++ b/net/clients.h
@@ -27,39 +27,39 @@
 #include "net/net.h"
 #include "qapi-types.h"

-int net_init_dump(const NetClientOptions *opts, const char *name,
+int net_init_dump(const Netdev *netdev, const char *name,
                   NetClientState *peer, Error **errp);

 #ifdef CONFIG_SLIRP
-int net_init_slirp(const NetClientOptions *opts, const char *name,
+int net_init_slirp(const Netdev *netdev, const char *name,
                    NetClientState *peer, Error **errp);
 #endif

-int net_init_hubport(const NetClientOptions *opts, const char *name,
+int net_init_hubport(const Netdev *netdev, const char *name,
                      NetClientState *peer, Error **errp);

-int net_init_socket(const NetClientOptions *opts, const char *name,
+int net_init_socket(const Netdev *netdev, const char *name,
                     NetClientState *peer, Error **errp);

-int net_init_tap(const NetClientOptions *opts, const char *name,
+int net_init_tap(const Netdev *netdev, const char *name,
                  NetClientState *peer, Error **errp);

-int net_init_bridge(const NetClientOptions *opts, const char *name,
+int net_init_bridge(const Netdev *netdev, const char *name,
                     NetClientState *peer, Error **errp);

-int net_init_l2tpv3(const NetClientOptions *opts, const char *name,
+int net_init_l2tpv3(const Netdev *netdev, const char *name,
                     NetClientState *peer, Error **errp);
 #ifdef CONFIG_VDE
-int net_init_vde(const NetClientOptions *opts, const char *name,
+int net_init_vde(const Netdev *netdev, const char *name,
                  NetClientState *peer, Error **errp);
 #endif

 #ifdef CONFIG_NETMAP
-int net_init_netmap(const NetClientOptions *opts, const char *name,
+int net_init_netmap(const Netdev *netdev, const char *name,
                     NetClientState *peer, Error **errp);
 #endif

-int net_init_vhost_user(const NetClientOptions *opts, const char *name,
+int net_init_vhost_user(const Netdev *netdev, const char *name,
                         NetClientState *peer, Error **errp);

 #endif /* QEMU_NET_CLIENTS_H */
diff --git a/net/dump.c b/net/dump.c
index 02c8064..d80fa94 100644
--- a/net/dump.c
+++ b/net/dump.c
@@ -146,7 +146,7 @@ static int net_dump_init(NetClientState *peer, const char *device,
     return 0;
 }

-int net_init_dump(const NetClientOptions *opts, const char *name,
+int net_init_dump(const Netdev *netdev, const char *name,
                   NetClientState *peer, Error **errp)
 {
     int len;
@@ -154,8 +154,8 @@ int net_init_dump(const NetClientOptions *opts, const char *name,
     char def_file[128];
     const NetdevDumpOptions *dump;

-    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_DUMP);
-    dump = opts->dump;
+    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_DUMP);
+    dump = netdev->opts->dump;

     assert(peer);

diff --git a/net/hub.c b/net/hub.c
index 3047f12..29f65b2 100644
--- a/net/hub.c
+++ b/net/hub.c
@@ -280,14 +280,14 @@ int net_hub_id_for_client(NetClientState *nc, int *id)
     return 0;
 }

-int net_init_hubport(const NetClientOptions *opts, const char *name,
+int net_init_hubport(const Netdev *netdev, const char *name,
                      NetClientState *peer, Error **errp)
 {
     const NetdevHubPortOptions *hubport;

-    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_HUBPORT);
+    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_HUBPORT);
     assert(!peer);
-    hubport = opts->hubport;
+    hubport = netdev->opts->hubport;

     net_hub_add_port(hubport->hubid, name);
     return 0;
diff --git a/net/l2tpv3.c b/net/l2tpv3.c
index 4f9bcee..d2f8431 100644
--- a/net/l2tpv3.c
+++ b/net/l2tpv3.c
@@ -524,7 +524,7 @@ static NetClientInfo net_l2tpv3_info = {
     .cleanup = net_l2tpv3_cleanup,
 };

-int net_init_l2tpv3(const NetClientOptions *opts,
+int net_init_l2tpv3(const Netdev *netdev,
                     const char *name,
                     NetClientState *peer, Error **errp)
 {
@@ -545,8 +545,8 @@ int net_init_l2tpv3(const NetClientOptions *opts,
     s->queue_tail = 0;
     s->header_mismatch = false;

-    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_L2TPV3);
-    l2tpv3 = opts->l2tpv3;
+    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_L2TPV3);
+    l2tpv3 = netdev->opts->l2tpv3;

     if (l2tpv3->has_ipv6 && l2tpv3->ipv6) {
         s->ipv6 = l2tpv3->ipv6;
diff --git a/net/net.c b/net/net.c
index 28a5597..4d99b48 100644
--- a/net/net.c
+++ b/net/net.c
@@ -813,15 +813,15 @@ int qemu_find_nic_model(NICInfo *nd, const char * const *models,
     return -1;
 }

-static int net_init_nic(const NetClientOptions *opts, const char *name,
+static int net_init_nic(const Netdev *netdev, const char *name,
                         NetClientState *peer, Error **errp)
 {
     int idx;
     NICInfo *nd;
     const NetLegacyNicOptions *nic;

-    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_NIC);
-    nic = opts->nic;
+    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_NIC);
+    nic = netdev->opts->nic;

     idx = nic_get_free_idx();
     if (idx == -1 || nb_nics >= MAX_NICS) {
@@ -882,7 +882,7 @@ static int net_init_nic(const NetClientOptions *opts, const char *name,


 static int (* const net_client_init_fun[NET_CLIENT_OPTIONS_KIND_MAX])(
-    const NetClientOptions *opts,
+    const Netdev *netdev,
     const char *name,
     NetClientState *peer, Error **errp) = {
         [NET_CLIENT_OPTIONS_KIND_NIC]       = net_init_nic,
@@ -914,11 +914,13 @@ static int (* const net_client_init_fun[NET_CLIENT_OPTIONS_KIND_MAX])(
 static int net_client_init1(const void *object, int is_netdev, Error **errp)
 {
     const NetClientOptions *opts;
+    Netdev legacy = {0};
+    const Netdev *netdev;
     const char *name;
     NetClientState *peer = NULL;

     if (is_netdev) {
-        const Netdev *netdev = object;
+        netdev = object;
         opts = netdev->opts;
         name = netdev->id;

@@ -931,7 +933,9 @@ static int net_client_init1(const void *object, int is_netdev, Error **errp)
         }
     } else {
         const NetLegacy *net = object;
-        opts = net->opts;
+        legacy.id = net->id;
+        opts = legacy.opts = net->opts;
+        netdev = &legacy;
         /* missing optional values have been initialized to "all bits zero" */
         name = net->has_id ? net->id : net->name;

@@ -958,7 +962,7 @@ static int net_client_init1(const void *object, int is_netdev, Error **errp)
         }
     }

-    if (net_client_init_fun[opts->kind](opts, name, peer, errp) < 0) {
+    if (net_client_init_fun[opts->kind](netdev, name, peer, errp) < 0) {
         /* FIXME drop when all init functions store an Error */
         if (errp && !*errp) {
             error_setg(errp, QERR_DEVICE_INIT_FAILED,
diff --git a/net/netmap.c b/net/netmap.c
index 508b829..a464618 100644
--- a/net/netmap.c
+++ b/net/netmap.c
@@ -435,11 +435,11 @@ static NetClientInfo net_netmap_info = {
  *
  * ... -net netmap,ifname="..."
  */
-int net_init_netmap(const NetClientOptions *opts,
+int net_init_netmap(const Netdev *netdev,
                     const char *name, NetClientState *peer, Error **errp)
 {
     /* FIXME error_setg(errp, ...) on failure */
-    const NetdevNetmapOptions *netmap_opts = opts->netmap;
+    const NetdevNetmapOptions *netmap_opts = netdev->opts->netmap;
     NetClientState *nc;
     NetmapPriv me;
     NetmapState *s;
diff --git a/net/slirp.c b/net/slirp.c
index 7657b38..0fc2c52 100644
--- a/net/slirp.c
+++ b/net/slirp.c
@@ -736,7 +736,7 @@ static const char **slirp_dnssearch(const StringList *dnsname)
     return ret;
 }

-int net_init_slirp(const NetClientOptions *opts, const char *name,
+int net_init_slirp(const Netdev *netdev, const char *name,
                    NetClientState *peer, Error **errp)
 {
     /* FIXME error_setg(errp, ...) on failure */
@@ -746,8 +746,8 @@ int net_init_slirp(const NetClientOptions *opts, const char *name,
     const NetdevUserOptions *user;
     const char **dnssearch;

-    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_USER);
-    user = opts->user;
+    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_USER);
+    user = netdev->opts->user;

     vnet = user->has_net ? g_strdup(user->net) :
            user->has_ip  ? g_strdup_printf("%s/24", user->ip) :
diff --git a/net/socket.c b/net/socket.c
index b1e3b1c..75f693c 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -699,15 +699,15 @@ static int net_socket_udp_init(NetClientState *peer,
     return 0;
 }

-int net_init_socket(const NetClientOptions *opts, const char *name,
+int net_init_socket(const Netdev *netdev, const char *name,
                     NetClientState *peer, Error **errp)
 {
     /* FIXME error_setg(errp, ...) on failure */
     Error *err = NULL;
     const NetdevSocketOptions *sock;

-    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_SOCKET);
-    sock = opts->socket;
+    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_SOCKET);
+    sock = netdev->opts->socket;

     if (sock->has_fd + sock->has_listen + sock->has_connect + sock->has_mcast +
         sock->has_udp != 1) {
diff --git a/net/tap-win32.c b/net/tap-win32.c
index 625d53c..acce480 100644
--- a/net/tap-win32.c
+++ b/net/tap-win32.c
@@ -761,14 +761,14 @@ static int tap_win32_init(NetClientState *peer, const char *model,
     return 0;
 }

-int net_init_tap(const NetClientOptions *opts, const char *name,
+int net_init_tap(const Netdev *netdev, const char *name,
                  NetClientState *peer, Error **errp)
 {
     /* FIXME error_setg(errp, ...) on failure */
     const NetdevTapOptions *tap;

-    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_TAP);
-    tap = opts->tap;
+    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_TAP);
+    tap = netdev->opts->tap;

     if (!tap->has_ifname) {
         error_report("tap: no interface name");
diff --git a/net/tap.c b/net/tap.c
index bd01590..263f807 100644
--- a/net/tap.c
+++ b/net/tap.c
@@ -557,7 +557,7 @@ static int net_bridge_run_helper(const char *helper, const char *bridge,
     }
 }

-int net_init_bridge(const NetClientOptions *opts, const char *name,
+int net_init_bridge(const Netdev *netdev, const char *name,
                     NetClientState *peer, Error **errp)
 {
     const NetdevBridgeOptions *bridge;
@@ -565,8 +565,8 @@ int net_init_bridge(const NetClientOptions *opts, const char *name,
     TAPState *s;
     int fd, vnet_hdr;

-    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_BRIDGE);
-    bridge = opts->bridge;
+    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_BRIDGE);
+    bridge = netdev->opts->bridge;

     helper = bridge->has_helper ? bridge->helper : DEFAULT_BRIDGE_HELPER;
     br     = bridge->has_br     ? bridge->br     : DEFAULT_BRIDGE_INTERFACE;
@@ -716,7 +716,7 @@ static int get_fds(char *str, char *fds[], int max)
     return i;
 }

-int net_init_tap(const NetClientOptions *opts, const char *name,
+int net_init_tap(const Netdev *netdev, const char *name,
                  NetClientState *peer, Error **errp)
 {
     const NetdevTapOptions *tap;
@@ -728,8 +728,8 @@ int net_init_tap(const NetClientOptions *opts, const char *name,
     const char *vhostfdname;
     char ifname[128];

-    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_TAP);
-    tap = opts->tap;
+    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_TAP);
+    tap = netdev->opts->tap;
     queues = tap->has_queues ? tap->queues : 1;
     vhostfdname = tap->has_vhostfd ? tap->vhostfd : NULL;

diff --git a/net/vde.c b/net/vde.c
index dacaa64..0ac2525 100644
--- a/net/vde.c
+++ b/net/vde.c
@@ -109,14 +109,14 @@ static int net_vde_init(NetClientState *peer, const char *model,
     return 0;
 }

-int net_init_vde(const NetClientOptions *opts, const char *name,
+int net_init_vde(const Netdev *netdev, const char *name,
                  NetClientState *peer, Error **errp)
 {
     /* FIXME error_setg(errp, ...) on failure */
     const NetdevVdeOptions *vde;

-    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_VDE);
-    vde = opts->vde;
+    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_VDE);
+    vde = netdev->opts->vde;

     /* missing optional values have been initialized to "all bits zero" */
     if (net_vde_init(peer, "vde", name, vde->sock, vde->port, vde->group,
diff --git a/net/vhost-user.c b/net/vhost-user.c
index 93dcecd..20981a9 100644
--- a/net/vhost-user.c
+++ b/net/vhost-user.c
@@ -223,14 +223,14 @@ static int net_vhost_check_net(void *opaque, QemuOpts *opts, Error **errp)
     return 0;
 }

-int net_init_vhost_user(const NetClientOptions *opts, const char *name,
+int net_init_vhost_user(const Netdev *netdev, const char *name,
                         NetClientState *peer, Error **errp)
 {
     const NetdevVhostUserOptions *vhost_user_opts;
     CharDriverState *chr;

-    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
-    vhost_user_opts = opts->vhost_user;
+    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
+    vhost_user_opts = netdev->opts->vhost_user;

     chr = net_vhost_parse_chardev(vhost_user_opts, errp);
     if (!chr) {
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 31/46] qapi: use 'type' in generated C code to match QMP union wire form
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (29 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 30/46] net: use Netdev instead of NetClientOptions in client init Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 32/46] qapi: Hide tag_name data member of variants Eric Blake
                   ` (15 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel
  Cc: Kevin Wolf, Fam Zheng, ehabkost, open list:qcow2,
	Michael S. Tsirkin, Jason Wang, armbru, Luiz Capitulino,
	Igor Mammedov, Paolo Bonzini, Gerd Hoffmann, DirtY.iCE.hu,
	marcandre.lureau, Michael Roth

When dealing with simple qapi unions, the code was generating a
discriminator field of 'kind' even though the discriminator is
sent as 'type' over QMP.  Renaming things to match gets us one
step closer to reusing common generator code for both simple and
flat unions, without having to special case the naming choice
for simple unions.  It also gets rid of some TODO markers in
using the raw QAPISchemaObjectTypeVariants.tag_name field,
although we can't yet convert that field to private until later
fixes to alternate types also quit using it.

However, this patch does not rename the generated enum, which is
still 'unionnameKind'; if we wanted, a further patch could
generate implicit enums as 'unionnameType', with even more
churn to C code to react, and probably update the qapi generator
to reserve the 'fooType' namespace instead of 'fooKind', or better
yet ensure that generated names do not conflict with user names.
But that is a lot harder, as we already have existing qapi usage
of types that end in 'Type'.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 block/qcow2.c                   |  2 +-
 block/vmdk.c                    |  2 +-
 blockdev.c                      | 16 ++++++++--------
 hmp.c                           | 12 ++++++------
 hw/input/hid.c                  |  2 +-
 hw/input/ps2.c                  |  2 +-
 hw/input/virtio-input-hid.c     |  2 +-
 hw/mem/pc-dimm.c                |  2 +-
 net/dump.c                      |  2 +-
 net/hub.c                       |  2 +-
 net/l2tpv3.c                    |  2 +-
 net/net.c                       | 20 ++++++++++----------
 net/slirp.c                     |  2 +-
 net/socket.c                    |  2 +-
 net/tap.c                       |  4 ++--
 net/vhost-user.c                |  2 +-
 numa.c                          |  4 ++--
 qemu-char.c                     | 24 ++++++++++++------------
 scripts/qapi-types.py           |  7 ++-----
 scripts/qapi-visit.py           |  9 +--------
 tests/test-qmp-commands.c       |  2 +-
 tests/test-qmp-input-visitor.c  |  8 ++++----
 tests/test-qmp-output-visitor.c |  6 +++---
 tpm.c                           |  2 +-
 ui/input-keymap.c               | 10 +++++-----
 ui/input-legacy.c               |  2 +-
 ui/input.c                      | 22 +++++++++++-----------
 util/qemu-sockets.c             | 12 ++++++------
 28 files changed, 87 insertions(+), 97 deletions(-)

diff --git a/block/qcow2.c b/block/qcow2.c
index 56ad808..28aa74d 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -2736,7 +2736,7 @@ static ImageInfoSpecific *qcow2_get_specific_info(BlockDriverState *bs)
     ImageInfoSpecific *spec_info = g_new(ImageInfoSpecific, 1);

     *spec_info = (ImageInfoSpecific){
-        .kind  = IMAGE_INFO_SPECIFIC_KIND_QCOW2,
+        .type  = IMAGE_INFO_SPECIFIC_KIND_QCOW2,
         {
             .qcow2 = g_new(ImageInfoSpecificQCow2, 1),
         },
diff --git a/block/vmdk.c b/block/vmdk.c
index be0d640..695780c 100644
--- a/block/vmdk.c
+++ b/block/vmdk.c
@@ -2156,7 +2156,7 @@ static ImageInfoSpecific *vmdk_get_specific_info(BlockDriverState *bs)
     ImageInfoList **next;

     *spec_info = (ImageInfoSpecific){
-        .kind = IMAGE_INFO_SPECIFIC_KIND_VMDK,
+        .type = IMAGE_INFO_SPECIFIC_KIND_VMDK,
         {
             .vmdk = g_new0(ImageInfoSpecificVmdk, 1),
         },
diff --git a/blockdev.c b/blockdev.c
index 32b04b4..bf8a7a2 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1052,12 +1052,12 @@ void hmp_commit(Monitor *mon, const QDict *qdict)
     }
 }

-static void blockdev_do_action(int kind, void *data, Error **errp)
+static void blockdev_do_action(int type, void *data, Error **errp)
 {
     TransactionAction action;
     TransactionActionList list;

-    action.kind = kind;
+    action.type = type;
     action.data = data;
     list.value = &action;
     list.next = NULL;
@@ -1297,7 +1297,7 @@ static void internal_snapshot_prepare(BlkTransactionState *common,
     InternalSnapshotState *state;
     int ret1;

-    g_assert(common->action->kind ==
+    g_assert(common->action->type ==
              TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_INTERNAL_SYNC);
     internal = common->action->blockdev_snapshot_internal_sync;
     state = DO_UPCAST(InternalSnapshotState, common, common);
@@ -1439,7 +1439,7 @@ static void external_snapshot_prepare(BlkTransactionState *common,
     TransactionAction *action = common->action;

     /* get parameters */
-    g_assert(action->kind == TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_SYNC);
+    g_assert(action->type == TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_SYNC);

     has_device = action->blockdev_snapshot_sync->has_device;
     device = action->blockdev_snapshot_sync->device;
@@ -1579,7 +1579,7 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)
     DriveBackup *backup;
     Error *local_err = NULL;

-    assert(common->action->kind == TRANSACTION_ACTION_KIND_DRIVE_BACKUP);
+    assert(common->action->type == TRANSACTION_ACTION_KIND_DRIVE_BACKUP);
     backup = common->action->drive_backup;

     blk = blk_by_name(backup->device);
@@ -1647,7 +1647,7 @@ static void blockdev_backup_prepare(BlkTransactionState *common, Error **errp)
     BlockBackend *blk;
     Error *local_err = NULL;

-    assert(common->action->kind == TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP);
+    assert(common->action->type == TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP);
     backup = common->action->blockdev_backup;

     blk = blk_by_name(backup->device);
@@ -1774,9 +1774,9 @@ void qmp_transaction(TransactionActionList *dev_list, Error **errp)
         dev_info = dev_entry->value;
         dev_entry = dev_entry->next;

-        assert(dev_info->kind < ARRAY_SIZE(actions));
+        assert(dev_info->type < ARRAY_SIZE(actions));

-        ops = &actions[dev_info->kind];
+        ops = &actions[dev_info->type];
         assert(ops->instance_size > 0);

         state = g_malloc0(ops->instance_size);
diff --git a/hmp.c b/hmp.c
index 3f807b7..849b292 100644
--- a/hmp.c
+++ b/hmp.c
@@ -830,9 +830,9 @@ void hmp_info_tpm(Monitor *mon, const QDict *qdict)
                        c, TpmModel_lookup[ti->model]);

         monitor_printf(mon, "  \\ %s: type=%s",
-                       ti->id, TpmTypeOptionsKind_lookup[ti->options->kind]);
+                       ti->id, TpmTypeOptionsKind_lookup[ti->options->type]);

-        switch (ti->options->kind) {
+        switch (ti->options->type) {
         case TPM_TYPE_OPTIONS_KIND_PASSTHROUGH:
             tpo = ti->options->passthrough;
             monitor_printf(mon, "%s%s%s%s",
@@ -1714,14 +1714,14 @@ void hmp_sendkey(Monitor *mon, const QDict *qdict)
             if (*endp != '\0') {
                 goto err_out;
             }
-            keylist->value->kind = KEY_VALUE_KIND_NUMBER;
+            keylist->value->type = KEY_VALUE_KIND_NUMBER;
             keylist->value->number = value;
         } else {
             int idx = index_from_key(keyname_buf);
             if (idx == Q_KEY_CODE_MAX) {
                 goto err_out;
             }
-            keylist->value->kind = KEY_VALUE_KIND_QCODE;
+            keylist->value->type = KEY_VALUE_KIND_QCODE;
             keylist->value->qcode = idx;
         }

@@ -1937,12 +1937,12 @@ void hmp_info_memory_devices(Monitor *mon, const QDict *qdict)
         value = info->value;

         if (value) {
-            switch (value->kind) {
+            switch (value->type) {
             case MEMORY_DEVICE_INFO_KIND_DIMM:
                 di = value->dimm;

                 monitor_printf(mon, "Memory device [%s]: \"%s\"\n",
-                               MemoryDeviceInfoKind_lookup[value->kind],
+                               MemoryDeviceInfoKind_lookup[value->type],
                                di->id ? di->id : "");
                 monitor_printf(mon, "  addr: 0x%" PRIx64 "\n", di->addr);
                 monitor_printf(mon, "  slot: %" PRId64 "\n", di->slot);
diff --git a/hw/input/hid.c b/hw/input/hid.c
index 21ebd9e..ac02f88 100644
--- a/hw/input/hid.c
+++ b/hw/input/hid.c
@@ -119,7 +119,7 @@ static void hid_pointer_event(DeviceState *dev, QemuConsole *src,
     assert(hs->n < QUEUE_LENGTH);
     e = &hs->ptr.queue[(hs->head + hs->n) & QUEUE_MASK];

-    switch (evt->kind) {
+    switch (evt->type) {
     case INPUT_EVENT_KIND_REL:
         if (evt->rel->axis == INPUT_AXIS_X) {
             e->xdx += evt->rel->value;
diff --git a/hw/input/ps2.c b/hw/input/ps2.c
index fdbe565..58decf2 100644
--- a/hw/input/ps2.c
+++ b/hw/input/ps2.c
@@ -393,7 +393,7 @@ static void ps2_mouse_event(DeviceState *dev, QemuConsole *src,
     if (!(s->mouse_status & MOUSE_STATUS_ENABLED))
         return;

-    switch (evt->kind) {
+    switch (evt->type) {
     case INPUT_EVENT_KIND_REL:
         if (evt->rel->axis == INPUT_AXIS_X) {
             s->mouse_dx += evt->rel->value;
diff --git a/hw/input/virtio-input-hid.c b/hw/input/virtio-input-hid.c
index 4d85dad..362dad3 100644
--- a/hw/input/virtio-input-hid.c
+++ b/hw/input/virtio-input-hid.c
@@ -191,7 +191,7 @@ static void virtio_input_handle_event(DeviceState *dev, QemuConsole *src,
     virtio_input_event event;
     int qcode;

-    switch (evt->kind) {
+    switch (evt->type) {
     case INPUT_EVENT_KIND_KEY:
         qcode = qemu_input_key_value_to_qcode(evt->key->key);
         if (qcode && keymap_qcode[qcode]) {
diff --git a/hw/mem/pc-dimm.c b/hw/mem/pc-dimm.c
index bb04862..a444195 100644
--- a/hw/mem/pc-dimm.c
+++ b/hw/mem/pc-dimm.c
@@ -196,7 +196,7 @@ ram_addr_t get_current_ram_size(void)
         MemoryDeviceInfo *value = info->value;

         if (value) {
-            switch (value->kind) {
+            switch (value->type) {
             case MEMORY_DEVICE_INFO_KIND_DIMM:
                 size += value->dimm->size;
                 break;
diff --git a/net/dump.c b/net/dump.c
index d80fa94..a1f99c3 100644
--- a/net/dump.c
+++ b/net/dump.c
@@ -154,7 +154,7 @@ int net_init_dump(const Netdev *netdev, const char *name,
     char def_file[128];
     const NetdevDumpOptions *dump;

-    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_DUMP);
+    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_DUMP);
     dump = netdev->opts->dump;

     assert(peer);
diff --git a/net/hub.c b/net/hub.c
index 29f65b2..047d861 100644
--- a/net/hub.c
+++ b/net/hub.c
@@ -285,7 +285,7 @@ int net_init_hubport(const Netdev *netdev, const char *name,
 {
     const NetdevHubPortOptions *hubport;

-    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_HUBPORT);
+    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_HUBPORT);
     assert(!peer);
     hubport = netdev->opts->hubport;

diff --git a/net/l2tpv3.c b/net/l2tpv3.c
index d2f8431..bdb13fa 100644
--- a/net/l2tpv3.c
+++ b/net/l2tpv3.c
@@ -545,7 +545,7 @@ int net_init_l2tpv3(const Netdev *netdev,
     s->queue_tail = 0;
     s->header_mismatch = false;

-    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_L2TPV3);
+    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_L2TPV3);
     l2tpv3 = netdev->opts->l2tpv3;

     if (l2tpv3->has_ipv6 && l2tpv3->ipv6) {
diff --git a/net/net.c b/net/net.c
index 4d99b48..94a3d18 100644
--- a/net/net.c
+++ b/net/net.c
@@ -820,7 +820,7 @@ static int net_init_nic(const Netdev *netdev, const char *name,
     NICInfo *nd;
     const NetLegacyNicOptions *nic;

-    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_NIC);
+    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_NIC);
     nic = netdev->opts->nic;

     idx = nic_get_free_idx();
@@ -924,9 +924,9 @@ static int net_client_init1(const void *object, int is_netdev, Error **errp)
         opts = netdev->opts;
         name = netdev->id;

-        if (opts->kind == NET_CLIENT_OPTIONS_KIND_DUMP ||
-            opts->kind == NET_CLIENT_OPTIONS_KIND_NIC ||
-            !net_client_init_fun[opts->kind]) {
+        if (opts->type == NET_CLIENT_OPTIONS_KIND_DUMP ||
+            opts->type == NET_CLIENT_OPTIONS_KIND_NIC ||
+            !net_client_init_fun[opts->type]) {
             error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "type",
                        "a netdev backend type");
             return -1;
@@ -939,16 +939,16 @@ static int net_client_init1(const void *object, int is_netdev, Error **errp)
         /* missing optional values have been initialized to "all bits zero" */
         name = net->has_id ? net->id : net->name;

-        if (opts->kind == NET_CLIENT_OPTIONS_KIND_NONE) {
+        if (opts->type == NET_CLIENT_OPTIONS_KIND_NONE) {
             return 0; /* nothing to do */
         }
-        if (opts->kind == NET_CLIENT_OPTIONS_KIND_HUBPORT) {
+        if (opts->type == NET_CLIENT_OPTIONS_KIND_HUBPORT) {
             error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "type",
                        "a net type");
             return -1;
         }

-        if (!net_client_init_fun[opts->kind]) {
+        if (!net_client_init_fun[opts->type]) {
             error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "type",
                        "a net backend type (maybe it is not compiled "
                        "into this binary)");
@@ -956,17 +956,17 @@ static int net_client_init1(const void *object, int is_netdev, Error **errp)
         }

         /* Do not add to a vlan if it's a nic with a netdev= parameter. */
-        if (opts->kind != NET_CLIENT_OPTIONS_KIND_NIC ||
+        if (opts->type != NET_CLIENT_OPTIONS_KIND_NIC ||
             !opts->nic->has_netdev) {
             peer = net_hub_add_port(net->has_vlan ? net->vlan : 0, NULL);
         }
     }

-    if (net_client_init_fun[opts->kind](netdev, name, peer, errp) < 0) {
+    if (net_client_init_fun[opts->type](netdev, name, peer, errp) < 0) {
         /* FIXME drop when all init functions store an Error */
         if (errp && !*errp) {
             error_setg(errp, QERR_DEVICE_INIT_FAILED,
-                       NetClientOptionsKind_lookup[opts->kind]);
+                       NetClientOptionsKind_lookup[opts->type]);
         }
         return -1;
     }
diff --git a/net/slirp.c b/net/slirp.c
index 0fc2c52..e160b9c 100644
--- a/net/slirp.c
+++ b/net/slirp.c
@@ -746,7 +746,7 @@ int net_init_slirp(const Netdev *netdev, const char *name,
     const NetdevUserOptions *user;
     const char **dnssearch;

-    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_USER);
+    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_USER);
     user = netdev->opts->user;

     vnet = user->has_net ? g_strdup(user->net) :
diff --git a/net/socket.c b/net/socket.c
index 75f693c..7f949e2 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -706,7 +706,7 @@ int net_init_socket(const Netdev *netdev, const char *name,
     Error *err = NULL;
     const NetdevSocketOptions *sock;

-    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_SOCKET);
+    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_SOCKET);
     sock = netdev->opts->socket;

     if (sock->has_fd + sock->has_listen + sock->has_connect + sock->has_mcast +
diff --git a/net/tap.c b/net/tap.c
index 263f807..aecc759 100644
--- a/net/tap.c
+++ b/net/tap.c
@@ -565,7 +565,7 @@ int net_init_bridge(const Netdev *netdev, const char *name,
     TAPState *s;
     int fd, vnet_hdr;

-    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_BRIDGE);
+    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_BRIDGE);
     bridge = netdev->opts->bridge;

     helper = bridge->has_helper ? bridge->helper : DEFAULT_BRIDGE_HELPER;
@@ -728,7 +728,7 @@ int net_init_tap(const Netdev *netdev, const char *name,
     const char *vhostfdname;
     char ifname[128];

-    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_TAP);
+    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_TAP);
     tap = netdev->opts->tap;
     queues = tap->has_queues ? tap->queues : 1;
     vhostfdname = tap->has_vhostfd ? tap->vhostfd : NULL;
diff --git a/net/vhost-user.c b/net/vhost-user.c
index 20981a9..8597ec4 100644
--- a/net/vhost-user.c
+++ b/net/vhost-user.c
@@ -229,7 +229,7 @@ int net_init_vhost_user(const Netdev *netdev, const char *name,
     const NetdevVhostUserOptions *vhost_user_opts;
     CharDriverState *chr;

-    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
+    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
     vhost_user_opts = netdev->opts->vhost_user;

     chr = net_vhost_parse_chardev(vhost_user_opts, errp);
diff --git a/numa.c b/numa.c
index e9b18f5..16a8c41 100644
--- a/numa.c
+++ b/numa.c
@@ -226,7 +226,7 @@ static int parse_numa(void *opaque, QemuOpts *opts, Error **errp)
         goto error;
     }

-    switch (object->kind) {
+    switch (object->type) {
     case NUMA_OPTIONS_KIND_NODE:
         numa_node_parse(object->node, opts, &err);
         if (err) {
@@ -487,7 +487,7 @@ static void numa_stat_memory_devices(uint64_t node_mem[])
         MemoryDeviceInfo *value = info->value;

         if (value) {
-            switch (value->kind) {
+            switch (value->type) {
             case MEMORY_DEVICE_INFO_KIND_DIMM:
                 node_mem[value->dimm->node] += value->dimm->size;
                 break;
diff --git a/qemu-char.c b/qemu-char.c
index 653ea10..a0379f2 100644
--- a/qemu-char.c
+++ b/qemu-char.c
@@ -122,7 +122,7 @@ static int SocketAddress_to_str(char *dest, int max_len,
                                 const char *prefix, SocketAddress *addr,
                                 bool is_listen, bool is_telnet)
 {
-    switch (addr->kind) {
+    switch (addr->type) {
     case SOCKET_ADDRESS_KIND_INET:
         return snprintf(dest, max_len, "%s%s:%s:%s%s", prefix,
                         is_telnet ? "telnet" : "tcp", addr->inet->host,
@@ -3567,11 +3567,11 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,

     addr = g_new0(SocketAddress, 1);
     if (path) {
-        addr->kind = SOCKET_ADDRESS_KIND_UNIX;
+        addr->type = SOCKET_ADDRESS_KIND_UNIX;
         addr->q_unix = g_new0(UnixSocketAddress, 1);
         addr->q_unix->path = g_strdup(path);
     } else {
-        addr->kind = SOCKET_ADDRESS_KIND_INET;
+        addr->type = SOCKET_ADDRESS_KIND_INET;
         addr->inet = g_new0(InetSocketAddress, 1);
         addr->inet->host = g_strdup(host);
         addr->inet->port = g_strdup(port);
@@ -3616,7 +3616,7 @@ static void qemu_chr_parse_udp(QemuOpts *opts, ChardevBackend *backend,
     backend->udp = g_new0(ChardevUdp, 1);

     addr = g_new0(SocketAddress, 1);
-    addr->kind = SOCKET_ADDRESS_KIND_INET;
+    addr->type = SOCKET_ADDRESS_KIND_INET;
     addr->inet = g_new0(InetSocketAddress, 1);
     addr->inet->host = g_strdup(host);
     addr->inet->port = g_strdup(port);
@@ -3629,7 +3629,7 @@ static void qemu_chr_parse_udp(QemuOpts *opts, ChardevBackend *backend,
     if (has_local) {
         backend->udp->has_local = true;
         addr = g_new0(SocketAddress, 1);
-        addr->kind = SOCKET_ADDRESS_KIND_INET;
+        addr->type = SOCKET_ADDRESS_KIND_INET;
         addr->inet = g_new0(InetSocketAddress, 1);
         addr->inet->host = g_strdup(localaddr);
         addr->inet->port = g_strdup(localport);
@@ -3701,7 +3701,7 @@ CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts,
     }

     chr = NULL;
-    backend->kind = cd->kind;
+    backend->type = cd->kind;
     if (cd->parse) {
         cd->parse(opts, backend, &local_err);
         if (local_err) {
@@ -3719,7 +3719,7 @@ CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts,
         qapi_free_ChardevReturn(ret);
         backend = g_new0(ChardevBackend, 1);
         backend->mux = g_new0(ChardevMux, 1);
-        backend->kind = CHARDEV_BACKEND_KIND_MUX;
+        backend->type = CHARDEV_BACKEND_KIND_MUX;
         backend->mux->chardev = g_strdup(bid);
         ret = qmp_chardev_add(id, backend, errp);
         if (!ret) {
@@ -4148,7 +4148,7 @@ static CharDriverState *qmp_chardev_open_socket(ChardevSocket *sock,

     s->fd = -1;
     s->listen_fd = -1;
-    s->is_unix = addr->kind == SOCKET_ADDRESS_KIND_UNIX;
+    s->is_unix = addr->type == SOCKET_ADDRESS_KIND_UNIX;
     s->is_listen = is_listen;
     s->is_telnet = is_telnet;
     s->do_nodelay = do_nodelay;
@@ -4222,7 +4222,7 @@ ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend,
         return NULL;
     }

-    switch (backend->kind) {
+    switch (backend->type) {
     case CHARDEV_BACKEND_KIND_FILE:
         chr = qmp_chardev_open_file(backend->file, errp);
         break;
@@ -4293,7 +4293,7 @@ ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend,
         chr = qemu_chr_open_ringbuf(backend->ringbuf, errp);
         break;
     default:
-        error_setg(errp, "unknown chardev backend (%d)", backend->kind);
+        error_setg(errp, "unknown chardev backend (%d)", backend->type);
         break;
     }

@@ -4309,9 +4309,9 @@ ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend,
     if (chr) {
         chr->label = g_strdup(id);
         chr->avail_connections =
-            (backend->kind == CHARDEV_BACKEND_KIND_MUX) ? MAX_MUX : 1;
+            (backend->type == CHARDEV_BACKEND_KIND_MUX) ? MAX_MUX : 1;
         if (!chr->filename) {
-            chr->filename = g_strdup(ChardevBackendKind_lookup[backend->kind]);
+            chr->filename = g_strdup(ChardevBackendKind_lookup[backend->type]);
         }
         if (!chr->explicit_be_open) {
             qemu_chr_be_event(chr, CHR_EVENT_OPENED);
diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
index fe998a1..7eb7f35 100644
--- a/scripts/qapi-types.py
+++ b/scripts/qapi-types.py
@@ -110,13 +110,10 @@ struct %(c_name)s {
         tag_name = 'type'
     else:
         ret += mcgen('''
-    %(c_type)s kind;
+    %(c_type)s type;
 ''',
                      c_type=c_name(variants.tag_member.type.name))
-        # TODO ugly special case for simple union
-        # Use same tag name in C as on the wire to get rid of
-        # it, then: tag_name = variants.tag_member.name
-        tag_name = 'kind'
+        tag_name = variants.tag_member.name

     # FIXME: What purpose does data serve, besides preventing a union that
     # has a branch named 'data'? We use it in qapi-visit.py to decide
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 898a32b..2b3b6a3 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -277,9 +277,6 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
                      c_name=c_name(name))

     tag_key = variants.tag_member.name
-    if not variants.tag_name:
-        # we pointlessly use a different key for simple unions
-        tag_key = 'type'
     ret += mcgen('''
     visit_type_%(c_type)s(v, &(*obj)->%(c_name)s, "%(name)s", &err);
     if (err) {
@@ -288,11 +285,7 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
     switch ((*obj)->%(c_name)s) {
 ''',
                  c_type=variants.tag_member.type.c_name(),
-                 # TODO ugly special case for simple union
-                 # Use same tag name in C as on the wire to get rid of
-                 # it, then: c_name=c_name(variants.tag_member.name)
-                 c_name=c_name(variants.tag_name or 'kind'),
-                 name=tag_key)
+                 c_name=c_name(tag_key), name=tag_key)

     for var in variants.variants:
         # TODO ugly special case for simple union
diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c
index 681ec65..d3b8d8f 100644
--- a/tests/test-qmp-commands.c
+++ b/tests/test-qmp-commands.c
@@ -64,7 +64,7 @@ __org_qemu_x_Union1 *qmp___org_qemu_x_command(__org_qemu_x_EnumList *a,
 {
     __org_qemu_x_Union1 *ret = g_new0(__org_qemu_x_Union1, 1);

-    ret->kind = ORG_QEMU_X_UNION1_KIND___ORG_QEMU_X_BRANCH;
+    ret->type = ORG_QEMU_X_UNION1_KIND___ORG_QEMU_X_BRANCH;
     ret->__org_qemu_x_branch = strdup("blah1");

     return ret;
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index bc9ed04..8928f86 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -549,7 +549,7 @@ static void test_native_list_integer_helper(TestInputVisitorData *data,
     visit_type_UserDefNativeListUnion(v, &cvalue, NULL, &err);
     g_assert(err == NULL);
     g_assert(cvalue != NULL);
-    g_assert_cmpint(cvalue->kind, ==, kind);
+    g_assert_cmpint(cvalue->type, ==, kind);

     switch (kind) {
     case USER_DEF_NATIVE_LIST_UNION_KIND_INTEGER: {
@@ -713,7 +713,7 @@ static void test_visitor_in_native_list_bool(TestInputVisitorData *data,
     visit_type_UserDefNativeListUnion(v, &cvalue, NULL, &err);
     g_assert(err == NULL);
     g_assert(cvalue != NULL);
-    g_assert_cmpint(cvalue->kind, ==, USER_DEF_NATIVE_LIST_UNION_KIND_BOOLEAN);
+    g_assert_cmpint(cvalue->type, ==, USER_DEF_NATIVE_LIST_UNION_KIND_BOOLEAN);

     for (i = 0, elem = cvalue->boolean; elem; elem = elem->next, i++) {
         g_assert_cmpint(elem->value, ==, (i % 3 == 0) ? 1 : 0);
@@ -749,7 +749,7 @@ static void test_visitor_in_native_list_string(TestInputVisitorData *data,
     visit_type_UserDefNativeListUnion(v, &cvalue, NULL, &err);
     g_assert(err == NULL);
     g_assert(cvalue != NULL);
-    g_assert_cmpint(cvalue->kind, ==, USER_DEF_NATIVE_LIST_UNION_KIND_STRING);
+    g_assert_cmpint(cvalue->type, ==, USER_DEF_NATIVE_LIST_UNION_KIND_STRING);

     for (i = 0, elem = cvalue->string; elem; elem = elem->next, i++) {
         gchar str[8];
@@ -789,7 +789,7 @@ static void test_visitor_in_native_list_number(TestInputVisitorData *data,
     visit_type_UserDefNativeListUnion(v, &cvalue, NULL, &err);
     g_assert(err == NULL);
     g_assert(cvalue != NULL);
-    g_assert_cmpint(cvalue->kind, ==, USER_DEF_NATIVE_LIST_UNION_KIND_NUMBER);
+    g_assert_cmpint(cvalue->type, ==, USER_DEF_NATIVE_LIST_UNION_KIND_NUMBER);

     for (i = 0, elem = cvalue->number; elem; elem = elem->next, i++) {
         GString *double_expected = g_string_new("");
diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c
index 0f72b5d..6d6f79c 100644
--- a/tests/test-qmp-output-visitor.c
+++ b/tests/test-qmp-output-visitor.c
@@ -556,7 +556,7 @@ static void test_visitor_out_empty(TestOutputVisitorData *data,
 static void init_native_list(UserDefNativeListUnion *cvalue)
 {
     int i;
-    switch (cvalue->kind) {
+    switch (cvalue->type) {
     case USER_DEF_NATIVE_LIST_UNION_KIND_INTEGER: {
         intList **list = &cvalue->integer;
         for (i = 0; i < 32; i++) {
@@ -777,14 +777,14 @@ static void test_native_list(TestOutputVisitorData *data,
     Error *err = NULL;
     QObject *obj;

-    cvalue->kind = kind;
+    cvalue->type = kind;
     init_native_list(cvalue);

     visit_type_UserDefNativeListUnion(data->ov, &cvalue, NULL, &err);
     g_assert(err == NULL);

     obj = qmp_output_get_qobject(data->qov);
-    check_native_list(obj, cvalue->kind);
+    check_native_list(obj, cvalue->type);
     qapi_free_UserDefNativeListUnion(cvalue);
     qobject_decref(obj);
 }
diff --git a/tpm.c b/tpm.c
index 4e9b109..fcab81c 100644
--- a/tpm.c
+++ b/tpm.c
@@ -260,7 +260,7 @@ static TPMInfo *qmp_query_tpm_inst(TPMBackend *drv)

     switch (drv->ops->type) {
     case TPM_TYPE_PASSTHROUGH:
-        res->options->kind = TPM_TYPE_OPTIONS_KIND_PASSTHROUGH;
+        res->options->type = TPM_TYPE_OPTIONS_KIND_PASSTHROUGH;
         tpo = g_new0(TPMPassthroughOptions, 1);
         res->options->passthrough = tpo;
         if (drv->path) {
diff --git a/ui/input-keymap.c b/ui/input-keymap.c
index 7635cb0..088523d 100644
--- a/ui/input-keymap.c
+++ b/ui/input-keymap.c
@@ -139,10 +139,10 @@ static int number_to_qcode[0x100];

 int qemu_input_key_value_to_number(const KeyValue *value)
 {
-    if (value->kind == KEY_VALUE_KIND_QCODE) {
+    if (value->type == KEY_VALUE_KIND_QCODE) {
         return qcode_to_number[value->qcode];
     } else {
-        assert(value->kind == KEY_VALUE_KIND_NUMBER);
+        assert(value->type == KEY_VALUE_KIND_NUMBER);
         return value->number;
     }
 }
@@ -166,10 +166,10 @@ int qemu_input_key_number_to_qcode(uint8_t nr)

 int qemu_input_key_value_to_qcode(const KeyValue *value)
 {
-    if (value->kind == KEY_VALUE_KIND_QCODE) {
+    if (value->type == KEY_VALUE_KIND_QCODE) {
         return value->qcode;
     } else {
-        assert(value->kind == KEY_VALUE_KIND_NUMBER);
+        assert(value->type == KEY_VALUE_KIND_NUMBER);
         return qemu_input_key_number_to_qcode(value->number);
     }
 }
@@ -180,7 +180,7 @@ int qemu_input_key_value_to_scancode(const KeyValue *value, bool down,
     int keycode = qemu_input_key_value_to_number(value);
     int count = 0;

-    if (value->kind == KEY_VALUE_KIND_QCODE &&
+    if (value->type == KEY_VALUE_KIND_QCODE &&
         value->qcode == Q_KEY_CODE_PAUSE) {
         /* specific case */
         int v = down ? 0 : 0x80;
diff --git a/ui/input-legacy.c b/ui/input-legacy.c
index e50f296..6149648 100644
--- a/ui/input-legacy.c
+++ b/ui/input-legacy.c
@@ -150,7 +150,7 @@ static void legacy_mouse_event(DeviceState *dev, QemuConsole *src,
     };
     QEMUPutMouseEntry *s = (QEMUPutMouseEntry *)dev;

-    switch (evt->kind) {
+    switch (evt->type) {
     case INPUT_EVENT_KIND_BTN:
         if (evt->btn->down) {
             s->buttons |= bmap[evt->btn->button];
diff --git a/ui/input.c b/ui/input.c
index 1a552d1..fd86571 100644
--- a/ui/input.c
+++ b/ui/input.c
@@ -147,10 +147,10 @@ void qmp_x_input_send_event(bool has_console, int64_t console,
     for (e = events; e != NULL; e = e->next) {
         InputEvent *event = e->value;

-        if (!qemu_input_find_handler(1 << event->kind, con)) {
+        if (!qemu_input_find_handler(1 << event->type, con)) {
             error_setg(errp, "Input handler not found for "
                              "event type %s",
-                            InputEventKind_lookup[event->kind]);
+                            InputEventKind_lookup[event->type]);
             return;
         }
     }
@@ -197,9 +197,9 @@ static void qemu_input_event_trace(QemuConsole *src, InputEvent *evt)
     if (src) {
         idx = qemu_console_get_index(src);
     }
-    switch (evt->kind) {
+    switch (evt->type) {
     case INPUT_EVENT_KIND_KEY:
-        switch (evt->key->key->kind) {
+        switch (evt->key->key->type) {
         case KEY_VALUE_KIND_NUMBER:
             qcode = qemu_input_key_number_to_qcode(evt->key->key->number);
             name = QKeyCode_lookup[qcode];
@@ -311,12 +311,12 @@ void qemu_input_event_send(QemuConsole *src, InputEvent *evt)
     qemu_input_event_trace(src, evt);

     /* pre processing */
-    if (graphic_rotate && (evt->kind == INPUT_EVENT_KIND_ABS)) {
+    if (graphic_rotate && (evt->type == INPUT_EVENT_KIND_ABS)) {
             qemu_input_transform_abs_rotate(evt);
     }

     /* send event */
-    s = qemu_input_find_handler(1 << evt->kind, src);
+    s = qemu_input_find_handler(1 << evt->type, src);
     if (!s) {
         return;
     }
@@ -349,7 +349,7 @@ InputEvent *qemu_input_event_new_key(KeyValue *key, bool down)
 {
     InputEvent *evt = g_new0(InputEvent, 1);
     evt->key = g_new0(InputKeyEvent, 1);
-    evt->kind = INPUT_EVENT_KIND_KEY;
+    evt->type = INPUT_EVENT_KIND_KEY;
     evt->key->key = key;
     evt->key->down = down;
     return evt;
@@ -372,7 +372,7 @@ void qemu_input_event_send_key(QemuConsole *src, KeyValue *key, bool down)
 void qemu_input_event_send_key_number(QemuConsole *src, int num, bool down)
 {
     KeyValue *key = g_new0(KeyValue, 1);
-    key->kind = KEY_VALUE_KIND_NUMBER;
+    key->type = KEY_VALUE_KIND_NUMBER;
     key->number = num;
     qemu_input_event_send_key(src, key, down);
 }
@@ -380,7 +380,7 @@ void qemu_input_event_send_key_number(QemuConsole *src, int num, bool down)
 void qemu_input_event_send_key_qcode(QemuConsole *src, QKeyCode q, bool down)
 {
     KeyValue *key = g_new0(KeyValue, 1);
-    key->kind = KEY_VALUE_KIND_QCODE;
+    key->type = KEY_VALUE_KIND_QCODE;
     key->qcode = q;
     qemu_input_event_send_key(src, key, down);
 }
@@ -399,7 +399,7 @@ InputEvent *qemu_input_event_new_btn(InputButton btn, bool down)
 {
     InputEvent *evt = g_new0(InputEvent, 1);
     evt->btn = g_new0(InputBtnEvent, 1);
-    evt->kind = INPUT_EVENT_KIND_BTN;
+    evt->type = INPUT_EVENT_KIND_BTN;
     evt->btn->button = btn;
     evt->btn->down = down;
     return evt;
@@ -451,7 +451,7 @@ InputEvent *qemu_input_event_new_move(InputEventKind kind,
     InputEvent *evt = g_new0(InputEvent, 1);
     InputMoveEvent *move = g_new0(InputMoveEvent, 1);

-    evt->kind = kind;
+    evt->type = kind;
     evt->data = move;
     move->axis = axis;
     move->value = value;
diff --git a/util/qemu-sockets.c b/util/qemu-sockets.c
index 2add83a..277b139 100644
--- a/util/qemu-sockets.c
+++ b/util/qemu-sockets.c
@@ -904,7 +904,7 @@ SocketAddress *socket_parse(const char *str, Error **errp)
             error_setg(errp, "invalid Unix socket address");
             goto fail;
         } else {
-            addr->kind = SOCKET_ADDRESS_KIND_UNIX;
+            addr->type = SOCKET_ADDRESS_KIND_UNIX;
             addr->q_unix = g_new(UnixSocketAddress, 1);
             addr->q_unix->path = g_strdup(str + 5);
         }
@@ -913,12 +913,12 @@ SocketAddress *socket_parse(const char *str, Error **errp)
             error_setg(errp, "invalid file descriptor address");
             goto fail;
         } else {
-            addr->kind = SOCKET_ADDRESS_KIND_FD;
+            addr->type = SOCKET_ADDRESS_KIND_FD;
             addr->fd = g_new(String, 1);
             addr->fd->str = g_strdup(str + 3);
         }
     } else {
-        addr->kind = SOCKET_ADDRESS_KIND_INET;
+        addr->type = SOCKET_ADDRESS_KIND_INET;
         addr->inet = inet_parse(str, errp);
         if (addr->inet == NULL) {
             goto fail;
@@ -938,7 +938,7 @@ int socket_connect(SocketAddress *addr, Error **errp,
     int fd;

     opts = qemu_opts_create(&socket_optslist, NULL, 0, &error_abort);
-    switch (addr->kind) {
+    switch (addr->type) {
     case SOCKET_ADDRESS_KIND_INET:
         inet_addr_to_opts(opts, addr->inet);
         fd = inet_connect_opts(opts, errp, callback, opaque);
@@ -970,7 +970,7 @@ int socket_listen(SocketAddress *addr, Error **errp)
     int fd;

     opts = qemu_opts_create(&socket_optslist, NULL, 0, &error_abort);
-    switch (addr->kind) {
+    switch (addr->type) {
     case SOCKET_ADDRESS_KIND_INET:
         inet_addr_to_opts(opts, addr->inet);
         fd = inet_listen_opts(opts, 0, errp);
@@ -998,7 +998,7 @@ int socket_dgram(SocketAddress *remote, SocketAddress *local, Error **errp)
     int fd;

     opts = qemu_opts_create(&socket_optslist, NULL, 0, &error_abort);
-    switch (remote->kind) {
+    switch (remote->type) {
     case SOCKET_ADDRESS_KIND_INET:
         inet_addr_to_opts(opts, remote->inet);
         if (local) {
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 32/46] qapi: Hide tag_name data member of variants
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (30 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 31/46] qapi: use 'type' in generated C code to match QMP union wire form Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 33/46] vnc: hoist allocation of VncBasicInfo to callers Eric Blake
                   ` (14 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Clean up the only remaining external use of the tag_name field of
QAPISchemaObjectTypeVariants, by explicitly listing the generated
'type' tag for simple unions in the testsuite.  Since alternate
types no longer use the tag_member field, we can now mark the
tag_name field as private by adding a leading underscore to
prevent any further use.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi.py                        | 8 ++++----
 tests/qapi-schema/qapi-schema-test.out | 2 ++
 tests/qapi-schema/test-qapi.py         | 4 ++--
 tests/qapi-schema/union-clash2.out     | 1 +
 tests/qapi-schema/union-empty.out      | 1 +
 5 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index a91bdf2..18cfde1 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -1015,7 +1015,7 @@ class QAPISchemaObjectTypeVariants(object):
         assert tag_enum is None or isinstance(tag_enum, str)
         for v in variants:
             assert isinstance(v, QAPISchemaObjectTypeVariant)
-        self.tag_name = tag_name
+        self._tag_name = tag_name
         if tag_name:         # flat union
             assert not tag_enum
             self.tag_member = None
@@ -1028,9 +1028,9 @@ class QAPISchemaObjectTypeVariants(object):
         self.variants = variants

     def check(self, schema, info, members, seen):
-        if self.tag_name:
-            self.tag_member = seen[c_name(self.tag_name)]
-            assert self.tag_name == self.tag_member.name
+        if self._tag_name:
+            self.tag_member = seen[c_name(self._tag_name)]
+            assert self._tag_name == self.tag_member.name
         elif self.tag_member:
             self.tag_member.check(schema, info, members, seen)
         typ = None
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 4405658..2036df5 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -113,6 +113,7 @@ object UserDefFlatUnion2
     case value2: UserDefB
     case value3: UserDefA
 object UserDefNativeListUnion
+    tag type
     case integer: :obj-intList-wrapper
     case s8: :obj-int8List-wrapper
     case s16: :obj-int16List-wrapper
@@ -167,6 +168,7 @@ object __org.qemu_x-Struct
 object __org.qemu_x-Struct2
     member array: __org.qemu_x-Union1List optional=False
 object __org.qemu_x-Union1
+    tag type
     case __org.qemu_x-branch: :obj-str-wrapper
 enum __org.qemu_x-Union1Kind ['__org.qemu_x-branch']
 object __org.qemu_x-Union2
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index f2cce64..3f0c2bc 100644
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -47,8 +47,8 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
     @staticmethod
     def _print_variants(variants):
         if variants:
-            if variants.tag_name:
-                print '    tag %s' % variants.tag_name
+            if variants.tag_member:
+                print '    tag %s' % variants.tag_member.name
             for v in variants.variants:
                 print '    case %s: %s' % (v.name, v.type.name)

diff --git a/tests/qapi-schema/union-clash2.out b/tests/qapi-schema/union-clash2.out
index 6277239..689ee74 100644
--- a/tests/qapi-schema/union-clash2.out
+++ b/tests/qapi-schema/union-clash2.out
@@ -2,5 +2,6 @@ object :empty
 object :obj-int-wrapper
     member data: int optional=False
 object TestUnion
+    tag type
     case data: :obj-int-wrapper
 enum TestUnionKind ['data']
diff --git a/tests/qapi-schema/union-empty.out b/tests/qapi-schema/union-empty.out
index 8b5a7bf..c5500da 100644
--- a/tests/qapi-schema/union-empty.out
+++ b/tests/qapi-schema/union-empty.out
@@ -1,3 +1,4 @@
 object :empty
 object Union
+    tag type
 enum UnionKind []
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 33/46] vnc: hoist allocation of VncBasicInfo to callers
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (31 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 32/46] qapi: Hide tag_name data member of variants Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 34/46] qapi: Unbox base members Eric Blake
                   ` (13 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel
  Cc: marcandre.lureau, DirtY.iCE.hu, Gerd Hoffmann, armbru, ehabkost

A future qapi patch will rework generated structs with a base
class to be unboxed.  In preparation for that, change the code
that allocates then populates an info struct to instead merely
populate the fields of an info field passed in as a parameter.
Add rudimentary Error handling for cases where the old code
returned NULL; but as before, callers merely ignore errors for
now.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 ui/vnc.c | 52 ++++++++++++++++++++++++++++------------------------
 1 file changed, 28 insertions(+), 24 deletions(-)

diff --git a/ui/vnc.c b/ui/vnc.c
index d73966a..61af4ba 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -156,10 +156,11 @@ char *vnc_socket_remote_addr(const char *format, int fd) {
     return addr_to_string(format, &sa, salen);
 }

-static VncBasicInfo *vnc_basic_info_get(struct sockaddr_storage *sa,
-                                        socklen_t salen)
+static void vnc_basic_info_get(struct sockaddr_storage *sa,
+                               socklen_t salen,
+                               VncBasicInfo *info,
+                               Error **errp)
 {
-    VncBasicInfo *info;
     char host[NI_MAXHOST];
     char serv[NI_MAXSERV];
     int err;
@@ -168,42 +169,44 @@ static VncBasicInfo *vnc_basic_info_get(struct sockaddr_storage *sa,
                            host, sizeof(host),
                            serv, sizeof(serv),
                            NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
-        VNC_DEBUG("Cannot resolve address %d: %s\n",
-                  err, gai_strerror(err));
-        return NULL;
+        error_setg(errp, "Cannot resolve address %d: %s",
+                   err, gai_strerror(err));
+        return;
     }

-    info = g_malloc0(sizeof(VncBasicInfo));
     info->host = g_strdup(host);
     info->service = g_strdup(serv);
     info->family = inet_netfamily(sa->ss_family);
-    return info;
 }

-static VncBasicInfo *vnc_basic_info_get_from_server_addr(int fd)
+static void vnc_basic_info_get_from_server_addr(int fd, VncBasicInfo *info,
+                                                Error **errp)
 {
     struct sockaddr_storage sa;
     socklen_t salen;

     salen = sizeof(sa);
     if (getsockname(fd, (struct sockaddr*)&sa, &salen) < 0) {
-        return NULL;
+        error_setg_errno(errp, errno, "getsockname failed");
+        return;
     }

-    return vnc_basic_info_get(&sa, salen);
+    vnc_basic_info_get(&sa, salen, info, errp);
 }

-static VncBasicInfo *vnc_basic_info_get_from_remote_addr(int fd)
+static void vnc_basic_info_get_from_remote_addr(int fd, VncBasicInfo *info,
+                                                Error **errp)
 {
     struct sockaddr_storage sa;
     socklen_t salen;

     salen = sizeof(sa);
     if (getpeername(fd, (struct sockaddr*)&sa, &salen) < 0) {
-        return NULL;
+        error_setg_errno(errp, errno, "getpeername failed");
+        return;
     }

-    return vnc_basic_info_get(&sa, salen);
+    vnc_basic_info_get(&sa, salen, info, errp);
 }

 static const char *vnc_auth_name(VncDisplay *vd) {
@@ -256,13 +259,10 @@ static const char *vnc_auth_name(VncDisplay *vd) {
 static VncServerInfo *vnc_server_info_get(VncDisplay *vd)
 {
     VncServerInfo *info;
-    VncBasicInfo *bi = vnc_basic_info_get_from_server_addr(vd->lsock);
-    if (!bi) {
-        return NULL;
-    }

     info = g_malloc(sizeof(*info));
-    info->base = bi;
+    info->base = g_malloc(sizeof(*info->base));
+    vnc_basic_info_get_from_server_addr(vd->lsock, info->base, NULL);
     info->has_auth = true;
     info->auth = g_strdup(vnc_auth_name(vd));
     return info;
@@ -291,11 +291,15 @@ static void vnc_client_cache_auth(VncState *client)

 static void vnc_client_cache_addr(VncState *client)
 {
-    VncBasicInfo *bi = vnc_basic_info_get_from_remote_addr(client->csock);
-
-    if (bi) {
-        client->info = g_malloc0(sizeof(*client->info));
-        client->info->base = bi;
+    Error *err = NULL;
+    client->info = g_malloc0(sizeof(*client->info));
+    client->info->base = g_malloc0(sizeof(*client->info->base));
+    vnc_basic_info_get_from_remote_addr(client->csock, client->info->base,
+                                        &err);
+    if (err) {
+        qapi_free_VncClientInfo(client->info);
+        client->info = NULL;
+        error_free(err);
     }
 }

-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 34/46] qapi: Unbox base members
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (32 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 33/46] vnc: hoist allocation of VncBasicInfo to callers Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 35/46] qapi-visit: Remove redundant functions for flat union base Eric Blake
                   ` (12 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel
  Cc: ehabkost, armbru, Michael Roth, Luiz Capitulino, Gerd Hoffmann,
	DirtY.iCE.hu, marcandre.lureau

Rather than storing a base class as a pointer to a box, just
store the fields of that base class in the same order, so that
a child struct can be safely cast to its parent.  This gives
less malloc overhead, less pointer dereferencing, and even less
generated code.

Without boxing, the corner case of one empty struct having
another empty struct as its base type now requires inserting a
dummy member (previously, the pointer to the base would have
sufficed).

And now that we no longer consume a 'base' member in the generated
C struct, we can delete the former negative test struct-base-clash2
and replace it with a positive test in qapi-schema-test that
ensures we don't reintroduce the naming collision.

Compare to the earlier commit 1e6c1616a "qapi: Generate a nicer
struct for flat unions".

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 hmp.c                                     |  6 +++---
 scripts/qapi-types.py                     | 29 +++++++++++++++--------------
 scripts/qapi-visit.py                     |  9 ++++++---
 tests/Makefile                            |  1 -
 tests/qapi-schema/qapi-schema-test.json   |  3 +++
 tests/qapi-schema/qapi-schema-test.out    |  3 +++
 tests/qapi-schema/struct-base-clash2.err  |  0
 tests/qapi-schema/struct-base-clash2.exit |  1 -
 tests/qapi-schema/struct-base-clash2.json |  5 -----
 tests/qapi-schema/struct-base-clash2.out  |  5 -----
 tests/test-qmp-commands.c                 | 15 +++++----------
 tests/test-qmp-event.c                    |  8 ++------
 tests/test-qmp-input-visitor.c            |  4 ++--
 tests/test-qmp-output-visitor.c           | 12 ++++--------
 tests/test-visitor-serialization.c        | 14 ++++++--------
 ui/spice-core.c                           | 23 +++++++++++++----------
 ui/vnc.c                                  | 20 +++++++++-----------
 17 files changed, 71 insertions(+), 87 deletions(-)
 delete mode 100644 tests/qapi-schema/struct-base-clash2.err
 delete mode 100644 tests/qapi-schema/struct-base-clash2.exit
 delete mode 100644 tests/qapi-schema/struct-base-clash2.json
 delete mode 100644 tests/qapi-schema/struct-base-clash2.out

diff --git a/hmp.c b/hmp.c
index 849b292..ab8e700 100644
--- a/hmp.c
+++ b/hmp.c
@@ -558,8 +558,8 @@ void hmp_info_vnc(Monitor *mon, const QDict *qdict)
         for (client = info->clients; client; client = client->next) {
             monitor_printf(mon, "Client:\n");
             monitor_printf(mon, "     address: %s:%s\n",
-                           client->value->base->host,
-                           client->value->base->service);
+                           client->value->host,
+                           client->value->service);
             monitor_printf(mon, "  x509_dname: %s\n",
                            client->value->x509_dname ?
                            client->value->x509_dname : "none");
@@ -627,7 +627,7 @@ void hmp_info_spice(Monitor *mon, const QDict *qdict)
         for (chan = info->channels; chan; chan = chan->next) {
             monitor_printf(mon, "Channel:\n");
             monitor_printf(mon, "     address: %s:%s%s\n",
-                           chan->value->base->host, chan->value->base->port,
+                           chan->value->host, chan->value->port,
                            chan->value->tls ? " [tls]" : "");
             monitor_printf(mon, "     session: %" PRId64 "\n",
                            chan->value->connection_id);
diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
index 7eb7f35..5b9cb69 100644
--- a/scripts/qapi-types.py
+++ b/scripts/qapi-types.py
@@ -51,8 +51,19 @@ def gen_struct_field(name, typ, optional):
     return ret


-def gen_struct_fields(members):
+def gen_struct_fields(members, base, nested=False):
     ret = ''
+    if base:
+        if not nested:
+            ret += mcgen('''
+    /* Members inherited from %(c_name)s: */
+''',
+                         c_name=base.c_name())
+        ret += gen_struct_fields(base.local_members, base.base, True)
+        if not nested:
+            ret += mcgen('''
+    /* Own members: */
+''')

     for memb in members:
         ret += gen_struct_field(memb.name, memb.type, memb.optional)
@@ -66,16 +77,13 @@ struct %(c_name)s {
 ''',
                 c_name=c_name(name))

-    if base:
-        ret += gen_struct_field('base', base, False)
-
-    ret += gen_struct_fields(members)
+    ret += gen_struct_fields(members, base)

     # Make sure that all structs have at least one field; this avoids
     # potential issues with attempting to malloc space for zero-length
     # structs in C, and also incompatibility with C++ (where an empty
     # struct is size 1).
-    if not base and not members:
+    if not (base and base.members) and not members:
         ret += mcgen('''
     char qapi_dummy_field_for_empty_struct;
 ''')
@@ -94,14 +102,7 @@ struct %(c_name)s {
 ''',
                 c_name=c_name(name))
     if base:
-        ret += mcgen('''
-    /* Members inherited from %(c_name)s: */
-''',
-                     c_name=c_name(base.name))
-        ret += gen_struct_fields(base.members)
-        ret += mcgen('''
-    /* Own members: */
-''')
+        ret += gen_struct_fields([], base)
         tag_name = variants.tag_member.name
     elif not variants.tag_member:
         ret += mcgen('''
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 2b3b6a3..9968cc5 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -69,12 +69,15 @@ static void visit_type_implicit_%(c_type)s(Visitor *v, %(c_type)s **obj, Error *


 def gen_visit_struct_fields(name, base, members):
+    if name in struct_fields_seen:
+        return ''
     struct_fields_seen.add(name)

     ret = ''

     if base:
-        ret += gen_visit_implicit_struct(base)
+        ret += gen_visit_struct_fields(base.name, base.base,
+                                       base.local_members)

     ret += mcgen('''

@@ -88,12 +91,12 @@ static void visit_type_%(c_name)s_fields(Visitor *v, %(c_name)s **obj, Error **e

     if base:
         ret += mcgen('''
-visit_type_implicit_%(c_type)s(v, &(*obj)->%(c_name)s, &err);
+visit_type_%(c_type)s_fields(v, (%(c_type)s **)obj, &err);
 if (err) {
     goto out;
 }
 ''',
-                     c_type=base.c_name(), c_name=c_name('base'))
+                     c_type=base.c_name())

     ret += gen_visit_fields(members, '(*obj)->', False, 'err')

diff --git a/tests/Makefile b/tests/Makefile
index 20b84b5..f351833 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -323,7 +323,6 @@ qapi-schema += returns-unknown.json
 qapi-schema += returns-whitelist.json
 qapi-schema += struct-base-clash-deep.json
 qapi-schema += struct-base-clash.json
-qapi-schema += struct-base-clash2.json
 qapi-schema += struct-data-invalid.json
 qapi-schema += struct-member-invalid.json
 qapi-schema += trailing-comma-list.json
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index 7593ecb..b1292c5 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -31,6 +31,9 @@
   'data': { 'string0': 'str',
             'dict1': 'UserDefTwoDict' } }

+{ 'struct': 'UserDefThree',
+  'base': 'UserDefOne', 'data': { 'base': 'str' } }
+
 # for testing unions
 # name collisions between branches should not clash
 { 'struct': 'UserDefA',
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 2036df5..d9e8595 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -139,6 +139,9 @@ object UserDefOptions
     member u16: uint16List optional=True
     member i64x: int optional=True
     member u64x: uint64 optional=True
+object UserDefThree
+    base UserDefOne
+    member base: str optional=False
 object UserDefTwo
     member string0: str optional=False
     member dict1: UserDefTwoDict optional=False
diff --git a/tests/qapi-schema/struct-base-clash2.err b/tests/qapi-schema/struct-base-clash2.err
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/qapi-schema/struct-base-clash2.exit b/tests/qapi-schema/struct-base-clash2.exit
deleted file mode 100644
index 573541a..0000000
--- a/tests/qapi-schema/struct-base-clash2.exit
+++ /dev/null
@@ -1 +0,0 @@
-0
diff --git a/tests/qapi-schema/struct-base-clash2.json b/tests/qapi-schema/struct-base-clash2.json
deleted file mode 100644
index 56166e0..0000000
--- a/tests/qapi-schema/struct-base-clash2.json
+++ /dev/null
@@ -1,5 +0,0 @@
-# FIXME - a base class collides with a member named base
-{ 'struct': 'Base', 'data': {} }
-{ 'struct': 'Sub',
-  'base': 'Base',
-  'data': { 'base': 'str' } }
diff --git a/tests/qapi-schema/struct-base-clash2.out b/tests/qapi-schema/struct-base-clash2.out
deleted file mode 100644
index e69a416..0000000
--- a/tests/qapi-schema/struct-base-clash2.out
+++ /dev/null
@@ -1,5 +0,0 @@
-object :empty
-object Base
-object Sub
-    base Base
-    member base: str optional=False
diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c
index d3b8d8f..89a3b47 100644
--- a/tests/test-qmp-commands.c
+++ b/tests/test-qmp-commands.c
@@ -25,11 +25,9 @@ UserDefTwo *qmp_user_def_cmd2(UserDefOne *ud1a,
     UserDefOne *ud1d = g_malloc0(sizeof(UserDefOne));

     ud1c->string = strdup(ud1a->string);
-    ud1c->base = g_new0(UserDefZero, 1);
-    ud1c->base->integer = ud1a->base->integer;
+    ud1c->integer = ud1a->integer;
     ud1d->string = strdup(has_udb1 ? ud1b->string : "blah0");
-    ud1d->base = g_new0(UserDefZero, 1);
-    ud1d->base->integer = has_udb1 ? ud1b->base->integer : 0;
+    ud1d->integer = has_udb1 ? ud1b->integer : 0;

     ret = g_new0(UserDefTwo, 1);
     ret->string0 = strdup("blah1");
@@ -176,20 +174,17 @@ static void test_dealloc_types(void)
     UserDefOneList *ud1list;

     ud1test = g_malloc0(sizeof(UserDefOne));
-    ud1test->base = g_new0(UserDefZero, 1);
-    ud1test->base->integer = 42;
+    ud1test->integer = 42;
     ud1test->string = g_strdup("hi there 42");

     qapi_free_UserDefOne(ud1test);

     ud1a = g_malloc0(sizeof(UserDefOne));
-    ud1a->base = g_new0(UserDefZero, 1);
-    ud1a->base->integer = 43;
+    ud1a->integer = 43;
     ud1a->string = g_strdup("hi there 43");

     ud1b = g_malloc0(sizeof(UserDefOne));
-    ud1b->base = g_new0(UserDefZero, 1);
-    ud1b->base->integer = 44;
+    ud1b->integer = 44;
     ud1b->string = g_strdup("hi there 44");

     ud1list = g_malloc0(sizeof(UserDefOneList));
diff --git a/tests/test-qmp-event.c b/tests/test-qmp-event.c
index 28f146d..035c65c 100644
--- a/tests/test-qmp-event.c
+++ b/tests/test-qmp-event.c
@@ -179,9 +179,7 @@ static void test_event_c(TestEventData *data,
     QDict *d, *d_data, *d_b;

     UserDefOne b;
-    UserDefZero z;
-    z.integer = 2;
-    b.base = &z;
+    b.integer = 2;
     b.string = g_strdup("test1");
     b.has_enum1 = false;

@@ -209,11 +207,9 @@ static void test_event_d(TestEventData *data,
 {
     UserDefOne struct1;
     EventStructOne a;
-    UserDefZero z;
     QDict *d, *d_data, *d_a, *d_struct1;

-    z.integer = 2;
-    struct1.base = &z;
+    struct1.integer = 2;
     struct1.string = g_strdup("test1");
     struct1.has_enum1 = true;
     struct1.enum1 = ENUM_ONE_VALUE1;
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index 8928f86..2da1b8b 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -272,7 +272,7 @@ static void test_visitor_in_struct_nested(TestInputVisitorData *data,

     check_and_free_str(udp->string0, "string0");
     check_and_free_str(udp->dict1->string1, "string1");
-    g_assert_cmpint(udp->dict1->dict2->userdef->base->integer, ==, 42);
+    g_assert_cmpint(udp->dict1->dict2->userdef->integer, ==, 42);
     check_and_free_str(udp->dict1->dict2->userdef->string, "string");
     check_and_free_str(udp->dict1->dict2->string, "string2");
     g_assert(udp->dict1->has_dict3 == false);
@@ -303,7 +303,7 @@ static void test_visitor_in_list(TestInputVisitorData *data,

         snprintf(string, sizeof(string), "string%d", i);
         g_assert_cmpstr(item->value->string, ==, string);
-        g_assert_cmpint(item->value->base->integer, ==, 42 + i);
+        g_assert_cmpint(item->value->integer, ==, 42 + i);
     }

     qapi_free_UserDefOneList(head);
diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c
index 6d6f79c..02ad5a7d 100644
--- a/tests/test-qmp-output-visitor.c
+++ b/tests/test-qmp-output-visitor.c
@@ -250,16 +250,14 @@ static void test_visitor_out_struct_nested(TestOutputVisitorData *data,
     ud2->dict1->dict2 = g_malloc0(sizeof(*ud2->dict1->dict2));
     ud2->dict1->dict2->userdef = g_new0(UserDefOne, 1);
     ud2->dict1->dict2->userdef->string = g_strdup(string);
-    ud2->dict1->dict2->userdef->base = g_new0(UserDefZero, 1);
-    ud2->dict1->dict2->userdef->base->integer = value;
+    ud2->dict1->dict2->userdef->integer = value;
     ud2->dict1->dict2->string = g_strdup(strings[2]);

     ud2->dict1->dict3 = g_malloc0(sizeof(*ud2->dict1->dict3));
     ud2->dict1->has_dict3 = true;
     ud2->dict1->dict3->userdef = g_new0(UserDefOne, 1);
     ud2->dict1->dict3->userdef->string = g_strdup(string);
-    ud2->dict1->dict3->userdef->base = g_new0(UserDefZero, 1);
-    ud2->dict1->dict3->userdef->base->integer = value;
+    ud2->dict1->dict3->userdef->integer = value;
     ud2->dict1->dict3->string = g_strdup(strings[3]);

     visit_type_UserDefTwo(data->ov, &ud2, "unused", &err);
@@ -301,8 +299,7 @@ static void test_visitor_out_struct_errors(TestOutputVisitorData *data,
                                            const void *unused)
 {
     EnumOne bad_values[] = { ENUM_ONE_MAX, -1 };
-    UserDefZero b;
-    UserDefOne u = { .base = &b }, *pu = &u;
+    UserDefOne u, *pu = &u;
     Error *err;
     int i;

@@ -416,8 +413,7 @@ static void test_visitor_out_list_qapi_free(TestOutputVisitorData *data,
         p->value->dict1->dict2 = g_new0(UserDefTwoDictDict, 1);
         p->value->dict1->dict2->userdef = g_new0(UserDefOne, 1);
         p->value->dict1->dict2->userdef->string = g_strdup(string);
-        p->value->dict1->dict2->userdef->base = g_new0(UserDefZero, 1);
-        p->value->dict1->dict2->userdef->base->integer = 42;
+        p->value->dict1->dict2->userdef->integer = 42;
         p->value->dict1->dict2->string = g_strdup(string);
         p->value->dict1->has_dict3 = false;

diff --git a/tests/test-visitor-serialization.c b/tests/test-visitor-serialization.c
index fa86cae..634563b 100644
--- a/tests/test-visitor-serialization.c
+++ b/tests/test-visitor-serialization.c
@@ -258,15 +258,13 @@ static UserDefTwo *nested_struct_create(void)
     udnp->dict1->string1 = strdup("test_string1");
     udnp->dict1->dict2 = g_malloc0(sizeof(*udnp->dict1->dict2));
     udnp->dict1->dict2->userdef = g_new0(UserDefOne, 1);
-    udnp->dict1->dict2->userdef->base = g_new0(UserDefZero, 1);
-    udnp->dict1->dict2->userdef->base->integer = 42;
+    udnp->dict1->dict2->userdef->integer = 42;
     udnp->dict1->dict2->userdef->string = strdup("test_string");
     udnp->dict1->dict2->string = strdup("test_string2");
     udnp->dict1->dict3 = g_malloc0(sizeof(*udnp->dict1->dict3));
     udnp->dict1->has_dict3 = true;
     udnp->dict1->dict3->userdef = g_new0(UserDefOne, 1);
-    udnp->dict1->dict3->userdef->base = g_new0(UserDefZero, 1);
-    udnp->dict1->dict3->userdef->base->integer = 43;
+    udnp->dict1->dict3->userdef->integer = 43;
     udnp->dict1->dict3->userdef->string = strdup("test_string");
     udnp->dict1->dict3->string = strdup("test_string3");
     return udnp;
@@ -278,15 +276,15 @@ static void nested_struct_compare(UserDefTwo *udnp1, UserDefTwo *udnp2)
     g_assert(udnp2);
     g_assert_cmpstr(udnp1->string0, ==, udnp2->string0);
     g_assert_cmpstr(udnp1->dict1->string1, ==, udnp2->dict1->string1);
-    g_assert_cmpint(udnp1->dict1->dict2->userdef->base->integer, ==,
-                    udnp2->dict1->dict2->userdef->base->integer);
+    g_assert_cmpint(udnp1->dict1->dict2->userdef->integer, ==,
+                    udnp2->dict1->dict2->userdef->integer);
     g_assert_cmpstr(udnp1->dict1->dict2->userdef->string, ==,
                     udnp2->dict1->dict2->userdef->string);
     g_assert_cmpstr(udnp1->dict1->dict2->string, ==,
                     udnp2->dict1->dict2->string);
     g_assert(udnp1->dict1->has_dict3 == udnp2->dict1->has_dict3);
-    g_assert_cmpint(udnp1->dict1->dict3->userdef->base->integer, ==,
-                    udnp2->dict1->dict3->userdef->base->integer);
+    g_assert_cmpint(udnp1->dict1->dict3->userdef->integer, ==,
+                    udnp2->dict1->dict3->userdef->integer);
     g_assert_cmpstr(udnp1->dict1->dict3->userdef->string, ==,
                     udnp2->dict1->dict3->userdef->string);
     g_assert_cmpstr(udnp1->dict1->dict3->string, ==,
diff --git a/ui/spice-core.c b/ui/spice-core.c
index bf4fd07..86f4441 100644
--- a/ui/spice-core.c
+++ b/ui/spice-core.c
@@ -200,8 +200,6 @@ static void channel_event(int event, SpiceChannelEventInfo *info)
 {
     SpiceServerInfo *server = g_malloc0(sizeof(*server));
     SpiceChannel *client = g_malloc0(sizeof(*client));
-    server->base = g_malloc0(sizeof(*server->base));
-    client->base = g_malloc0(sizeof(*client->base));

     /*
      * Spice server might have called us from spice worker thread
@@ -218,9 +216,11 @@ static void channel_event(int event, SpiceChannelEventInfo *info)
     }

     if (info->flags & SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT) {
-        add_addr_info(client->base, (struct sockaddr *)&info->paddr_ext,
+        add_addr_info((SpiceBasicInfo *)client,
+                      (struct sockaddr *)&info->paddr_ext,
                       info->plen_ext);
-        add_addr_info(server->base, (struct sockaddr *)&info->laddr_ext,
+        add_addr_info((SpiceBasicInfo *)server,
+                      (struct sockaddr *)&info->laddr_ext,
                       info->llen_ext);
     } else {
         error_report("spice: %s, extended address is expected",
@@ -229,7 +229,9 @@ static void channel_event(int event, SpiceChannelEventInfo *info)

     switch (event) {
     case SPICE_CHANNEL_EVENT_CONNECTED:
-        qapi_event_send_spice_connected(server->base, client->base, &error_abort);
+        qapi_event_send_spice_connected((SpiceBasicInfo *)server,
+                                        (SpiceBasicInfo *)client,
+                                        &error_abort);
         break;
     case SPICE_CHANNEL_EVENT_INITIALIZED:
         if (auth) {
@@ -242,7 +244,9 @@ static void channel_event(int event, SpiceChannelEventInfo *info)
         break;
     case SPICE_CHANNEL_EVENT_DISCONNECTED:
         channel_list_del(info);
-        qapi_event_send_spice_disconnected(server->base, client->base, &error_abort);
+        qapi_event_send_spice_disconnected((SpiceBasicInfo *)server,
+                                           (SpiceBasicInfo *)client,
+                                           &error_abort);
         break;
     default:
         break;
@@ -378,16 +382,15 @@ static SpiceChannelList *qmp_query_spice_channels(void)

         chan = g_malloc0(sizeof(*chan));
         chan->value = g_malloc0(sizeof(*chan->value));
-        chan->value->base = g_malloc0(sizeof(*chan->value->base));

         paddr = (struct sockaddr *)&item->info->paddr_ext;
         plen = item->info->plen_ext;
         getnameinfo(paddr, plen,
                     host, sizeof(host), port, sizeof(port),
                     NI_NUMERICHOST | NI_NUMERICSERV);
-        chan->value->base->host = g_strdup(host);
-        chan->value->base->port = g_strdup(port);
-        chan->value->base->family = inet_netfamily(paddr->sa_family);
+        chan->value->host = g_strdup(host);
+        chan->value->port = g_strdup(port);
+        chan->value->family = inet_netfamily(paddr->sa_family);

         chan->value->connection_id = item->info->connection_id;
         chan->value->channel_type = item->info->type;
diff --git a/ui/vnc.c b/ui/vnc.c
index 61af4ba..e7c29fe 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -261,8 +261,7 @@ static VncServerInfo *vnc_server_info_get(VncDisplay *vd)
     VncServerInfo *info;

     info = g_malloc(sizeof(*info));
-    info->base = g_malloc(sizeof(*info->base));
-    vnc_basic_info_get_from_server_addr(vd->lsock, info->base, NULL);
+    vnc_basic_info_get_from_server_addr(vd->lsock, (VncBasicInfo *)info, NULL);
     info->has_auth = true;
     info->auth = g_strdup(vnc_auth_name(vd));
     return info;
@@ -293,8 +292,8 @@ static void vnc_client_cache_addr(VncState *client)
 {
     Error *err = NULL;
     client->info = g_malloc0(sizeof(*client->info));
-    client->info->base = g_malloc0(sizeof(*client->info->base));
-    vnc_basic_info_get_from_remote_addr(client->csock, client->info->base,
+    vnc_basic_info_get_from_remote_addr(client->csock,
+                                        (VncBasicInfo *)client->info,
                                         &err);
     if (err) {
         qapi_free_VncClientInfo(client->info);
@@ -310,7 +309,6 @@ static void vnc_qmp_event(VncState *vs, QAPIEvent event)
     if (!vs->info) {
         return;
     }
-    g_assert(vs->info->base);

     si = vnc_server_info_get(vs->vd);
     if (!si) {
@@ -319,7 +317,8 @@ static void vnc_qmp_event(VncState *vs, QAPIEvent event)

     switch (event) {
     case QAPI_EVENT_VNC_CONNECTED:
-        qapi_event_send_vnc_connected(si, vs->info->base, &error_abort);
+        qapi_event_send_vnc_connected(si, (VncBasicInfo *)vs->info,
+                                      &error_abort);
         break;
     case QAPI_EVENT_VNC_INITIALIZED:
         qapi_event_send_vnc_initialized(si, vs->info, &error_abort);
@@ -354,11 +353,10 @@ static VncClientInfo *qmp_query_vnc_client(const VncState *client)
     }

     info = g_malloc0(sizeof(*info));
-    info->base = g_malloc0(sizeof(*info->base));
-    info->base->host = g_strdup(host);
-    info->base->service = g_strdup(serv);
-    info->base->family = inet_netfamily(sa.ss_family);
-    info->base->websocket = client->websocket;
+    info->host = g_strdup(host);
+    info->service = g_strdup(serv);
+    info->family = inet_netfamily(sa.ss_family);
+    info->websocket = client->websocket;

     if (client->tls) {
         info->x509_dname = qcrypto_tls_session_get_peer_name(client->tls);
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 35/46] qapi-visit: Remove redundant functions for flat union base
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (33 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 34/46] qapi: Unbox base members Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-23 20:55   ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 36/46] qapi: Avoid use of 'data' member of qapi unions Eric Blake
                   ` (11 subsequent siblings)
  46 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

The code for visiting the base class of a child struct created
visit_type_Base_fields(); the code for visiting the base class
of a flat union created visit_type_Union_fields(). If the same
type is shared between a struct and a union, the two functions
differed only by whether they visited the discriminator used by
the union. But if the base class always visits all its fields,
then we don't need to revisit the discriminator specially for
a union.  By consistently visiting the base class fields under
the name of the base class, we can eliminate some redundant
code.

Now that gen_visit_struct_fields() can potentially collect more
than one function into 'ret', a regular expression searching for
whether a label was used may hit a false positive within the
body of the first function.  But using a regex was overkill,
since we can easily determine when we jumped to a label.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi-visit.py | 38 +++++++++++++++++++-------------------
 1 file changed, 19 insertions(+), 19 deletions(-)

diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 9968cc5..00be0dc 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -101,7 +101,7 @@ if (err) {
     ret += gen_visit_fields(members, '(*obj)->', False, 'err')

     pop_indent()
-    if re.search('^ *goto out;', ret, re.MULTILINE):
+    if base or members:
         ret += mcgen('''

 out:
@@ -245,8 +245,8 @@ def gen_visit_union(name, base, variants):
     ret = ''

     if base:
-        members = [m for m in base.members if m != variants.tag_member]
-        ret += gen_visit_struct_fields(name, None, members)
+        ret += gen_visit_struct_fields(base.name, base.base,
+                                       base.local_members)

     for var in variants.variants:
         # Ugly special case for simple union TODO get rid of it
@@ -270,25 +270,25 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
 ''',
                  c_name=c_name(name), name=name)

-    if base:
-        ret += mcgen('''
-    visit_type_%(c_name)s_fields(v, obj, &err);
-    if (err) {
-        goto out_obj;
-    }
-''',
-                     c_name=c_name(name))
-
     tag_key = variants.tag_member.name
-    ret += mcgen('''
+    if base:
+        ret += mcgen('''
+    visit_type_%(c_name)s_fields(v, (%(c_name)s **)obj, &err);
+''',
+                     c_name=c_name(base.name))
+    else:
+        ret += mcgen('''
     visit_type_%(c_type)s(v, &(*obj)->%(c_name)s, "%(name)s", &err);
-    if (err) {
-        goto out_obj;
-    }
-    switch ((*obj)->%(c_name)s) {
 ''',
-                 c_type=variants.tag_member.type.c_name(),
-                 c_name=c_name(tag_key), name=tag_key)
+                     c_type=variants.tag_member.type.c_name(),
+                     c_name=c_name(tag_key), name=tag_key)
+    ret += mcgen('''
+        if (err) {
+            goto out_obj;
+        }
+        switch ((*obj)->%(c_name)s) {
+''',
+                 c_name=c_name(tag_key))

     for var in variants.variants:
         # TODO ugly special case for simple union
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 36/46] qapi: Avoid use of 'data' member of qapi unions
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (34 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 35/46] qapi-visit: Remove redundant functions for flat union base Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 37/46] qapi: Forbid empty unions and useless alternates Eric Blake
                   ` (10 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel
  Cc: Kevin Wolf, ehabkost, open list:Block layer core, armbru,
	Gerd Hoffmann, DirtY.iCE.hu, marcandre.lureau

qapi code generators currently create a 'void *data' member as
part of the anonymous union embedded in the C struct corresponding
to a qapi union.  However, directly assigning to this member of
the union feels a bit fishy, when we can directly use the rest
of the struct instead.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 blockdev.c | 22 ++++++++++++----------
 ui/input.c |  2 +-
 2 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/blockdev.c b/blockdev.c
index bf8a7a2..28a3375 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1052,14 +1052,11 @@ void hmp_commit(Monitor *mon, const QDict *qdict)
     }
 }

-static void blockdev_do_action(int type, void *data, Error **errp)
+static void blockdev_do_action(TransactionAction *action, Error **errp)
 {
-    TransactionAction action;
     TransactionActionList list;

-    action.type = type;
-    action.data = data;
-    list.value = &action;
+    list.value = action;
     list.next = NULL;
     qmp_transaction(&list, errp);
 }
@@ -1085,8 +1082,11 @@ void qmp_blockdev_snapshot_sync(bool has_device, const char *device,
         .has_mode = has_mode,
         .mode = mode,
     };
-    blockdev_do_action(TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_SYNC,
-                       &snapshot, errp);
+    TransactionAction action = {
+        .type = TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_SYNC,
+        .blockdev_snapshot_sync = &snapshot,
+    };
+    blockdev_do_action(&action, errp);
 }

 void qmp_blockdev_snapshot_internal_sync(const char *device,
@@ -1097,9 +1097,11 @@ void qmp_blockdev_snapshot_internal_sync(const char *device,
         .device = (char *) device,
         .name = (char *) name
     };
-
-    blockdev_do_action(TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_INTERNAL_SYNC,
-                       &snapshot, errp);
+    TransactionAction action = {
+        .type = TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_INTERNAL_SYNC,
+        .blockdev_snapshot_internal_sync = &snapshot,
+    };
+    blockdev_do_action(&action, errp);
 }

 SnapshotInfo *qmp_blockdev_snapshot_delete_internal_sync(const char *device,
diff --git a/ui/input.c b/ui/input.c
index fd86571..edd237d 100644
--- a/ui/input.c
+++ b/ui/input.c
@@ -452,7 +452,7 @@ InputEvent *qemu_input_event_new_move(InputEventKind kind,
     InputMoveEvent *move = g_new0(InputMoveEvent, 1);

     evt->type = kind;
-    evt->data = move;
+    evt->rel = move; /* also would work as evt->abs */
     move->axis = axis;
     move->value = value;
     return evt;
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 37/46] qapi: Forbid empty unions and useless alternates
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (35 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 36/46] qapi: Avoid use of 'data' member of qapi unions Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 38/46] qapi: Drop useless 'data' member of unions Eric Blake
                   ` (9 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Empty unions serve no purpose, and while we compile with gcc
which permits them, strict C99 forbids them.  We could inject
a dummy member (and in fact, we do for empty structs), but while
empty structs make sense in qapi, empty unions don't add any
expressiveness to the QMP language.  So prohibit them at parse
time.  Update the documentation and testsuite to match.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 docs/qapi-code-gen.txt                  | 15 ++++++++-------
 scripts/qapi.py                         | 12 ++++++++++--
 tests/qapi-schema/alternate-empty.err   |  1 +
 tests/qapi-schema/alternate-empty.exit  |  2 +-
 tests/qapi-schema/alternate-empty.json  |  2 +-
 tests/qapi-schema/alternate-empty.out   |  3 ---
 tests/qapi-schema/flat-union-empty.err  |  1 +
 tests/qapi-schema/flat-union-empty.exit |  2 +-
 tests/qapi-schema/flat-union-empty.json |  2 +-
 tests/qapi-schema/flat-union-empty.out  |  7 -------
 tests/qapi-schema/union-empty.err       |  1 +
 tests/qapi-schema/union-empty.exit      |  2 +-
 tests/qapi-schema/union-empty.json      |  2 +-
 tests/qapi-schema/union-empty.out       |  4 ----
 14 files changed, 27 insertions(+), 29 deletions(-)

diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index 842be1b..f0fb881 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -182,11 +182,11 @@ prevent incomplete include files.

 Usage: { 'struct': STRING, 'data': DICT, '*base': STRUCT-NAME }

-A struct is a dictionary containing a single 'data' key whose
-value is a dictionary.  This corresponds to a struct in C or an Object
-in JSON. Each value of the 'data' dictionary must be the name of a
-type, or a one-element array containing a type name.  An example of a
-struct is:
+A struct is a dictionary containing a single 'data' key whose value is
+a dictionary; the dictionary may be empty.  This corresponds to a
+struct in C or an Object in JSON. Each value of the 'data' dictionary
+must be the name of a type, or a one-element array containing a type
+name.  An example of a struct is:

  { 'struct': 'MyType',
    'data': { 'member1': 'str', 'member2': 'int', '*member3': 'str' } }
@@ -283,9 +283,10 @@ or:    { 'union': STRING, 'data': DICT, 'base': STRUCT-NAME,

 Union types are used to let the user choose between several different
 variants for an object.  There are two flavors: simple (no
-discriminator or base), flat (both discriminator and base).  A union
+discriminator or base), and flat (both discriminator and base).  A union
 type is defined using a data dictionary as explained in the following
-paragraphs.
+paragraphs.  The data dictionary for either type of union must not
+be empty.

 A simple union type defines a mapping from automatic discriminator
 values to data types like in this example:
diff --git a/scripts/qapi.py b/scripts/qapi.py
index 18cfde1..a93053d 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -573,7 +573,10 @@ def check_union(expr, expr_info):
                                 "Discriminator '%s' must be of enumeration "
                                 "type" % discriminator)

-    # Check every branch
+    # Check every branch; don't allow an empty union
+    if len(members) == 0:
+        raise QAPIExprError(expr_info,
+                            "Union '%s' cannot have empty 'data'" % name)
     for (key, value) in members.items():
         check_name(expr_info, "Member of union '%s'" % name, key)

@@ -606,7 +609,11 @@ def check_alternate(expr, expr_info):
     values = {'MAX': '(automatic)'}
     types_seen = {}

-    # Check every branch
+    # Check every branch; require at least two branches
+    if len(members) < 2:
+        raise QAPIExprError(expr_info,
+                            "Alternate '%s' should have at least two branches "
+                            "in 'data'" % name)
     for (key, value) in members.items():
         check_name(expr_info, "Member of alternate '%s'" % name, key)

@@ -1013,6 +1020,7 @@ class QAPISchemaObjectTypeVariants(object):
     def __init__(self, tag_name, tag_enum, variants):
         assert tag_name is None or isinstance(tag_name, str)
         assert tag_enum is None or isinstance(tag_enum, str)
+        assert len(variants) > 0
         for v in variants:
             assert isinstance(v, QAPISchemaObjectTypeVariant)
         self._tag_name = tag_name
diff --git a/tests/qapi-schema/alternate-empty.err b/tests/qapi-schema/alternate-empty.err
index e69de29..bb06c5b 100644
--- a/tests/qapi-schema/alternate-empty.err
+++ b/tests/qapi-schema/alternate-empty.err
@@ -0,0 +1 @@
+tests/qapi-schema/alternate-empty.json:2: Alternate 'Alt' should have at least two branches in 'data'
diff --git a/tests/qapi-schema/alternate-empty.exit b/tests/qapi-schema/alternate-empty.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/alternate-empty.exit
+++ b/tests/qapi-schema/alternate-empty.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/alternate-empty.json b/tests/qapi-schema/alternate-empty.json
index db3820f..fff15ba 100644
--- a/tests/qapi-schema/alternate-empty.json
+++ b/tests/qapi-schema/alternate-empty.json
@@ -1,2 +1,2 @@
-# FIXME - alternates should list at least two types to be useful
+# alternates must list at least two types to be useful
 { 'alternate': 'Alt', 'data': { 'i': 'int' } }
diff --git a/tests/qapi-schema/alternate-empty.out b/tests/qapi-schema/alternate-empty.out
index 9b010d8..e69de29 100644
--- a/tests/qapi-schema/alternate-empty.out
+++ b/tests/qapi-schema/alternate-empty.out
@@ -1,3 +0,0 @@
-object :empty
-alternate Alt
-    case i: int
diff --git a/tests/qapi-schema/flat-union-empty.err b/tests/qapi-schema/flat-union-empty.err
index e69de29..15754f5 100644
--- a/tests/qapi-schema/flat-union-empty.err
+++ b/tests/qapi-schema/flat-union-empty.err
@@ -0,0 +1 @@
+tests/qapi-schema/flat-union-empty.json:4: Union 'Union' cannot have empty 'data'
diff --git a/tests/qapi-schema/flat-union-empty.exit b/tests/qapi-schema/flat-union-empty.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/flat-union-empty.exit
+++ b/tests/qapi-schema/flat-union-empty.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/flat-union-empty.json b/tests/qapi-schema/flat-union-empty.json
index 67dd297..77f1d9a 100644
--- a/tests/qapi-schema/flat-union-empty.json
+++ b/tests/qapi-schema/flat-union-empty.json
@@ -1,4 +1,4 @@
-# FIXME - flat unions should not be empty
+# flat unions cannot be empty
 { 'enum': 'Empty', 'data': [ ] }
 { 'struct': 'Base', 'data': { 'type': 'Empty' } }
 { 'union': 'Union', 'base': 'Base', 'discriminator': 'type', 'data': { } }
diff --git a/tests/qapi-schema/flat-union-empty.out b/tests/qapi-schema/flat-union-empty.out
index 0e0665a..e69de29 100644
--- a/tests/qapi-schema/flat-union-empty.out
+++ b/tests/qapi-schema/flat-union-empty.out
@@ -1,7 +0,0 @@
-object :empty
-object Base
-    member type: Empty optional=False
-enum Empty []
-object Union
-    base Base
-    tag type
diff --git a/tests/qapi-schema/union-empty.err b/tests/qapi-schema/union-empty.err
index e69de29..12c2022 100644
--- a/tests/qapi-schema/union-empty.err
+++ b/tests/qapi-schema/union-empty.err
@@ -0,0 +1 @@
+tests/qapi-schema/union-empty.json:2: Union 'Union' cannot have empty 'data'
diff --git a/tests/qapi-schema/union-empty.exit b/tests/qapi-schema/union-empty.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/union-empty.exit
+++ b/tests/qapi-schema/union-empty.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/union-empty.json b/tests/qapi-schema/union-empty.json
index 1785007..1f0b13c 100644
--- a/tests/qapi-schema/union-empty.json
+++ b/tests/qapi-schema/union-empty.json
@@ -1,2 +1,2 @@
-# FIXME - unions should not be empty
+# unions cannot be empty
 { 'union': 'Union', 'data': { } }
diff --git a/tests/qapi-schema/union-empty.out b/tests/qapi-schema/union-empty.out
index c5500da..e69de29 100644
--- a/tests/qapi-schema/union-empty.out
+++ b/tests/qapi-schema/union-empty.out
@@ -1,4 +0,0 @@
-object :empty
-object Union
-    tag type
-enum UnionKind []
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 38/46] qapi: Drop useless 'data' member of unions
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (36 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 37/46] qapi: Forbid empty unions and useless alternates Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 39/46] qapi: Plumb in 'box' to qapi generator lower levels Eric Blake
                   ` (8 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Now that we no longer have any clients of the 'void *data'
member injected into unions, we can drop it.  Update the
testsuite to drop the negative test union-clash2, and
replace it with a positive test in qapi-schema-test that
proves that we no longer have a name collision.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi-types.py                   | 9 ---------
 tests/Makefile                          | 1 -
 tests/qapi-schema/qapi-schema-test.json | 2 +-
 tests/qapi-schema/qapi-schema-test.out  | 4 ++--
 tests/qapi-schema/union-clash2.err      | 0
 tests/qapi-schema/union-clash2.exit     | 1 -
 tests/qapi-schema/union-clash2.json     | 3 ---
 tests/qapi-schema/union-clash2.out      | 7 -------
 8 files changed, 3 insertions(+), 24 deletions(-)
 delete mode 100644 tests/qapi-schema/union-clash2.err
 delete mode 100644 tests/qapi-schema/union-clash2.exit
 delete mode 100644 tests/qapi-schema/union-clash2.json
 delete mode 100644 tests/qapi-schema/union-clash2.out

diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
index 5b9cb69..e7f7c36 100644
--- a/scripts/qapi-types.py
+++ b/scripts/qapi-types.py
@@ -116,17 +116,8 @@ struct %(c_name)s {
                      c_type=c_name(variants.tag_member.type.name))
         tag_name = variants.tag_member.name

-    # FIXME: What purpose does data serve, besides preventing a union that
-    # has a branch named 'data'? We use it in qapi-visit.py to decide
-    # whether to bypass the switch statement if visiting the discriminator
-    # failed; but since we 0-initialize structs, and cannot tell what
-    # branch of the union is in use if the discriminator is invalid, there
-    # should not be any data leaks even without a data pointer.  Or, if
-    # 'data' is merely added to guarantee we don't have an empty union,
-    # shouldn't we enforce that at .json parse time?
     ret += mcgen('''
     union { /* union tag is @%(c_name)s */
-        void *data;
 ''',
                  c_name=c_name(tag_name))

diff --git a/tests/Makefile b/tests/Makefile
index f351833..638bf50 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -335,7 +335,6 @@ qapi-schema += unicode-str.json
 qapi-schema += union-bad-branch.json
 qapi-schema += union-base-no-discriminator.json
 qapi-schema += union-clash.json
-qapi-schema += union-clash2.json
 qapi-schema += union-empty.json
 qapi-schema += union-invalid-base.json
 qapi-schema += union-max.json
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index b1292c5..97830a3 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -90,7 +90,7 @@
             'number': ['number'],
             'boolean': ['bool'],
             'string': ['str'],
-            'sizes': ['size'],
+            'data': ['size'],
             'any': ['any'] } }

 # testing commands
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index d9e8595..c1a5a02 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -126,9 +126,9 @@ object UserDefNativeListUnion
     case number: :obj-numberList-wrapper
     case boolean: :obj-boolList-wrapper
     case string: :obj-strList-wrapper
-    case sizes: :obj-sizeList-wrapper
+    case data: :obj-sizeList-wrapper
     case any: :obj-anyList-wrapper
-enum UserDefNativeListUnionKind ['integer', 's8', 's16', 's32', 's64', 'u8', 'u16', 'u32', 'u64', 'number', 'boolean', 'string', 'sizes', 'any']
+enum UserDefNativeListUnionKind ['integer', 's8', 's16', 's32', 's64', 'u8', 'u16', 'u32', 'u64', 'number', 'boolean', 'string', 'data', 'any']
 object UserDefOne
     base UserDefZero
     member string: str optional=False
diff --git a/tests/qapi-schema/union-clash2.err b/tests/qapi-schema/union-clash2.err
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/qapi-schema/union-clash2.exit b/tests/qapi-schema/union-clash2.exit
deleted file mode 100644
index 573541a..0000000
--- a/tests/qapi-schema/union-clash2.exit
+++ /dev/null
@@ -1 +0,0 @@
-0
diff --git a/tests/qapi-schema/union-clash2.json b/tests/qapi-schema/union-clash2.json
deleted file mode 100644
index b2d45fb..0000000
--- a/tests/qapi-schema/union-clash2.json
+++ /dev/null
@@ -1,3 +0,0 @@
-# FIXME - a union branch named 'data' collides with generated C code
-{ 'union': 'TestUnion',
-  'data': { 'data': 'int' } }
diff --git a/tests/qapi-schema/union-clash2.out b/tests/qapi-schema/union-clash2.out
deleted file mode 100644
index 689ee74..0000000
--- a/tests/qapi-schema/union-clash2.out
+++ /dev/null
@@ -1,7 +0,0 @@
-object :empty
-object :obj-int-wrapper
-    member data: int optional=False
-object TestUnion
-    tag type
-    case data: :obj-int-wrapper
-enum TestUnionKind ['data']
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 39/46] qapi: Plumb in 'box' to qapi generator lower levels
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (37 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 38/46] qapi: Drop useless 'data' member of unions Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 40/46] qapi: Implement boxed structs for commands/events Eric Blake
                   ` (7 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

A future patch will add support for passing a qapi union
type as the 'data' of a command.  But to do that, the user
function for implementing the command, as called by the
generated marshal command, must take the corresponding C
struct as a single boxed pointer, rather than a breakdown
into one parameter per member.  This patch adds the
internal plubming of a 'box' flag associated with each
command and event.  For this patch, no behavior changes,
other than the testsuite outputting the value of the new
flag (always False for now).

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi-commands.py                | 63 +++++++++++++++++++--------------
 scripts/qapi-event.py                   | 34 ++++++++++--------
 scripts/qapi-introspect.py              |  4 +--
 scripts/qapi.py                         | 51 +++++++++++++++-----------
 tests/qapi-schema/args-member-array.out |  2 +-
 tests/qapi-schema/event-case.out        |  1 +
 tests/qapi-schema/ident-with-escape.out |  2 +-
 tests/qapi-schema/indented-expr.out     |  4 +--
 tests/qapi-schema/qapi-schema-test.out  | 17 +++++----
 tests/qapi-schema/returns-int.out       |  2 +-
 tests/qapi-schema/test-qapi.py          |  8 +++--
 11 files changed, 111 insertions(+), 77 deletions(-)

diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
index 55287b1..d003e3c 100644
--- a/scripts/qapi-commands.py
+++ b/scripts/qapi-commands.py
@@ -16,24 +16,27 @@ from qapi import *
 import re


-def gen_command_decl(name, arg_type, ret_type):
+def gen_command_decl(name, arg_type, box, ret_type):
     return mcgen('''
 %(c_type)s qmp_%(c_name)s(%(params)s);
 ''',
                  c_type=(ret_type and ret_type.c_type()) or 'void',
                  c_name=c_name(name),
-                 params=gen_params(arg_type, 'Error **errp'))
+                 params=gen_params(arg_type, box, 'Error **errp'))


-def gen_call(name, arg_type, ret_type):
+def gen_call(name, arg_type, box, ret_type):
     ret = ''

     argstr = ''
-    if arg_type:
-        for memb in arg_type.members:
-            if memb.optional:
-                argstr += 'has_%s, ' % c_name(memb.name)
-            argstr += '%s, ' % c_name(memb.name)
+    if box:
+        assert False    # not implemented
+    else:
+        if arg_type:
+            for memb in arg_type.members:
+                if memb.optional:
+                    argstr += 'has_%s, ' % c_name(memb.name)
+                argstr += '%s, ' % c_name(memb.name)

     lhs = ''
     if ret_type:
@@ -56,7 +59,7 @@ qmp_marshal_output_%(c_name)s(retval, ret, &err);
     return ret


-def gen_marshal_vars(arg_type, ret_type):
+def gen_marshal_vars(arg_type, box, ret_type):
     ret = mcgen('''
     Error *err = NULL;
 ''')
@@ -76,18 +79,21 @@ QapiDeallocVisitor *md;
 Visitor *v;
 ''')

-        for memb in arg_type.members:
-            if memb.optional:
-                ret += mcgen('''
+        if box:
+            assert False    # not implemented
+        else:
+            for memb in arg_type.members:
+                if memb.optional:
+                    ret += mcgen('''
 bool has_%(c_name)s = false;
 ''',
-                             c_name=c_name(memb.name))
-            ret += mcgen('''
+                                 c_name=c_name(memb.name))
+                ret += mcgen('''
 %(c_type)s %(c_name)s = %(c_null)s;
 ''',
-                         c_name=c_name(memb.name),
-                         c_type=memb.type.c_type(),
-                         c_null=memb.type.c_null())
+                             c_name=c_name(memb.name),
+                             c_type=memb.type.c_type(),
+                             c_null=memb.type.c_null())
         ret += '\n'
     else:
         ret += mcgen('''
@@ -99,7 +105,7 @@ bool has_%(c_name)s = false;
     return ret


-def gen_marshal_input_visit(arg_type, dealloc=False):
+def gen_marshal_input_visit(arg_type, box, dealloc=False):
     ret = ''

     if not arg_type:
@@ -120,7 +126,10 @@ v = qapi_dealloc_get_visitor(md);
 v = qmp_input_get_visitor(mi);
 ''')

-    ret += gen_visit_fields(arg_type.members, '', False, errarg)
+    if box:
+        assert False    # not implemented
+    else:
+        ret += gen_visit_fields(arg_type.members, '', False, errarg)

     if dealloc:
         ret += mcgen('''
@@ -173,7 +182,7 @@ def gen_marshal_decl(name):
                  proto=gen_marshal_proto(name))


-def gen_marshal(name, arg_type, ret_type):
+def gen_marshal(name, arg_type, box, ret_type):
     ret = mcgen('''

 %(proto)s
@@ -181,9 +190,9 @@ def gen_marshal(name, arg_type, ret_type):
 ''',
                 proto=gen_marshal_proto(name))

-    ret += gen_marshal_vars(arg_type, ret_type)
-    ret += gen_marshal_input_visit(arg_type)
-    ret += gen_call(name, arg_type, ret_type)
+    ret += gen_marshal_vars(arg_type, box, ret_type)
+    ret += gen_marshal_input_visit(arg_type, box)
+    ret += gen_call(name, arg_type, box, ret_type)

     if re.search('^ *goto out;', ret, re.MULTILINE):
         ret += mcgen('''
@@ -193,7 +202,7 @@ out:
     ret += mcgen('''
     error_propagate(errp, err);
 ''')
-    ret += gen_marshal_input_visit(arg_type, dealloc=True)
+    ret += gen_marshal_input_visit(arg_type, box, dealloc=True)
     ret += mcgen('''
 }
 ''')
@@ -250,16 +259,16 @@ class QAPISchemaGenCommandVisitor(QAPISchemaVisitor):
         self._visited_ret_types = None

     def visit_command(self, name, info, arg_type, ret_type,
-                      gen, success_response):
+                      gen, success_response, box):
         if not gen:
             return
-        self.decl += gen_command_decl(name, arg_type, ret_type)
+        self.decl += gen_command_decl(name, arg_type, box, ret_type)
         if ret_type and ret_type not in self._visited_ret_types:
             self._visited_ret_types.add(ret_type)
             self.defn += gen_marshal_output(ret_type)
         if middle_mode:
             self.decl += gen_marshal_decl(name)
-        self.defn += gen_marshal(name, arg_type, ret_type)
+        self.defn += gen_marshal(name, arg_type, box, ret_type)
         if not middle_mode:
             self._regy += gen_register_command(name, success_response)

diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py
index 6c70a06..f732001 100644
--- a/scripts/qapi-event.py
+++ b/scripts/qapi-event.py
@@ -14,21 +14,21 @@
 from qapi import *


-def gen_event_send_proto(name, arg_type):
+def gen_event_send_proto(name, arg_type, box):
     return 'void qapi_event_send_%(c_name)s(%(param)s)' % {
         'c_name': c_name(name.lower()),
-        'param': gen_params(arg_type, 'Error **errp')}
+        'param': gen_params(arg_type, box, 'Error **errp')}


-def gen_event_send_decl(name, arg_type):
+def gen_event_send_decl(name, arg_type, box):
     return mcgen('''

 %(proto)s;
 ''',
-                 proto=gen_event_send_proto(name, arg_type))
+                 proto=gen_event_send_proto(name, arg_type, box))


-def gen_event_send(name, arg_type):
+def gen_event_send(name, arg_type, box):
     ret = mcgen('''

 %(proto)s
@@ -37,7 +37,7 @@ def gen_event_send(name, arg_type):
     Error *err = NULL;
     QMPEventFuncEmit emit;
 ''',
-                proto=gen_event_send_proto(name, arg_type))
+                proto=gen_event_send_proto(name, arg_type, box))

     if arg_type and arg_type.members:
         ret += mcgen('''
@@ -66,6 +66,12 @@ def gen_event_send(name, arg_type):
     v = qmp_output_get_visitor(qov);
     g_assert(v);

+''')
+
+        if box:
+            assert False     # not implemented
+        else:
+            ret += mcgen('''
     /* Fake visit, as if all members are under a structure */
     visit_start_struct(v, NULL, "", "%(name)s", 0, &err);
     if (err) {
@@ -73,11 +79,11 @@ def gen_event_send(name, arg_type):
     }

 ''',
-                     name=name)
-        push_indent()
-        ret += gen_visit_fields(arg_type.members, '', True, 'err')
-        pop_indent()
-        ret += mcgen('''
+                         name=name)
+            push_indent()
+            ret += gen_visit_fields(arg_type.members, '', True, 'err')
+            pop_indent()
+            ret += mcgen('''

     visit_end_struct(v, &err);
     if (err) {
@@ -125,9 +131,9 @@ class QAPISchemaGenEventVisitor(QAPISchemaVisitor):
         self.defn += gen_enum_lookup(event_enum_name, self._event_names)
         self._event_names = None

-    def visit_event(self, name, info, arg_type):
-        self.decl += gen_event_send_decl(name, arg_type)
-        self.defn += gen_event_send(name, arg_type)
+    def visit_event(self, name, info, arg_type, box):
+        self.decl += gen_event_send_decl(name, arg_type, box)
+        self.defn += gen_event_send(name, arg_type, box)
         self._event_names.append(name)


diff --git a/scripts/qapi-introspect.py b/scripts/qapi-introspect.py
index 2adefdd..8023f1b 100644
--- a/scripts/qapi-introspect.py
+++ b/scripts/qapi-introspect.py
@@ -154,14 +154,14 @@ const char %(c_name)s[] = %(c_string)s;
                                     for m in variants.variants]})

     def visit_command(self, name, info, arg_type, ret_type,
-                      gen, success_response):
+                      gen, success_response, box):
         arg_type = arg_type or self._schema.the_empty_object_type
         ret_type = ret_type or self._schema.the_empty_object_type
         self._gen_json(name, 'command',
                        {'arg-type': self._use_type(arg_type),
                         'ret-type': self._use_type(ret_type)})

-    def visit_event(self, name, info, arg_type):
+    def visit_event(self, name, info, arg_type, box):
         arg_type = arg_type or self._schema.the_empty_object_type
         self._gen_json(name, 'event', {'arg-type': self._use_type(arg_type)})

diff --git a/scripts/qapi.py b/scripts/qapi.py
index a93053d..fb941c9 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -809,10 +809,10 @@ class QAPISchemaVisitor(object):
         pass

     def visit_command(self, name, info, arg_type, ret_type,
-                      gen, success_response):
+                      gen, success_response, box):
         pass

-    def visit_event(self, name, info, arg_type):
+    def visit_event(self, name, info, arg_type, box):
         pass


@@ -1091,7 +1091,8 @@ class QAPISchemaAlternateType(QAPISchemaType):


 class QAPISchemaCommand(QAPISchemaEntity):
-    def __init__(self, name, info, arg_type, ret_type, gen, success_response):
+    def __init__(self, name, info, arg_type, ret_type, gen, success_response,
+                 box):
         QAPISchemaEntity.__init__(self, name, info)
         assert not arg_type or isinstance(arg_type, str)
         assert not ret_type or isinstance(ret_type, str)
@@ -1101,12 +1102,14 @@ class QAPISchemaCommand(QAPISchemaEntity):
         self.ret_type = None
         self.gen = gen
         self.success_response = success_response
+        self.box = box

     def check(self, schema):
         if self._arg_type_name:
             self.arg_type = schema.lookup_type(self._arg_type_name)
             assert isinstance(self.arg_type, QAPISchemaObjectType)
-            assert not self.arg_type.variants   # not implemented
+            if not self.box:
+                assert not self.arg_type.variants
         if self._ret_type_name:
             self.ret_type = schema.lookup_type(self._ret_type_name)
             assert isinstance(self.ret_type, QAPISchemaType)
@@ -1114,24 +1117,26 @@ class QAPISchemaCommand(QAPISchemaEntity):
     def visit(self, visitor):
         visitor.visit_command(self.name, self._info,
                               self.arg_type, self.ret_type,
-                              self.gen, self.success_response)
+                              self.gen, self.success_response, self.box)


 class QAPISchemaEvent(QAPISchemaEntity):
-    def __init__(self, name, info, arg_type):
+    def __init__(self, name, info, arg_type, box):
         QAPISchemaEntity.__init__(self, name, info)
         assert not arg_type or isinstance(arg_type, str)
         self._arg_type_name = arg_type
         self.arg_type = None
+        self.box = box

     def check(self, schema):
         if self._arg_type_name:
             self.arg_type = schema.lookup_type(self._arg_type_name)
             assert isinstance(self.arg_type, QAPISchemaObjectType)
-            assert not self.arg_type.variants   # not implemented
+            if not self.box:
+                assert not self.arg_type.variants

     def visit(self, visitor):
-        visitor.visit_event(self.name, self._info, self.arg_type)
+        visitor.visit_event(self.name, self._info, self.arg_type, self.box)


 class QAPISchema(object):
@@ -1289,7 +1294,8 @@ class QAPISchema(object):
         rets = expr.get('returns')
         gen = expr.get('gen', True)
         success_response = expr.get('success-response', True)
-        if isinstance(data, OrderedDict):
+        box = expr.get('box', False)
+        if isinstance(data, dict):
             owner = name + ' arguments'
             data = self._make_implicit_object_type(name, info, 'arg',
                                                    self._make_members(data,
@@ -1298,17 +1304,18 @@ class QAPISchema(object):
             assert len(rets) == 1
             rets = self._make_array_type(rets[0])
         self._def_entity(QAPISchemaCommand(name, info, data, rets, gen,
-                                           success_response))
+                                           success_response, box))

     def _def_event(self, expr, info):
         name = expr['event']
         data = expr.get('data')
-        if isinstance(data, OrderedDict):
+        box = expr.get('box', False)
+        if isinstance(data, dict):
             owner = name + ' data'
             data = self._make_implicit_object_type(name, info, 'arg',
                                                    self._make_members(data,
                                                                       owner))
-        self._def_entity(QAPISchemaEvent(name, info, data))
+        self._def_entity(QAPISchemaEvent(name, info, data, box))

     def _def_exprs(self):
         for expr_elem in self.exprs:
@@ -1551,18 +1558,22 @@ extern const char *const %(c_name)s_lookup[];
     return ret


-def gen_params(arg_type, extra):
+def gen_params(arg_type, box, extra):
     if not arg_type:
         return extra
-    assert not arg_type.variants
     ret = ''
     sep = ''
-    for memb in arg_type.members:
-        ret += sep
-        sep = ', '
-        if memb.optional:
-            ret += 'bool has_%s, ' % c_name(memb.name)
-        ret += '%s %s' % (memb.type.c_type(is_param=True), c_name(memb.name))
+    if box:
+        assert False     # not implemented
+    else:
+        assert not arg_type.variants
+        for memb in arg_type.members:
+            ret += sep
+            sep = ', '
+            if memb.optional:
+                ret += 'bool has_%s, ' % c_name(memb.name)
+            ret += '%s %s' % (memb.type.c_type(is_param=True),
+                              c_name(memb.name))
     if extra:
         ret += sep + extra
     return ret
diff --git a/tests/qapi-schema/args-member-array.out b/tests/qapi-schema/args-member-array.out
index b3b92df..f5dc409 100644
--- a/tests/qapi-schema/args-member-array.out
+++ b/tests/qapi-schema/args-member-array.out
@@ -6,4 +6,4 @@ enum abc ['a', 'b', 'c']
 object def
     member array: abcList optional=False
 command okay :obj-okay-arg -> None
-   gen=True success_response=True
+   gen=True success_response=True box=False
diff --git a/tests/qapi-schema/event-case.out b/tests/qapi-schema/event-case.out
index cdfd264..97a682a 100644
--- a/tests/qapi-schema/event-case.out
+++ b/tests/qapi-schema/event-case.out
@@ -1,2 +1,3 @@
 object :empty
 event oops None
+   box=False
diff --git a/tests/qapi-schema/ident-with-escape.out b/tests/qapi-schema/ident-with-escape.out
index f4542b1..2381c33 100644
--- a/tests/qapi-schema/ident-with-escape.out
+++ b/tests/qapi-schema/ident-with-escape.out
@@ -2,4 +2,4 @@ object :empty
 object :obj-fooA-arg
     member bar1: str optional=False
 command fooA :obj-fooA-arg -> None
-   gen=True success_response=True
+   gen=True success_response=True box=False
diff --git a/tests/qapi-schema/indented-expr.out b/tests/qapi-schema/indented-expr.out
index 226d300..f5d2695 100644
--- a/tests/qapi-schema/indented-expr.out
+++ b/tests/qapi-schema/indented-expr.out
@@ -1,5 +1,5 @@
 object :empty
 command eins None -> None
-   gen=True success_response=True
+   gen=True success_response=True box=False
 command zwei None -> None
-   gen=True success_response=True
+   gen=True success_response=True box=False
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index c1a5a02..6f39a92 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -72,9 +72,13 @@ alternate AltTwo
     case s: str
     case n: number
 event EVENT_A None
+   box=False
 event EVENT_B None
+   box=False
 event EVENT_C :obj-EVENT_C-arg
+   box=False
 event EVENT_D :obj-EVENT_D-arg
+   box=False
 enum EnumOne ['value1', 'value2', 'value3']
 object EventStructOne
     member struct1: UserDefOne optional=False
@@ -159,6 +163,7 @@ object UserDefUnionBase
 object UserDefZero
     member integer: int optional=False
 event __ORG.QEMU_X-EVENT __org.qemu_x-Struct
+   box=False
 alternate __org.qemu_x-Alt
     case __org.qemu_x-branch: str
     case b: __org.qemu_x-Base
@@ -179,14 +184,14 @@ object __org.qemu_x-Union2
     tag __org.qemu_x-member1
     case __org.qemu_x-value: __org.qemu_x-Struct2
 command __org.qemu_x-command :obj-__org.qemu_x-command-arg -> __org.qemu_x-Union1
-   gen=True success_response=True
+   gen=True success_response=True box=False
 command guest-sync :obj-guest-sync-arg -> any
-   gen=True success_response=True
+   gen=True success_response=True box=False
 command user_def_cmd None -> None
-   gen=True success_response=True
+   gen=True success_response=True box=False
 command user_def_cmd1 :obj-user_def_cmd1-arg -> None
-   gen=True success_response=True
+   gen=True success_response=True box=False
 command user_def_cmd2 :obj-user_def_cmd2-arg -> UserDefTwo
-   gen=True success_response=True
+   gen=True success_response=True box=False
 command user_def_cmd3 :obj-user_def_cmd3-arg -> int
-   gen=True success_response=True
+   gen=True success_response=True box=False
diff --git a/tests/qapi-schema/returns-int.out b/tests/qapi-schema/returns-int.out
index a2da259..911212d 100644
--- a/tests/qapi-schema/returns-int.out
+++ b/tests/qapi-schema/returns-int.out
@@ -1,3 +1,3 @@
 object :empty
 command guest-get-time None -> int
-   gen=True success_response=True
+   gen=True success_response=True box=False
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index 3f0c2bc..559f0e8 100644
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -36,13 +36,15 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
         self._print_variants(variants)

     def visit_command(self, name, info, arg_type, ret_type,
-                      gen, success_response):
+                      gen, success_response, box):
         print 'command %s %s -> %s' % \
             (name, arg_type and arg_type.name, ret_type and ret_type.name)
-        print '   gen=%s success_response=%s' % (gen, success_response)
+        print '   gen=%s success_response=%s box=%s' % (gen, success_response,
+                                                        box)

-    def visit_event(self, name, info, arg_type):
+    def visit_event(self, name, info, arg_type, box):
         print 'event %s %s' % (name, arg_type and arg_type.name)
+        print '   box=%s' % box

     @staticmethod
     def _print_variants(variants):
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 40/46] qapi: Implement boxed structs for commands/events
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (38 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 39/46] qapi: Plumb in 'box' to qapi generator lower levels Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 41/46] qapi: Support boxed unions Eric Blake
                   ` (6 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Turn on the ability to pass command and event arguments in
a single boxed parameter.  This patch merely tests the use
of the feature on structs.  With this patch, we still reject
union types, and crash on { 'command':'foo', 'data': {
anonymous...}, 'box':true }; that will be addressed in the
next patch.

While this does not alter any QMP commands, it opens the door
for clients to turn on boxing to receive a single pointer
parameter of the overall struct instead of a series of
parameters for each member of the struct; which will be
useful for commands whose argument struct has lots of members.
The generated code is slightly reorganized for convenience.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 docs/qapi-code-gen.txt                  | 24 ++++++++++++++++++++++--
 scripts/qapi-commands.py                | 16 +++++++++++++---
 scripts/qapi-event.py                   | 13 +++++++++++--
 scripts/qapi.py                         | 11 ++++++++---
 tests/qapi-schema/qapi-schema-test.json |  2 ++
 tests/qapi-schema/qapi-schema-test.out  |  4 ++++
 tests/test-qmp-commands.c               |  4 ++++
 7 files changed, 64 insertions(+), 10 deletions(-)

diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index f0fb881..49e3586 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -404,7 +404,7 @@ following example objects:
 === Commands ===

 Usage: { 'command': STRING, '*data': COMPLEX-TYPE-NAME-OR-DICT,
-         '*returns': TYPE-NAME,
+         '*returns': TYPE-NAME, '*box': true,
          '*gen': false, '*success-response': false }

 Commands are defined by using a dictionary containing several members,
@@ -455,6 +455,17 @@ which would validate this Client JSON Protocol transaction:
  => { "execute": "my-second-command" }
  <= { "return": [ { "value": "one" }, { } ] }

+By default, the generator creates a marshalling function that converts
+an input QDict into a function call implemented by the user, and
+declares a prototype for the user's function which has a parameter for
+each member of the argument struct, including boolean arguments that
+describe whether optional arguments were provided.  But if the QAPI
+description includes the key 'box' with the boolean value true, the
+user call prototype will have only a single parameter for the overall
+generated C structure.  The 'box' key is required in order to use a
+union as an input argument, since it is not possible to list all
+members of the union as separate parameters.
+
 In rare cases, QAPI cannot express a type-safe representation of a
 corresponding Client JSON Protocol command.  You then have to suppress
 generation of a marshalling function by including a key 'gen' with
@@ -478,7 +489,8 @@ use of this field.

 === Events ===

-Usage: { 'event': STRING, '*data': COMPLEX-TYPE-NAME-OR-DICT }
+Usage: { 'event': STRING, '*data': COMPLEX-TYPE-NAME-OR-DICT,
+         '*box': true }

 Events are defined with the keyword 'event'.  It is not allowed to
 name an event 'MAX', since the generator also produces a C enumeration
@@ -499,6 +511,14 @@ Resulting in this JSON object:
   "data": { "b": "test string" },
   "timestamp": { "seconds": 1267020223, "microseconds": 435656 } }

+By default, the generator creates a C function that takes as
+parameters each member of the argument struct and turns it into the
+appropriate JSON Client event.  But if the QAPI description includes
+the key 'box' with the boolean value true, the event function will
+have only a single parameter for the overall generated C structure.
+The 'box' key is required in order to use a union as the data key,
+since it is not possible to list all members of the union as separate
+parameters.

 == Client JSON Protocol introspection ==

diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
index d003e3c..0256c27 100644
--- a/scripts/qapi-commands.py
+++ b/scripts/qapi-commands.py
@@ -30,7 +30,7 @@ def gen_call(name, arg_type, box, ret_type):

     argstr = ''
     if box:
-        assert False    # not implemented
+        argstr = 'arg, '
     else:
         if arg_type:
             for memb in arg_type.members:
@@ -80,7 +80,10 @@ Visitor *v;
 ''')

         if box:
-            assert False    # not implemented
+            ret += mcgen('''
+%(c_type)s arg = %(c_null)s;
+''',
+                         c_type=arg_type.c_type(), c_null=arg_type.c_null())
         else:
             for memb in arg_type.members:
                 if memb.optional:
@@ -115,6 +118,7 @@ def gen_marshal_input_visit(arg_type, box, dealloc=False):

     if dealloc:
         errarg = None
+        errparg = 'NULL'
         ret += mcgen('''
 qmp_input_visitor_cleanup(mi);
 md = qapi_dealloc_visitor_new();
@@ -122,12 +126,18 @@ v = qapi_dealloc_get_visitor(md);
 ''')
     else:
         errarg = 'err'
+        errparg = '&err'
         ret += mcgen('''
 v = qmp_input_get_visitor(mi);
 ''')

     if box:
-        assert False    # not implemented
+        ret += mcgen('''
+visit_type_%(c_name)s(v, &arg, NULL, %(errp)s);
+
+''',
+                     c_name=arg_type.c_name(), errp=errparg)
+        ret += gen_err_check(errarg)
     else:
         ret += gen_visit_fields(arg_type.members, '', False, errarg)

diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py
index f732001..b46989c 100644
--- a/scripts/qapi-event.py
+++ b/scripts/qapi-event.py
@@ -40,10 +40,13 @@ def gen_event_send(name, arg_type, box):
                 proto=gen_event_send_proto(name, arg_type, box))

     if arg_type and arg_type.members:
+        if not box:
+            ret += mcgen('''
+    QObject *obj;
+''')
         ret += mcgen('''
     QmpOutputVisitor *qov;
     Visitor *v;
-    QObject *obj;

 ''')

@@ -69,7 +72,13 @@ def gen_event_send(name, arg_type, box):
 ''')

         if box:
-            assert False     # not implemented
+            ret += mcgen('''
+    visit_type_%(c_name)s(v, &arg, NULL, &err);
+    if (err) {
+        goto out;
+    }
+''',
+                         c_name=arg_type.c_name(), name=arg_type.name)
         else:
             ret += mcgen('''
     /* Fake visit, as if all members are under a structure */
diff --git a/scripts/qapi.py b/scripts/qapi.py
index fb941c9..a54ff64 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -689,6 +689,10 @@ def check_keys(expr_elem, meta, required, optional=[]):
             raise QAPIExprError(info,
                                 "'%s' of %s '%s' should only use false value"
                                 % (key, meta, name))
+        if key == 'box' and value is not True:
+            raise QAPIExprError(info,
+                                "'%s' of %s '%s' should only use true value"
+                                % (key, meta, name))
     for key in required:
         if key not in expr:
             raise QAPIExprError(info,
@@ -720,10 +724,10 @@ def check_exprs(exprs):
             add_struct(expr, info)
         elif 'command' in expr:
             check_keys(expr_elem, 'command', [],
-                       ['data', 'returns', 'gen', 'success-response'])
+                       ['data', 'returns', 'gen', 'success-response', 'box'])
             add_name(expr['command'], info, 'command')
         elif 'event' in expr:
-            check_keys(expr_elem, 'event', [], ['data'])
+            check_keys(expr_elem, 'event', [], ['data', 'box'])
             add_name(expr['event'], info, 'event')
         else:
             raise QAPIExprError(expr_elem['info'],
@@ -1564,7 +1568,8 @@ def gen_params(arg_type, box, extra):
     ret = ''
     sep = ''
     if box:
-        assert False     # not implemented
+        ret += '%s arg' % arg_type.c_type(is_param=True)
+        sep = ', '
     else:
         assert not arg_type.variants
         for memb in arg_type.members:
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index 97830a3..f0f5ad2 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -103,6 +103,7 @@
   'returns': 'int' }
 # note: command name 'guest-sync' chosen to avoid "cannot use built-in" error
 { 'command': 'guest-sync', 'data': { 'arg': 'any' }, 'returns': 'any' }
+{ 'command': 'boxed', 'box': true, 'data': 'UserDefZero' }

 # For testing integer range flattening in opts-visitor. The following schema
 # corresponds to the option format:
@@ -130,6 +131,7 @@
   'data': { '*a': 'int', '*b': 'UserDefOne', 'c': 'str' } }
 { 'event': 'EVENT_D',
   'data': { 'a' : 'EventStructOne', 'b' : 'str', '*c': 'str', '*enum3': 'EnumOne' } }
+{ 'event': 'EVENT_E', 'box': true, 'data': 'UserDefZero' }

 # test that we correctly compile downstream extensions
 { 'enum': '__org.qemu_x-Enum', 'data': [ '__org.qemu_x-value' ] }
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 6f39a92..fb58d54 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -79,6 +79,8 @@ event EVENT_C :obj-EVENT_C-arg
    box=False
 event EVENT_D :obj-EVENT_D-arg
    box=False
+event EVENT_E UserDefZero
+   box=True
 enum EnumOne ['value1', 'value2', 'value3']
 object EventStructOne
     member struct1: UserDefOne optional=False
@@ -185,6 +187,8 @@ object __org.qemu_x-Union2
     case __org.qemu_x-value: __org.qemu_x-Struct2
 command __org.qemu_x-command :obj-__org.qemu_x-command-arg -> __org.qemu_x-Union1
    gen=True success_response=True box=False
+command boxed UserDefZero -> None
+   gen=True success_response=True box=True
 command guest-sync :obj-guest-sync-arg -> any
    gen=True success_response=True box=False
 command user_def_cmd None -> None
diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c
index 89a3b47..a190f31 100644
--- a/tests/test-qmp-commands.c
+++ b/tests/test-qmp-commands.c
@@ -54,6 +54,10 @@ QObject *qmp_guest_sync(QObject *arg, Error **errp)
     return arg;
 }

+void qmp_boxed(UserDefZero *arg, Error **errp)
+{
+}
+
 __org_qemu_x_Union1 *qmp___org_qemu_x_command(__org_qemu_x_EnumList *a,
                                               __org_qemu_x_StructList *b,
                                               __org_qemu_x_Union2 *c,
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 41/46] qapi: Support boxed unions
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (39 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 40/46] qapi: Implement boxed structs for commands/events Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 42/46] qapi: support implicit structs in OptsVisitor Eric Blake
                   ` (5 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

This patch completes support for boxed types, by allowing
union types to be used when 'box':true is specified.  It also
avoids a python crash when attempting to use boxing on an
anonymous type.  While it was possible to support 'box':true
on an empty event, it was easier to just reject missing 'data'
for commands.  Add some tests to cover new error messages, as
well as enhancing qapi-schema-test to cover situations that
are now valid.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 scripts/qapi.py                         | 22 +++++++++++++++++-----
 tests/Makefile                          |  3 +++
 tests/qapi-schema/args-bad-box.err      |  1 +
 tests/qapi-schema/args-bad-box.exit     |  1 +
 tests/qapi-schema/args-bad-box.json     |  2 ++
 tests/qapi-schema/args-bad-box.out      |  0
 tests/qapi-schema/args-box-anon.err     |  1 +
 tests/qapi-schema/args-box-anon.exit    |  1 +
 tests/qapi-schema/args-box-anon.json    |  2 ++
 tests/qapi-schema/args-box-anon.out     |  0
 tests/qapi-schema/args-box-empty.err    |  1 +
 tests/qapi-schema/args-box-empty.exit   |  1 +
 tests/qapi-schema/args-box-empty.json   |  2 ++
 tests/qapi-schema/args-box-empty.out    |  0
 tests/qapi-schema/args-union.err        |  2 +-
 tests/qapi-schema/args-union.json       |  3 +--
 tests/qapi-schema/qapi-schema-test.json |  3 ++-
 tests/qapi-schema/qapi-schema-test.out  |  4 +++-
 tests/test-qmp-commands.c               |  4 ++++
 19 files changed, 43 insertions(+), 10 deletions(-)
 create mode 100644 tests/qapi-schema/args-bad-box.err
 create mode 100644 tests/qapi-schema/args-bad-box.exit
 create mode 100644 tests/qapi-schema/args-bad-box.json
 create mode 100644 tests/qapi-schema/args-bad-box.out
 create mode 100644 tests/qapi-schema/args-box-anon.err
 create mode 100644 tests/qapi-schema/args-box-anon.exit
 create mode 100644 tests/qapi-schema/args-box-anon.json
 create mode 100644 tests/qapi-schema/args-box-anon.out
 create mode 100644 tests/qapi-schema/args-box-empty.err
 create mode 100644 tests/qapi-schema/args-box-empty.exit
 create mode 100644 tests/qapi-schema/args-box-empty.json
 create mode 100644 tests/qapi-schema/args-box-empty.out

diff --git a/scripts/qapi.py b/scripts/qapi.py
index a54ff64..66d9000 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -501,10 +501,18 @@ def check_type(expr_info, source, value, allow_array=False,

 def check_command(expr, expr_info):
     name = expr['command']
+    box = expr.get('box', False)

+    args_meta = ['struct']
+    if box:
+        args_meta += ['union']
+        if not expr.get('data'):
+            raise QAPIExprError(expr_info,
+                                "Use of 'box' requires 'data' for command '%s'"
+                                % name)
     check_type(expr_info, "'data' for command '%s'" % name,
-               expr.get('data'), allow_dict=True, allow_optional=True,
-               allow_metas=['struct'])
+               expr.get('data'), allow_dict=not box, allow_optional=True,
+               allow_metas=args_meta)
     returns_meta = ['union', 'struct']
     if name in returns_whitelist:
         returns_meta += ['built-in', 'alternate', 'enum']
@@ -516,13 +524,17 @@ def check_command(expr, expr_info):
 def check_event(expr, expr_info):
     global events
     name = expr['event']
+    box = expr.get('box', False)
+    meta = ['struct']

+    if box:
+        meta += ['union']
     if name.upper() == 'MAX':
         raise QAPIExprError(expr_info, "Event name 'MAX' cannot be created")
     events.append(name)
     check_type(expr_info, "'data' for event '%s'" % name,
-               expr.get('data'), allow_dict=True, allow_optional=True,
-               allow_metas=['struct'])
+               expr.get('data'), allow_dict=not box, allow_optional=True,
+               allow_metas=meta)


 def check_union(expr, expr_info):
@@ -1567,7 +1579,7 @@ def gen_params(arg_type, box, extra):
         return extra
     ret = ''
     sep = ''
-    if box:
+    if box and arg_type.members:
         ret += '%s arg' % arg_type.c_type(is_param=True)
         sep = ', '
     else:
diff --git a/tests/Makefile b/tests/Makefile
index 638bf50..8ce3665 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -234,6 +234,9 @@ qapi-schema += args-alternate.json
 qapi-schema += args-any.json
 qapi-schema += args-array-empty.json
 qapi-schema += args-array-unknown.json
+qapi-schema += args-bad-box.json
+qapi-schema += args-box-anon.json
+qapi-schema += args-box-empty.json
 qapi-schema += args-int.json
 qapi-schema += args-invalid.json
 qapi-schema += args-member-array-bad.json
diff --git a/tests/qapi-schema/args-bad-box.err b/tests/qapi-schema/args-bad-box.err
new file mode 100644
index 0000000..16afe3c
--- /dev/null
+++ b/tests/qapi-schema/args-bad-box.err
@@ -0,0 +1 @@
+tests/qapi-schema/args-bad-box.json:2: 'box' of command 'foo' should only use true value
diff --git a/tests/qapi-schema/args-bad-box.exit b/tests/qapi-schema/args-bad-box.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/args-bad-box.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/args-bad-box.json b/tests/qapi-schema/args-bad-box.json
new file mode 100644
index 0000000..8d5737a
--- /dev/null
+++ b/tests/qapi-schema/args-bad-box.json
@@ -0,0 +1,2 @@
+# 'box' should only appear with value true
+{ 'command': 'foo', 'box': false }
diff --git a/tests/qapi-schema/args-bad-box.out b/tests/qapi-schema/args-bad-box.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/args-box-anon.err b/tests/qapi-schema/args-box-anon.err
new file mode 100644
index 0000000..11eaefc
--- /dev/null
+++ b/tests/qapi-schema/args-box-anon.err
@@ -0,0 +1 @@
+tests/qapi-schema/args-box-anon.json:2: 'data' for command 'foo' should be a type name
diff --git a/tests/qapi-schema/args-box-anon.exit b/tests/qapi-schema/args-box-anon.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/args-box-anon.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/args-box-anon.json b/tests/qapi-schema/args-box-anon.json
new file mode 100644
index 0000000..947e3c6
--- /dev/null
+++ b/tests/qapi-schema/args-box-anon.json
@@ -0,0 +1,2 @@
+# 'box' can only be used with named types
+{ 'command': 'foo', 'box': true, 'data': { 'string': 'str' } }
diff --git a/tests/qapi-schema/args-box-anon.out b/tests/qapi-schema/args-box-anon.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/args-box-empty.err b/tests/qapi-schema/args-box-empty.err
new file mode 100644
index 0000000..574c8b5
--- /dev/null
+++ b/tests/qapi-schema/args-box-empty.err
@@ -0,0 +1 @@
+tests/qapi-schema/args-box-empty.json:2: Use of 'box' requires 'data' for command 'foo'
diff --git a/tests/qapi-schema/args-box-empty.exit b/tests/qapi-schema/args-box-empty.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/args-box-empty.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/args-box-empty.json b/tests/qapi-schema/args-box-empty.json
new file mode 100644
index 0000000..31dca5e
--- /dev/null
+++ b/tests/qapi-schema/args-box-empty.json
@@ -0,0 +1,2 @@
+# 'box' can only be used with named types
+{ 'command': 'foo', 'box': true }
diff --git a/tests/qapi-schema/args-box-empty.out b/tests/qapi-schema/args-box-empty.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/args-union.err b/tests/qapi-schema/args-union.err
index 1d693d7..f8ad223 100644
--- a/tests/qapi-schema/args-union.err
+++ b/tests/qapi-schema/args-union.err
@@ -1 +1 @@
-tests/qapi-schema/args-union.json:4: 'data' for command 'oops' cannot use union type 'Uni'
+tests/qapi-schema/args-union.json:3: 'data' for command 'oops' cannot use union type 'Uni'
diff --git a/tests/qapi-schema/args-union.json b/tests/qapi-schema/args-union.json
index 7bdcbb7..c0ce091 100644
--- a/tests/qapi-schema/args-union.json
+++ b/tests/qapi-schema/args-union.json
@@ -1,4 +1,3 @@
-# we do not allow union arguments
-# TODO should we support this?
+# use of union arguments requires 'box':true
 { 'union': 'Uni', 'data': { 'case1': 'int', 'case2': 'str' } }
 { 'command': 'oops', 'data': 'Uni' }
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index f0f5ad2..4d7fb07 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -104,6 +104,7 @@
 # note: command name 'guest-sync' chosen to avoid "cannot use built-in" error
 { 'command': 'guest-sync', 'data': { 'arg': 'any' }, 'returns': 'any' }
 { 'command': 'boxed', 'box': true, 'data': 'UserDefZero' }
+{ 'command': 'boxed2', 'data': 'UserDefNativeListUnion', 'box': true }

 # For testing integer range flattening in opts-visitor. The following schema
 # corresponds to the option format:
@@ -124,7 +125,7 @@
 { 'struct': 'EventStructOne',
   'data': { 'struct1': 'UserDefOne', 'string': 'str', '*enum2': 'EnumOne' } }

-{ 'event': 'EVENT_A' }
+{ 'event': 'EVENT_A', 'box': true }
 { 'event': 'EVENT_B',
   'data': { } }
 { 'event': 'EVENT_C',
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index fb58d54..a8730fc 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -72,7 +72,7 @@ alternate AltTwo
     case s: str
     case n: number
 event EVENT_A None
-   box=False
+   box=True
 event EVENT_B None
    box=False
 event EVENT_C :obj-EVENT_C-arg
@@ -189,6 +189,8 @@ command __org.qemu_x-command :obj-__org.qemu_x-command-arg -> __org.qemu_x-Union
    gen=True success_response=True box=False
 command boxed UserDefZero -> None
    gen=True success_response=True box=True
+command boxed2 UserDefNativeListUnion -> None
+   gen=True success_response=True box=True
 command guest-sync :obj-guest-sync-arg -> any
    gen=True success_response=True box=False
 command user_def_cmd None -> None
diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c
index a190f31..886de98 100644
--- a/tests/test-qmp-commands.c
+++ b/tests/test-qmp-commands.c
@@ -58,6 +58,10 @@ void qmp_boxed(UserDefZero *arg, Error **errp)
 {
 }

+void qmp_boxed2(UserDefNativeListUnion *arg, Error **errp)
+{
+}
+
 __org_qemu_x_Union1 *qmp___org_qemu_x_command(__org_qemu_x_EnumList *a,
                                               __org_qemu_x_StructList *b,
                                               __org_qemu_x_Union2 *c,
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 42/46] qapi: support implicit structs in OptsVisitor
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (40 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 41/46] qapi: Support boxed unions Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 43/46] qapi: Change Netdev into a flat union Eric Blake
                   ` (4 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel
  Cc: Michael Roth, marcandre.lureau,
	Kővágó, Zoltán, armbru, ehabkost

From: Kővágó, Zoltán <dirty.ice.hu@gmail.com>

They are required for flat unions (you still have to allocate the
structs).

Signed-off-by: Kővágó, Zoltán <DirtY.iCE.hu@gmail.com>
Message-Id: <88451f26df139c09b56b1525f3c5afeea43dd3db.1441627175.git.DirtY.iCE.hu@gmail.com>
Signed-off-by: Eric Blake <eblake@redhat.com>
---
 qapi/opts-visitor.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/qapi/opts-visitor.c b/qapi/opts-visitor.c
index b2cd5b3..900b2fa 100644
--- a/qapi/opts-visitor.c
+++ b/qapi/opts-visitor.c
@@ -149,6 +149,12 @@ opts_start_struct(Visitor *v, void **obj, const char *kind,
     }
 }

+static void
+opts_start_implicit_struct(Visitor *v, void **obj, size_t size, Error **errp)
+{
+    opts_start_struct(v, obj, NULL, NULL, size, errp);
+}
+

 static gboolean
 ghr_true(gpointer ign_key, gpointer ign_value, gpointer ign_user_data)
@@ -185,6 +191,12 @@ opts_end_struct(Visitor *v, Error **errp)
     ov->fake_id_opt = NULL;
 }

+static void
+opts_end_implicit_struct(Visitor *v, Error **errp)
+{
+    opts_end_struct(v, errp);
+}
+

 static GQueue *
 lookup_distinct(const OptsVisitor *ov, const char *name, Error **errp)
@@ -508,6 +520,9 @@ opts_visitor_new(const QemuOpts *opts)
     ov->visitor.start_struct = &opts_start_struct;
     ov->visitor.end_struct   = &opts_end_struct;

+    ov->visitor.start_implicit_struct = &opts_start_implicit_struct;
+    ov->visitor.end_implicit_struct = &opts_end_implicit_struct;
+
     ov->visitor.start_list = &opts_start_list;
     ov->visitor.next_list  = &opts_next_list;
     ov->visitor.end_list   = &opts_end_list;
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 43/46] qapi: Change Netdev into a flat union
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (41 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 42/46] qapi: support implicit structs in OptsVisitor Eric Blake
@ 2015-09-21 21:57 ` Eric Blake
  2015-09-21 21:58 ` [Qemu-devel] [PATCH v5 44/46] net: Use correct type for bool flag Eric Blake
                   ` (3 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:57 UTC (permalink / raw
  To: qemu-devel
  Cc: Peter Maydell, Michael S. Tsirkin, Jason Wang, Vincenzo Maffione,
	armbru, Max Filippov, Gerd Hoffmann, Dmitry Fleytman,
	Edgar E. Iglesias, Rob Herring, Alexander Graf, Scott Feldman,
	Kővágó, Zoltán, marcandre.lureau, Jiri Pirko,
	ehabkost, Alistair Francis, Jan Kiszka, Giuseppe Lettieri,
	Luiz Capitulino, Luigi Rizzo, David Gibson, Peter Crosthwaite,
	Michael Walle, open list:sPAPR pseries, Peter Chubb

From: Kővágó, Zoltán <dirty.ice.hu@gmail.com>

Except qapi-schema.json, this patch was generated by:

find . -name .git -prune -o -type f \! -name '*~' -print0 | \
  xargs -0 sed -i \
    -e 's/NetClientOptionsKind/NetClientDriver/g' \
    -e 's/NET_CLIENT_OPTIONS_KIND_/NET_CLIENT_DRIVER_/g' \
    -e 's/netdev->opts/netdev/g'

Signed-off-by: Kővágó, Zoltán <DirtY.iCE.hu@gmail.com>
Message-Id: <01a527fbf1a5de880091f98cf011616a78adeeee.1441627176.git.DirtY.iCE.hu@gmail.com>

Additional changes:
Rebase the patch on top of an earlier change from netdev->kind to
netdev->type, so that tweak no longer needed here.

Rework so that NetdevLegacy doesn't pollute QMP command but is instead
copied piecewise into the new Netdev, which means that NetClientOptions
must still remain in qapi. Since legacy previously always rejected
'hubport', we can now make that explicit by having the two unions be
slightly different; but that means we must manually map between the
two structures.
Signed-off-by: Eric Blake <eblake@redhat.com>
---
 hw/arm/musicpal.c                |   2 +-
 hw/core/qdev-properties-system.c |   2 +-
 hw/net/allwinner_emac.c          |   2 +-
 hw/net/cadence_gem.c             |   2 +-
 hw/net/dp8393x.c                 |   2 +-
 hw/net/e1000.c                   |   2 +-
 hw/net/eepro100.c                |   2 +-
 hw/net/etraxfs_eth.c             |   2 +-
 hw/net/fsl_etsec/etsec.c         |   2 +-
 hw/net/imx_fec.c                 |   2 +-
 hw/net/lan9118.c                 |   2 +-
 hw/net/lance.c                   |   2 +-
 hw/net/mcf_fec.c                 |   2 +-
 hw/net/milkymist-minimac2.c      |   2 +-
 hw/net/mipsnet.c                 |   2 +-
 hw/net/ne2000-isa.c              |   2 +-
 hw/net/ne2000.c                  |   2 +-
 hw/net/opencores_eth.c           |   2 +-
 hw/net/pcnet-pci.c               |   2 +-
 hw/net/rocker/rocker_fp.c        |   2 +-
 hw/net/rtl8139.c                 |   2 +-
 hw/net/smc91c111.c               |   2 +-
 hw/net/spapr_llan.c              |   2 +-
 hw/net/stellaris_enet.c          |   2 +-
 hw/net/vhost_net.c               |  18 +++---
 hw/net/virtio-net.c              |   6 +-
 hw/net/vmxnet3.c                 |   2 +-
 hw/net/xen_nic.c                 |   2 +-
 hw/net/xgmac.c                   |   2 +-
 hw/net/xilinx_axienet.c          |   2 +-
 hw/net/xilinx_ethlite.c          |   2 +-
 hw/usb/dev-network.c             |   2 +-
 include/net/net.h                |   4 +-
 monitor.c                        |  14 ++---
 net/dump.c                       |   6 +-
 net/hub.c                        |  22 +++----
 net/l2tpv3.c                     |   6 +-
 net/net.c                        | 133 +++++++++++++++++++++++++--------------
 net/netmap.c                     |   4 +-
 net/slirp.c                      |   6 +-
 net/socket.c                     |   8 +--
 net/tap-win32.c                  |   6 +-
 net/tap.c                        |  24 +++----
 net/vde.c                        |   6 +-
 net/vhost-user.c                 |  12 ++--
 qapi-schema.json                 |  57 +++++++++++++----
 46 files changed, 230 insertions(+), 162 deletions(-)

diff --git a/hw/arm/musicpal.c b/hw/arm/musicpal.c
index b534bb9..527b703 100644
--- a/hw/arm/musicpal.c
+++ b/hw/arm/musicpal.c
@@ -374,7 +374,7 @@ static void eth_cleanup(NetClientState *nc)
 }

 static NetClientInfo net_mv88w8618_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = eth_receive,
     .cleanup = eth_cleanup,
diff --git a/hw/core/qdev-properties-system.c b/hw/core/qdev-properties-system.c
index 921e799..249976e 100644
--- a/hw/core/qdev-properties-system.c
+++ b/hw/core/qdev-properties-system.c
@@ -229,7 +229,7 @@ static void set_netdev(Object *obj, Visitor *v, void *opaque,
     }

     queues = qemu_find_net_clients_except(str, peers,
-                                          NET_CLIENT_OPTIONS_KIND_NIC,
+                                          NET_CLIENT_DRIVER_NIC,
                                           MAX_QUEUE_NUM);
     if (queues == 0) {
         err = -ENOENT;
diff --git a/hw/net/allwinner_emac.c b/hw/net/allwinner_emac.c
index 0407dee..4fdf824 100644
--- a/hw/net/allwinner_emac.c
+++ b/hw/net/allwinner_emac.c
@@ -422,7 +422,7 @@ static const MemoryRegionOps aw_emac_mem_ops = {
 };

 static NetClientInfo net_aw_emac_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .can_receive = aw_emac_can_receive,
     .receive = aw_emac_receive,
diff --git a/hw/net/cadence_gem.c b/hw/net/cadence_gem.c
index 1127223..9c4773e 100644
--- a/hw/net/cadence_gem.c
+++ b/hw/net/cadence_gem.c
@@ -1175,7 +1175,7 @@ static void gem_set_link(NetClientState *nc)
 }

 static NetClientInfo net_gem_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .can_receive = gem_can_receive,
     .receive = gem_receive,
diff --git a/hw/net/dp8393x.c b/hw/net/dp8393x.c
index ab607e4..fb57900 100644
--- a/hw/net/dp8393x.c
+++ b/hw/net/dp8393x.c
@@ -810,7 +810,7 @@ static void dp8393x_reset(DeviceState *dev)
 }

 static NetClientInfo net_dp83932_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .can_receive = dp8393x_can_receive,
     .receive = dp8393x_receive,
diff --git a/hw/net/e1000.c b/hw/net/e1000.c
index 09c9e9d..d699fb3 100644
--- a/hw/net/e1000.c
+++ b/hw/net/e1000.c
@@ -1516,7 +1516,7 @@ pci_e1000_uninit(PCIDevice *dev)
 }

 static NetClientInfo net_e1000_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .can_receive = e1000_can_receive,
     .receive = e1000_receive,
diff --git a/hw/net/eepro100.c b/hw/net/eepro100.c
index 60333b7..28552f7 100644
--- a/hw/net/eepro100.c
+++ b/hw/net/eepro100.c
@@ -1832,7 +1832,7 @@ static void pci_nic_uninit(PCIDevice *pci_dev)
 }

 static NetClientInfo net_eepro100_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = nic_receive,
 };
diff --git a/hw/net/etraxfs_eth.c b/hw/net/etraxfs_eth.c
index d600275..f43a170 100644
--- a/hw/net/etraxfs_eth.c
+++ b/hw/net/etraxfs_eth.c
@@ -577,7 +577,7 @@ static const MemoryRegionOps eth_ops = {
 };

 static NetClientInfo net_etraxfs_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = eth_receive,
     .link_status_changed = eth_set_link,
diff --git a/hw/net/fsl_etsec/etsec.c b/hw/net/fsl_etsec/etsec.c
index 04bb41d..48ee27b 100644
--- a/hw/net/fsl_etsec/etsec.c
+++ b/hw/net/fsl_etsec/etsec.c
@@ -369,7 +369,7 @@ static void etsec_set_link_status(NetClientState *nc)
 }

 static NetClientInfo net_etsec_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = etsec_receive,
     .link_status_changed = etsec_set_link_status,
diff --git a/hw/net/imx_fec.c b/hw/net/imx_fec.c
index 725f3fa..785a3cc 100644
--- a/hw/net/imx_fec.c
+++ b/hw/net/imx_fec.c
@@ -651,7 +651,7 @@ static void imx_fec_cleanup(NetClientState *nc)
 }

 static NetClientInfo net_imx_fec_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .can_receive = imx_fec_can_receive,
     .receive = imx_fec_receive,
diff --git a/hw/net/lan9118.c b/hw/net/lan9118.c
index 4f0e840..11bfd23 100644
--- a/hw/net/lan9118.c
+++ b/hw/net/lan9118.c
@@ -1305,7 +1305,7 @@ static const MemoryRegionOps lan9118_16bit_mem_ops = {
 };

 static NetClientInfo net_lan9118_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = lan9118_receive,
     .link_status_changed = lan9118_set_link,
diff --git a/hw/net/lance.c b/hw/net/lance.c
index 780b39d..7940c05 100644
--- a/hw/net/lance.c
+++ b/hw/net/lance.c
@@ -92,7 +92,7 @@ static const MemoryRegionOps lance_mem_ops = {
 };

 static NetClientInfo net_lance_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = pcnet_receive,
     .link_status_changed = pcnet_set_link_status,
diff --git a/hw/net/mcf_fec.c b/hw/net/mcf_fec.c
index 21928f9..5386597 100644
--- a/hw/net/mcf_fec.c
+++ b/hw/net/mcf_fec.c
@@ -506,7 +506,7 @@ static const MemoryRegionOps mcf_fec_ops = {
 };

 static NetClientInfo net_mcf_fec_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = mcf_fec_receive,
 };
diff --git a/hw/net/milkymist-minimac2.c b/hw/net/milkymist-minimac2.c
index 6302b8b..b05c801 100644
--- a/hw/net/milkymist-minimac2.c
+++ b/hw/net/milkymist-minimac2.c
@@ -443,7 +443,7 @@ static void milkymist_minimac2_reset(DeviceState *d)
 }

 static NetClientInfo net_milkymist_minimac2_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = minimac2_rx,
 };
diff --git a/hw/net/mipsnet.c b/hw/net/mipsnet.c
index f261011..b9bf03f 100644
--- a/hw/net/mipsnet.c
+++ b/hw/net/mipsnet.c
@@ -218,7 +218,7 @@ static const VMStateDescription vmstate_mipsnet = {
 };

 static NetClientInfo net_mipsnet_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = mipsnet_receive,
 };
diff --git a/hw/net/ne2000-isa.c b/hw/net/ne2000-isa.c
index 18b0644..7894fab 100644
--- a/hw/net/ne2000-isa.c
+++ b/hw/net/ne2000-isa.c
@@ -42,7 +42,7 @@ typedef struct ISANE2000State {
 } ISANE2000State;

 static NetClientInfo net_ne2000_isa_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = ne2000_receive,
 };
diff --git a/hw/net/ne2000.c b/hw/net/ne2000.c
index 010f9ef..457f8ae 100644
--- a/hw/net/ne2000.c
+++ b/hw/net/ne2000.c
@@ -705,7 +705,7 @@ void ne2000_setup_io(NE2000State *s, DeviceState *dev, unsigned size)
 }

 static NetClientInfo net_ne2000_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = ne2000_receive,
 };
diff --git a/hw/net/opencores_eth.c b/hw/net/opencores_eth.c
index 3642046..4b6407b 100644
--- a/hw/net/opencores_eth.c
+++ b/hw/net/opencores_eth.c
@@ -473,7 +473,7 @@ static ssize_t open_eth_receive(NetClientState *nc,
 }

 static NetClientInfo net_open_eth_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .can_receive = open_eth_can_receive,
     .receive = open_eth_receive,
diff --git a/hw/net/pcnet-pci.c b/hw/net/pcnet-pci.c
index b4d60b8..c04c3a2 100644
--- a/hw/net/pcnet-pci.c
+++ b/hw/net/pcnet-pci.c
@@ -271,7 +271,7 @@ static void pci_pcnet_uninit(PCIDevice *dev)
 }

 static NetClientInfo net_pci_pcnet_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = pcnet_receive,
     .link_status_changed = pcnet_set_link_status,
diff --git a/hw/net/rocker/rocker_fp.c b/hw/net/rocker/rocker_fp.c
index c693ae5..3f82e30 100644
--- a/hw/net/rocker/rocker_fp.c
+++ b/hw/net/rocker/rocker_fp.c
@@ -166,7 +166,7 @@ static void fp_port_set_link_status(NetClientState *nc)
 }

 static NetClientInfo fp_port_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = fp_port_receive,
     .receive_iov = fp_port_receive_iov,
diff --git a/hw/net/rtl8139.c b/hw/net/rtl8139.c
index b0d6c40..0b255b9 100644
--- a/hw/net/rtl8139.c
+++ b/hw/net/rtl8139.c
@@ -3413,7 +3413,7 @@ static void rtl8139_set_link_status(NetClientState *nc)
 }

 static NetClientInfo net_rtl8139_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .can_receive = rtl8139_can_receive,
     .receive = rtl8139_receive,
diff --git a/hw/net/smc91c111.c b/hw/net/smc91c111.c
index c19cdd1..47db078 100644
--- a/hw/net/smc91c111.c
+++ b/hw/net/smc91c111.c
@@ -754,7 +754,7 @@ static const MemoryRegionOps smc91c111_mem_ops = {
 };

 static NetClientInfo net_smc91c111_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .can_receive = smc91c111_can_receive_nc,
     .receive = smc91c111_receive,
diff --git a/hw/net/spapr_llan.c b/hw/net/spapr_llan.c
index 1ca5e9c..4ee4b92 100644
--- a/hw/net/spapr_llan.c
+++ b/hw/net/spapr_llan.c
@@ -188,7 +188,7 @@ static ssize_t spapr_vlan_receive(NetClientState *nc, const uint8_t *buf,
 }

 static NetClientInfo net_spapr_vlan_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .can_receive = spapr_vlan_can_receive,
     .receive = spapr_vlan_receive,
diff --git a/hw/net/stellaris_enet.c b/hw/net/stellaris_enet.c
index 21a4773..10290fe 100644
--- a/hw/net/stellaris_enet.c
+++ b/hw/net/stellaris_enet.c
@@ -449,7 +449,7 @@ static void stellaris_enet_reset(stellaris_enet_state *s)
 }

 static NetClientInfo net_stellaris_enet_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = stellaris_enet_receive,
 };
diff --git a/hw/net/vhost_net.c b/hw/net/vhost_net.c
index 1d76b94..04a054e 100644
--- a/hw/net/vhost_net.c
+++ b/hw/net/vhost_net.c
@@ -95,10 +95,10 @@ static const int *vhost_net_get_feature_bits(struct vhost_net *net)
     const int *feature_bits = 0;

     switch (net->nc->info->type) {
-    case NET_CLIENT_OPTIONS_KIND_TAP:
+    case NET_CLIENT_DRIVER_TAP:
         feature_bits = kernel_feature_bits;
         break;
-    case NET_CLIENT_OPTIONS_KIND_VHOST_USER:
+    case NET_CLIENT_DRIVER_VHOST_USER:
         feature_bits = user_feature_bits;
         break;
     default:
@@ -125,7 +125,7 @@ void vhost_net_ack_features(struct vhost_net *net, uint64_t features)
 static int vhost_net_get_fd(NetClientState *backend)
 {
     switch (backend->info->type) {
-    case NET_CLIENT_OPTIONS_KIND_TAP:
+    case NET_CLIENT_DRIVER_TAP:
         return tap_get_fd(backend);
     default:
         fprintf(stderr, "vhost-net requires tap backend\n");
@@ -236,7 +236,7 @@ static int vhost_net_start_one(struct vhost_net *net,
         net->nc->info->poll(net->nc, false);
     }

-    if (net->nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP) {
+    if (net->nc->info->type == NET_CLIENT_DRIVER_TAP) {
         qemu_set_fd_handler(net->backend, NULL, NULL, NULL);
         file.fd = net->backend;
         for (file.index = 0; file.index < net->dev.nvqs; ++file.index) {
@@ -252,7 +252,7 @@ static int vhost_net_start_one(struct vhost_net *net,
     return 0;
 fail:
     file.fd = -1;
-    if (net->nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP) {
+    if (net->nc->info->type == NET_CLIENT_DRIVER_TAP) {
         while (file.index-- > 0) {
             const VhostOps *vhost_ops = net->dev.vhost_ops;
             int r = vhost_ops->vhost_call(&net->dev, VHOST_NET_SET_BACKEND,
@@ -275,14 +275,14 @@ static void vhost_net_stop_one(struct vhost_net *net,
 {
     struct vhost_vring_file file = { .fd = -1 };

-    if (net->nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP) {
+    if (net->nc->info->type == NET_CLIENT_DRIVER_TAP) {
         for (file.index = 0; file.index < net->dev.nvqs; ++file.index) {
             const VhostOps *vhost_ops = net->dev.vhost_ops;
             int r = vhost_ops->vhost_call(&net->dev, VHOST_NET_SET_BACKEND,
                                           &file);
             assert(r >= 0);
         }
-    } else if (net->nc->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER) {
+    } else if (net->nc->info->type == NET_CLIENT_DRIVER_VHOST_USER) {
         for (file.index = 0; file.index < net->dev.nvqs; ++file.index) {
             const VhostOps *vhost_ops = net->dev.vhost_ops;
             int r = vhost_ops->vhost_call(&net->dev, VHOST_RESET_OWNER,
@@ -399,10 +399,10 @@ VHostNetState *get_vhost_net(NetClientState *nc)
     }

     switch (nc->info->type) {
-    case NET_CLIENT_OPTIONS_KIND_TAP:
+    case NET_CLIENT_DRIVER_TAP:
         vhost_net = tap_get_vhost_net(nc);
         break;
-    case NET_CLIENT_OPTIONS_KIND_VHOST_USER:
+    case NET_CLIENT_DRIVER_VHOST_USER:
         vhost_net = vhost_user_get_vhost_net(nc);
         break;
     default:
diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c
index f72eebf..aea0e4d 100644
--- a/hw/net/virtio-net.c
+++ b/hw/net/virtio-net.c
@@ -406,7 +406,7 @@ static int peer_attach(VirtIONet *n, int index)
         return 0;
     }

-    if (nc->peer->info->type != NET_CLIENT_OPTIONS_KIND_TAP) {
+    if (nc->peer->info->type != NET_CLIENT_DRIVER_TAP) {
         return 0;
     }

@@ -421,7 +421,7 @@ static int peer_detach(VirtIONet *n, int index)
         return 0;
     }

-    if (nc->peer->info->type !=  NET_CLIENT_OPTIONS_KIND_TAP) {
+    if (nc->peer->info->type !=  NET_CLIENT_DRIVER_TAP) {
         return 0;
     }

@@ -1596,7 +1596,7 @@ static int virtio_net_load_device(VirtIODevice *vdev, QEMUFile *f,
 }

 static NetClientInfo net_virtio_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .can_receive = virtio_net_can_receive,
     .receive = virtio_net_receive,
diff --git a/hw/net/vmxnet3.c b/hw/net/vmxnet3.c
index 04159c8..ae465bd 100644
--- a/hw/net/vmxnet3.c
+++ b/hw/net/vmxnet3.c
@@ -1986,7 +1986,7 @@ static void vmxnet3_set_link_status(NetClientState *nc)
 }

 static NetClientInfo net_vmxnet3_info = {
-        .type = NET_CLIENT_OPTIONS_KIND_NIC,
+        .type = NET_CLIENT_DRIVER_NIC,
         .size = sizeof(NICState),
         .receive = vmxnet3_receive,
         .link_status_changed = vmxnet3_set_link_status,
diff --git a/hw/net/xen_nic.c b/hw/net/xen_nic.c
index 0da16b4..bf90a25 100644
--- a/hw/net/xen_nic.c
+++ b/hw/net/xen_nic.c
@@ -279,7 +279,7 @@ static ssize_t net_rx_packet(NetClientState *nc, const uint8_t *buf, size_t size
 /* ------------------------------------------------------------- */

 static NetClientInfo net_xen_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = net_rx_packet,
 };
diff --git a/hw/net/xgmac.c b/hw/net/xgmac.c
index 15fb681..ccf8a77 100644
--- a/hw/net/xgmac.c
+++ b/hw/net/xgmac.c
@@ -370,7 +370,7 @@ out:
 }

 static NetClientInfo net_xgmac_enet_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = eth_rx,
 };
diff --git a/hw/net/xilinx_axienet.c b/hw/net/xilinx_axienet.c
index d63c423..cc464c9 100644
--- a/hw/net/xilinx_axienet.c
+++ b/hw/net/xilinx_axienet.c
@@ -933,7 +933,7 @@ xilinx_axienet_data_stream_push(StreamSlave *obj, uint8_t *buf, size_t size)
 }

 static NetClientInfo net_xilinx_enet_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = eth_rx,
 };
diff --git a/hw/net/xilinx_ethlite.c b/hw/net/xilinx_ethlite.c
index ad6b553..ab555f6 100644
--- a/hw/net/xilinx_ethlite.c
+++ b/hw/net/xilinx_ethlite.c
@@ -214,7 +214,7 @@ static void xilinx_ethlite_reset(DeviceState *dev)
 }

 static NetClientInfo net_xilinx_ethlite_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .can_receive = eth_can_rx,
     .receive = eth_rx,
diff --git a/hw/usb/dev-network.c b/hw/usb/dev-network.c
index 7800cee..97b2c2a 100644
--- a/hw/usb/dev-network.c
+++ b/hw/usb/dev-network.c
@@ -1330,7 +1330,7 @@ static void usb_net_handle_destroy(USBDevice *dev)
 }

 static NetClientInfo net_usbnet_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .type = NET_CLIENT_DRIVER_NIC,
     .size = sizeof(NICState),
     .receive = usbnet_receive,
     .cleanup = usbnet_cleanup,
diff --git a/include/net/net.h b/include/net/net.h
index 6a6cbef..c0e00ef 100644
--- a/include/net/net.h
+++ b/include/net/net.h
@@ -59,7 +59,7 @@ typedef int (SetVnetLE)(NetClientState *, bool);
 typedef int (SetVnetBE)(NetClientState *, bool);

 typedef struct NetClientInfo {
-    NetClientOptionsKind type;
+    NetClientDriver type;
     size_t size;
     NetReceive *receive;
     NetReceive *receive_raw;
@@ -104,7 +104,7 @@ typedef struct NICState {
 char *qemu_mac_strdup_printf(const uint8_t *macaddr);
 NetClientState *qemu_find_netdev(const char *id);
 int qemu_find_net_clients_except(const char *id, NetClientState **ncs,
-                                 NetClientOptionsKind type, int max);
+                                 NetClientDriver type, int max);
 NetClientState *qemu_new_net_client(NetClientInfo *info,
                                     NetClientState *peer,
                                     const char *model,
diff --git a/monitor.c b/monitor.c
index d0edb3b..4ae654c 100644
--- a/monitor.c
+++ b/monitor.c
@@ -2994,8 +2994,8 @@ void netdev_add_completion(ReadLineState *rs, int nb_args, const char *str)
     }
     len = strlen(str);
     readline_set_completion_index(rs, len);
-    for (i = 0; NetClientOptionsKind_lookup[i]; i++) {
-        add_completion_option(rs, str, NetClientOptionsKind_lookup[i]);
+    for (i = 0; NetClientDriver_lookup[i]; i++) {
+        add_completion_option(rs, str, NetClientDriver_lookup[i]);
     }
 }

@@ -3195,7 +3195,7 @@ void set_link_completion(ReadLineState *rs, int nb_args, const char *str)
         NetClientState *ncs[MAX_QUEUE_NUM];
         int count, i;
         count = qemu_find_net_clients_except(NULL, ncs,
-                                             NET_CLIENT_OPTIONS_KIND_NONE,
+                                             NET_CLIENT_DRIVER_NONE,
                                              MAX_QUEUE_NUM);
         for (i = 0; i < MIN(count, MAX_QUEUE_NUM); i++) {
             const char *name = ncs[i]->name;
@@ -3220,7 +3220,7 @@ void netdev_del_completion(ReadLineState *rs, int nb_args, const char *str)

     len = strlen(str);
     readline_set_completion_index(rs, len);
-    count = qemu_find_net_clients_except(NULL, ncs, NET_CLIENT_OPTIONS_KIND_NIC,
+    count = qemu_find_net_clients_except(NULL, ncs, NET_CLIENT_DRIVER_NIC,
                                          MAX_QUEUE_NUM);
     for (i = 0; i < MIN(count, MAX_QUEUE_NUM); i++) {
         QemuOpts *opts;
@@ -3332,7 +3332,7 @@ void host_net_remove_completion(ReadLineState *rs, int nb_args, const char *str)
     readline_set_completion_index(rs, len);
     if (nb_args == 2) {
         count = qemu_find_net_clients_except(NULL, ncs,
-                                             NET_CLIENT_OPTIONS_KIND_NONE,
+                                             NET_CLIENT_DRIVER_NONE,
                                              MAX_QUEUE_NUM);
         for (i = 0; i < MIN(count, MAX_QUEUE_NUM); i++) {
             int id;
@@ -3349,13 +3349,13 @@ void host_net_remove_completion(ReadLineState *rs, int nb_args, const char *str)
         return;
     } else if (nb_args == 3) {
         count = qemu_find_net_clients_except(NULL, ncs,
-                                             NET_CLIENT_OPTIONS_KIND_NIC,
+                                             NET_CLIENT_DRIVER_NIC,
                                              MAX_QUEUE_NUM);
         for (i = 0; i < MIN(count, MAX_QUEUE_NUM); i++) {
             int id;
             const char *name;

-            if (ncs[i]->info->type == NET_CLIENT_OPTIONS_KIND_HUBPORT ||
+            if (ncs[i]->info->type == NET_CLIENT_DRIVER_HUBPORT ||
                 net_hub_id_for_client(ncs[i], &id)) {
                 continue;
             }
diff --git a/net/dump.c b/net/dump.c
index a1f99c3..d2c68f8 100644
--- a/net/dump.c
+++ b/net/dump.c
@@ -94,7 +94,7 @@ static void dump_cleanup(NetClientState *nc)
 }

 static NetClientInfo net_dump_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_DUMP,
+    .type = NET_CLIENT_DRIVER_DUMP,
     .size = sizeof(DumpState),
     .receive = dump_receive,
     .cleanup = dump_cleanup,
@@ -154,8 +154,8 @@ int net_init_dump(const Netdev *netdev, const char *name,
     char def_file[128];
     const NetdevDumpOptions *dump;

-    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_DUMP);
-    dump = netdev->opts->dump;
+    assert(netdev->type == NET_CLIENT_DRIVER_DUMP);
+    dump = netdev->dump;

     assert(peer);

diff --git a/net/hub.c b/net/hub.c
index 047d861..11e6450 100644
--- a/net/hub.c
+++ b/net/hub.c
@@ -130,7 +130,7 @@ static void net_hub_port_cleanup(NetClientState *nc)
 }

 static NetClientInfo net_hub_port_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_HUBPORT,
+    .type = NET_CLIENT_DRIVER_HUBPORT,
     .size = sizeof(NetHubPort),
     .can_receive = net_hub_port_can_receive,
     .receive = net_hub_port_receive,
@@ -265,10 +265,10 @@ int net_hub_id_for_client(NetClientState *nc, int *id)
 {
     NetHubPort *port;

-    if (nc->info->type == NET_CLIENT_OPTIONS_KIND_HUBPORT) {
+    if (nc->info->type == NET_CLIENT_DRIVER_HUBPORT) {
         port = DO_UPCAST(NetHubPort, nc, nc);
     } else if (nc->peer != NULL && nc->peer->info->type ==
-            NET_CLIENT_OPTIONS_KIND_HUBPORT) {
+            NET_CLIENT_DRIVER_HUBPORT) {
         port = DO_UPCAST(NetHubPort, nc, nc->peer);
     } else {
         return -ENOENT;
@@ -285,9 +285,9 @@ int net_init_hubport(const Netdev *netdev, const char *name,
 {
     const NetdevHubPortOptions *hubport;

-    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_HUBPORT);
+    assert(netdev->type == NET_CLIENT_DRIVER_HUBPORT);
     assert(!peer);
-    hubport = netdev->opts->hubport;
+    hubport = netdev->hubport;

     net_hub_add_port(hubport->hubid, name);
     return 0;
@@ -314,14 +314,14 @@ void net_hub_check_clients(void)
             }

             switch (peer->info->type) {
-            case NET_CLIENT_OPTIONS_KIND_NIC:
+            case NET_CLIENT_DRIVER_NIC:
                 has_nic = 1;
                 break;
-            case NET_CLIENT_OPTIONS_KIND_USER:
-            case NET_CLIENT_OPTIONS_KIND_TAP:
-            case NET_CLIENT_OPTIONS_KIND_SOCKET:
-            case NET_CLIENT_OPTIONS_KIND_VDE:
-            case NET_CLIENT_OPTIONS_KIND_VHOST_USER:
+            case NET_CLIENT_DRIVER_USER:
+            case NET_CLIENT_DRIVER_TAP:
+            case NET_CLIENT_DRIVER_SOCKET:
+            case NET_CLIENT_DRIVER_VDE:
+            case NET_CLIENT_DRIVER_VHOST_USER:
                 has_host_dev = 1;
                 break;
             default:
diff --git a/net/l2tpv3.c b/net/l2tpv3.c
index bdb13fa..aa2c020 100644
--- a/net/l2tpv3.c
+++ b/net/l2tpv3.c
@@ -516,7 +516,7 @@ static void net_l2tpv3_cleanup(NetClientState *nc)
 }

 static NetClientInfo net_l2tpv3_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_L2TPV3,
+    .type = NET_CLIENT_DRIVER_L2TPV3,
     .size = sizeof(NetL2TPV3State),
     .receive = net_l2tpv3_receive_dgram,
     .receive_iov = net_l2tpv3_receive_dgram_iov,
@@ -545,8 +545,8 @@ int net_init_l2tpv3(const Netdev *netdev,
     s->queue_tail = 0;
     s->header_mismatch = false;

-    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_L2TPV3);
-    l2tpv3 = netdev->opts->l2tpv3;
+    assert(netdev->type == NET_CLIENT_DRIVER_L2TPV3);
+    l2tpv3 = netdev->l2tpv3;

     if (l2tpv3->has_ipv6 && l2tpv3->ipv6) {
         s->ipv6 = l2tpv3->ipv6;
diff --git a/net/net.c b/net/net.c
index 94a3d18..c1d407e 100644
--- a/net/net.c
+++ b/net/net.c
@@ -315,7 +315,7 @@ NICState *qemu_new_nic(NetClientInfo *info,
     NICState *nic;
     int i, queues = MAX(1, conf->peers.queues);

-    assert(info->type == NET_CLIENT_OPTIONS_KIND_NIC);
+    assert(info->type == NET_CLIENT_DRIVER_NIC);
     assert(info->size >= sizeof(NICState));

     nic = g_malloc0(info->size + sizeof(NetClientState) * queues);
@@ -385,18 +385,18 @@ void qemu_del_net_client(NetClientState *nc)
     NetClientState *ncs[MAX_QUEUE_NUM];
     int queues, i;

-    assert(nc->info->type != NET_CLIENT_OPTIONS_KIND_NIC);
+    assert(nc->info->type != NET_CLIENT_DRIVER_NIC);

     /* If the NetClientState belongs to a multiqueue backend, we will change all
      * other NetClientStates also.
      */
     queues = qemu_find_net_clients_except(nc->name, ncs,
-                                          NET_CLIENT_OPTIONS_KIND_NIC,
+                                          NET_CLIENT_DRIVER_NIC,
                                           MAX_QUEUE_NUM);
     assert(queues != 0);

     /* If there is a peer NIC, delete and cleanup client, but do not free. */
-    if (nc->peer && nc->peer->info->type == NET_CLIENT_OPTIONS_KIND_NIC) {
+    if (nc->peer && nc->peer->info->type == NET_CLIENT_DRIVER_NIC) {
         NICState *nic = qemu_get_nic(nc->peer);
         if (nic->peer_deleted) {
             return;
@@ -452,7 +452,7 @@ void qemu_foreach_nic(qemu_nic_foreach func, void *opaque)
     NetClientState *nc;

     QTAILQ_FOREACH(nc, &net_clients, next) {
-        if (nc->info->type == NET_CLIENT_OPTIONS_KIND_NIC) {
+        if (nc->info->type == NET_CLIENT_DRIVER_NIC) {
             if (nc->queue_index == 0) {
                 func(qemu_get_nic(nc), opaque);
             }
@@ -598,7 +598,7 @@ void qemu_flush_or_purge_queued_packets(NetClientState *nc, bool purge)
 {
     nc->receive_disabled = 0;

-    if (nc->peer && nc->peer->info->type == NET_CLIENT_OPTIONS_KIND_HUBPORT) {
+    if (nc->peer && nc->peer->info->type == NET_CLIENT_DRIVER_HUBPORT) {
         if (net_hub_flush(nc->peer)) {
             qemu_notify_event();
         }
@@ -728,7 +728,7 @@ NetClientState *qemu_find_netdev(const char *id)
     NetClientState *nc;

     QTAILQ_FOREACH(nc, &net_clients, next) {
-        if (nc->info->type == NET_CLIENT_OPTIONS_KIND_NIC)
+        if (nc->info->type == NET_CLIENT_DRIVER_NIC)
             continue;
         if (!strcmp(nc->name, id)) {
             return nc;
@@ -739,7 +739,7 @@ NetClientState *qemu_find_netdev(const char *id)
 }

 int qemu_find_net_clients_except(const char *id, NetClientState **ncs,
-                                 NetClientOptionsKind type, int max)
+                                 NetClientDriver type, int max)
 {
     NetClientState *nc;
     int ret = 0;
@@ -820,8 +820,8 @@ static int net_init_nic(const Netdev *netdev, const char *name,
     NICInfo *nd;
     const NetLegacyNicOptions *nic;

-    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_NIC);
-    nic = netdev->opts->nic;
+    assert(netdev->type == NET_CLIENT_DRIVER_NIC);
+    nic = netdev->nic;

     idx = nic_get_free_idx();
     if (idx == -1 || nb_nics >= MAX_NICS) {
@@ -881,39 +881,38 @@ static int net_init_nic(const Netdev *netdev, const char *name,
 }


-static int (* const net_client_init_fun[NET_CLIENT_OPTIONS_KIND_MAX])(
+static int (* const net_client_init_fun[NET_CLIENT_DRIVER_MAX])(
     const Netdev *netdev,
     const char *name,
     NetClientState *peer, Error **errp) = {
-        [NET_CLIENT_OPTIONS_KIND_NIC]       = net_init_nic,
+        [NET_CLIENT_DRIVER_NIC]       = net_init_nic,
 #ifdef CONFIG_SLIRP
-        [NET_CLIENT_OPTIONS_KIND_USER]      = net_init_slirp,
+        [NET_CLIENT_DRIVER_USER]      = net_init_slirp,
 #endif
-        [NET_CLIENT_OPTIONS_KIND_TAP]       = net_init_tap,
-        [NET_CLIENT_OPTIONS_KIND_SOCKET]    = net_init_socket,
+        [NET_CLIENT_DRIVER_TAP]       = net_init_tap,
+        [NET_CLIENT_DRIVER_SOCKET]    = net_init_socket,
 #ifdef CONFIG_VDE
-        [NET_CLIENT_OPTIONS_KIND_VDE]       = net_init_vde,
+        [NET_CLIENT_DRIVER_VDE]       = net_init_vde,
 #endif
 #ifdef CONFIG_NETMAP
-        [NET_CLIENT_OPTIONS_KIND_NETMAP]    = net_init_netmap,
+        [NET_CLIENT_DRIVER_NETMAP]    = net_init_netmap,
 #endif
-        [NET_CLIENT_OPTIONS_KIND_DUMP]      = net_init_dump,
+        [NET_CLIENT_DRIVER_DUMP]      = net_init_dump,
 #ifdef CONFIG_NET_BRIDGE
-        [NET_CLIENT_OPTIONS_KIND_BRIDGE]    = net_init_bridge,
+        [NET_CLIENT_DRIVER_BRIDGE]    = net_init_bridge,
 #endif
-        [NET_CLIENT_OPTIONS_KIND_HUBPORT]   = net_init_hubport,
+        [NET_CLIENT_DRIVER_HUBPORT]   = net_init_hubport,
 #ifdef CONFIG_VHOST_NET_USED
-        [NET_CLIENT_OPTIONS_KIND_VHOST_USER] = net_init_vhost_user,
+        [NET_CLIENT_DRIVER_VHOST_USER] = net_init_vhost_user,
 #endif
 #ifdef CONFIG_L2TPV3
-        [NET_CLIENT_OPTIONS_KIND_L2TPV3]    = net_init_l2tpv3,
+        [NET_CLIENT_DRIVER_L2TPV3]    = net_init_l2tpv3,
 #endif
 };


 static int net_client_init1(const void *object, int is_netdev, Error **errp)
 {
-    const NetClientOptions *opts;
     Netdev legacy = {0};
     const Netdev *netdev;
     const char *name;
@@ -921,34 +920,72 @@ static int net_client_init1(const void *object, int is_netdev, Error **errp)

     if (is_netdev) {
         netdev = object;
-        opts = netdev->opts;
         name = netdev->id;

-        if (opts->type == NET_CLIENT_OPTIONS_KIND_DUMP ||
-            opts->type == NET_CLIENT_OPTIONS_KIND_NIC ||
-            !net_client_init_fun[opts->type]) {
+        if (netdev->type == NET_CLIENT_DRIVER_DUMP ||
+            netdev->type == NET_CLIENT_DRIVER_NIC ||
+            !net_client_init_fun[netdev->type]) {
             error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "type",
                        "a netdev backend type");
             return -1;
         }
     } else {
         const NetLegacy *net = object;
+        const NetClientOptions *opts = net->opts;
         legacy.id = net->id;
-        opts = legacy.opts = net->opts;
         netdev = &legacy;
         /* missing optional values have been initialized to "all bits zero" */
         name = net->has_id ? net->id : net->name;

-        if (opts->type == NET_CLIENT_OPTIONS_KIND_NONE) {
+        /* Map the old options to the new flat type */
+        switch (opts->type) {
+        case NET_CLIENT_OPTIONS_KIND_NONE:
             return 0; /* nothing to do */
-        }
-        if (opts->type == NET_CLIENT_OPTIONS_KIND_HUBPORT) {
-            error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "type",
-                       "a net type");
-            return -1;
+        case NET_CLIENT_OPTIONS_KIND_NIC:
+            legacy.type = NET_CLIENT_DRIVER_NIC;
+            legacy.nic = opts->nic;
+            break;
+        case NET_CLIENT_OPTIONS_KIND_USER:
+            legacy.type = NET_CLIENT_DRIVER_USER;
+            legacy.user = opts->user;
+            break;
+        case NET_CLIENT_OPTIONS_KIND_TAP:
+            legacy.type = NET_CLIENT_DRIVER_TAP;
+            legacy.tap = opts->tap;
+            break;
+        case NET_CLIENT_OPTIONS_KIND_L2TPV3:
+            legacy.type = NET_CLIENT_DRIVER_L2TPV3;
+            legacy.l2tpv3 = opts->l2tpv3;
+            break;
+        case NET_CLIENT_OPTIONS_KIND_SOCKET:
+            legacy.type = NET_CLIENT_DRIVER_SOCKET;
+            legacy.socket = opts->socket;
+            break;
+        case NET_CLIENT_OPTIONS_KIND_VDE:
+            legacy.type = NET_CLIENT_DRIVER_VDE;
+            legacy.vde = opts->vde;
+            break;
+        case NET_CLIENT_OPTIONS_KIND_DUMP:
+            legacy.type = NET_CLIENT_DRIVER_DUMP;
+            legacy.dump = opts->dump;
+            break;
+        case NET_CLIENT_OPTIONS_KIND_BRIDGE:
+            legacy.type = NET_CLIENT_DRIVER_BRIDGE;
+            legacy.bridge = opts->bridge;
+            break;
+        case NET_CLIENT_OPTIONS_KIND_NETMAP:
+            legacy.type = NET_CLIENT_DRIVER_NETMAP;
+            legacy.netmap = opts->netmap;
+            break;
+        case NET_CLIENT_OPTIONS_KIND_VHOST_USER:
+            legacy.type = NET_CLIENT_DRIVER_VHOST_USER;
+            legacy.vhost_user = opts->vhost_user;
+            break;
+        default:
+            abort();
         }

-        if (!net_client_init_fun[opts->type]) {
+        if (!net_client_init_fun[netdev->type]) {
             error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "type",
                        "a net backend type (maybe it is not compiled "
                        "into this binary)");
@@ -956,17 +993,17 @@ static int net_client_init1(const void *object, int is_netdev, Error **errp)
         }

         /* Do not add to a vlan if it's a nic with a netdev= parameter. */
-        if (opts->type != NET_CLIENT_OPTIONS_KIND_NIC ||
+        if (netdev->type != NET_CLIENT_DRIVER_NIC ||
             !opts->nic->has_netdev) {
             peer = net_hub_add_port(net->has_vlan ? net->vlan : 0, NULL);
         }
     }

-    if (net_client_init_fun[opts->type](netdev, name, peer, errp) < 0) {
+    if (net_client_init_fun[netdev->type](netdev, name, peer, errp) < 0) {
         /* FIXME drop when all init functions store an Error */
         if (errp && !*errp) {
             error_setg(errp, QERR_DEVICE_INIT_FAILED,
-                       NetClientOptionsKind_lookup[opts->type]);
+                       NetClientDriver_lookup[netdev->type]);
         }
         return -1;
     }
@@ -1065,7 +1102,7 @@ void hmp_host_net_remove(Monitor *mon, const QDict *qdict)
                      device, vlan_id);
         return;
     }
-    if (nc->info->type == NET_CLIENT_OPTIONS_KIND_NIC) {
+    if (nc->info->type == NET_CLIENT_DRIVER_NIC) {
         error_report("invalid host network device '%s'", device);
         return;
     }
@@ -1131,7 +1168,7 @@ void print_net_client(Monitor *mon, NetClientState *nc)
 {
     monitor_printf(mon, "%s: index=%d,type=%s,%s\n", nc->name,
                    nc->queue_index,
-                   NetClientOptionsKind_lookup[nc->info->type],
+                   NetClientDriver_lookup[nc->info->type],
                    nc->info_str);
 }

@@ -1150,7 +1187,7 @@ RxFilterInfoList *qmp_query_rx_filter(bool has_name, const char *name,
         }

         /* only query rx-filter information of NIC */
-        if (nc->info->type != NET_CLIENT_OPTIONS_KIND_NIC) {
+        if (nc->info->type != NET_CLIENT_DRIVER_NIC) {
             if (has_name) {
                 error_setg(errp, "net client(%s) isn't a NIC", name);
                 return NULL;
@@ -1190,7 +1227,7 @@ RxFilterInfoList *qmp_query_rx_filter(bool has_name, const char *name,
 void hmp_info_network(Monitor *mon, const QDict *qdict)
 {
     NetClientState *nc, *peer;
-    NetClientOptionsKind type;
+    NetClientDriver type;

     net_hub_info(mon);

@@ -1203,10 +1240,10 @@ void hmp_info_network(Monitor *mon, const QDict *qdict)
             continue;
         }

-        if (!peer || type == NET_CLIENT_OPTIONS_KIND_NIC) {
+        if (!peer || type == NET_CLIENT_DRIVER_NIC) {
             print_net_client(mon, nc);
         } /* else it's a netdev connected to a NIC, printed with the NIC */
-        if (peer && type == NET_CLIENT_OPTIONS_KIND_NIC) {
+        if (peer && type == NET_CLIENT_DRIVER_NIC) {
             monitor_printf(mon, " \\ ");
             print_net_client(mon, peer);
         }
@@ -1220,7 +1257,7 @@ void qmp_set_link(const char *name, bool up, Error **errp)
     int queues, i;

     queues = qemu_find_net_clients_except(name, ncs,
-                                          NET_CLIENT_OPTIONS_KIND_MAX,
+                                          NET_CLIENT_DRIVER_MAX,
                                           MAX_QUEUE_NUM);

     if (queues == 0) {
@@ -1247,7 +1284,7 @@ void qmp_set_link(const char *name, bool up, Error **errp)
          * multiple clients that can still communicate with each other in
          * disconnected mode. For now maintain this compatibility.
          */
-        if (nc->peer->info->type == NET_CLIENT_OPTIONS_KIND_NIC) {
+        if (nc->peer->info->type == NET_CLIENT_DRIVER_NIC) {
             for (i = 0; i < queues; i++) {
                 ncs[i]->peer->link_down = !up;
             }
@@ -1288,7 +1325,7 @@ void net_cleanup(void)
      */
     while (!QTAILQ_EMPTY(&net_clients)) {
         nc = QTAILQ_FIRST(&net_clients);
-        if (nc->info->type == NET_CLIENT_OPTIONS_KIND_NIC) {
+        if (nc->info->type == NET_CLIENT_DRIVER_NIC) {
             qemu_del_nic(qemu_get_nic(nc));
         } else {
             qemu_del_net_client(nc);
@@ -1320,7 +1357,7 @@ void net_check_clients(void)
     QTAILQ_FOREACH(nc, &net_clients, next) {
         if (!nc->peer) {
             fprintf(stderr, "Warning: %s %s has no peer\n",
-                    nc->info->type == NET_CLIENT_OPTIONS_KIND_NIC ?
+                    nc->info->type == NET_CLIENT_DRIVER_NIC ?
                     "nic" : "netdev", nc->name);
         }
     }
diff --git a/net/netmap.c b/net/netmap.c
index a464618..eff9b8c 100644
--- a/net/netmap.c
+++ b/net/netmap.c
@@ -417,7 +417,7 @@ static void netmap_set_offload(NetClientState *nc, int csum, int tso4, int tso6,

 /* NetClientInfo methods */
 static NetClientInfo net_netmap_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NETMAP,
+    .type = NET_CLIENT_DRIVER_NETMAP,
     .size = sizeof(NetmapState),
     .receive = netmap_receive,
     .receive_iov = netmap_receive_iov,
@@ -439,7 +439,7 @@ int net_init_netmap(const Netdev *netdev,
                     const char *name, NetClientState *peer, Error **errp)
 {
     /* FIXME error_setg(errp, ...) on failure */
-    const NetdevNetmapOptions *netmap_opts = netdev->opts->netmap;
+    const NetdevNetmapOptions *netmap_opts = netdev->netmap;
     NetClientState *nc;
     NetmapPriv me;
     NetmapState *s;
diff --git a/net/slirp.c b/net/slirp.c
index e160b9c..055c9de 100644
--- a/net/slirp.c
+++ b/net/slirp.c
@@ -126,7 +126,7 @@ static void net_slirp_cleanup(NetClientState *nc)
 }

 static NetClientInfo net_slirp_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_USER,
+    .type = NET_CLIENT_DRIVER_USER,
     .size = sizeof(SlirpState),
     .receive = net_slirp_receive,
     .cleanup = net_slirp_cleanup,
@@ -746,8 +746,8 @@ int net_init_slirp(const Netdev *netdev, const char *name,
     const NetdevUserOptions *user;
     const char **dnssearch;

-    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_USER);
-    user = netdev->opts->user;
+    assert(netdev->type == NET_CLIENT_DRIVER_USER);
+    user = netdev->user;

     vnet = user->has_net ? g_strdup(user->net) :
            user->has_ip  ? g_strdup_printf("%s/24", user->ip) :
diff --git a/net/socket.c b/net/socket.c
index 7f949e2..915bed2 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -346,7 +346,7 @@ static void net_socket_cleanup(NetClientState *nc)
 }

 static NetClientInfo net_dgram_socket_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_SOCKET,
+    .type = NET_CLIENT_DRIVER_SOCKET,
     .size = sizeof(NetSocketState),
     .receive = net_socket_receive_dgram,
     .cleanup = net_socket_cleanup,
@@ -429,7 +429,7 @@ static void net_socket_connect(void *opaque)
 }

 static NetClientInfo net_socket_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_SOCKET,
+    .type = NET_CLIENT_DRIVER_SOCKET,
     .size = sizeof(NetSocketState),
     .receive = net_socket_receive,
     .cleanup = net_socket_cleanup,
@@ -706,8 +706,8 @@ int net_init_socket(const Netdev *netdev, const char *name,
     Error *err = NULL;
     const NetdevSocketOptions *sock;

-    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_SOCKET);
-    sock = netdev->opts->socket;
+    assert(netdev->type == NET_CLIENT_DRIVER_SOCKET);
+    sock = netdev->socket;

     if (sock->has_fd + sock->has_listen + sock->has_connect + sock->has_mcast +
         sock->has_udp != 1) {
diff --git a/net/tap-win32.c b/net/tap-win32.c
index acce480..32be2a0 100644
--- a/net/tap-win32.c
+++ b/net/tap-win32.c
@@ -723,7 +723,7 @@ static void tap_set_vnet_hdr_len(NetClientState *nc, int len)
 }

 static NetClientInfo net_tap_win32_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_TAP,
+    .type = NET_CLIENT_DRIVER_TAP,
     .size = sizeof(TAPState),
     .receive = tap_receive,
     .cleanup = tap_cleanup,
@@ -767,8 +767,8 @@ int net_init_tap(const Netdev *netdev, const char *name,
     /* FIXME error_setg(errp, ...) on failure */
     const NetdevTapOptions *tap;

-    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_TAP);
-    tap = netdev->opts->tap;
+    assert(netdev->type == NET_CLIENT_DRIVER_TAP);
+    tap = netdev->tap;

     if (!tap->has_ifname) {
         error_report("tap: no interface name");
diff --git a/net/tap.c b/net/tap.c
index aecc759..626dfca 100644
--- a/net/tap.c
+++ b/net/tap.c
@@ -221,7 +221,7 @@ static bool tap_has_ufo(NetClientState *nc)
 {
     TAPState *s = DO_UPCAST(TAPState, nc, nc);

-    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
+    assert(nc->info->type == NET_CLIENT_DRIVER_TAP);

     return s->has_ufo;
 }
@@ -230,7 +230,7 @@ static bool tap_has_vnet_hdr(NetClientState *nc)
 {
     TAPState *s = DO_UPCAST(TAPState, nc, nc);

-    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
+    assert(nc->info->type == NET_CLIENT_DRIVER_TAP);

     return !!s->host_vnet_hdr_len;
 }
@@ -239,7 +239,7 @@ static bool tap_has_vnet_hdr_len(NetClientState *nc, int len)
 {
     TAPState *s = DO_UPCAST(TAPState, nc, nc);

-    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
+    assert(nc->info->type == NET_CLIENT_DRIVER_TAP);

     return !!tap_probe_vnet_hdr_len(s->fd, len);
 }
@@ -248,7 +248,7 @@ static void tap_set_vnet_hdr_len(NetClientState *nc, int len)
 {
     TAPState *s = DO_UPCAST(TAPState, nc, nc);

-    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
+    assert(nc->info->type == NET_CLIENT_DRIVER_TAP);
     assert(len == sizeof(struct virtio_net_hdr_mrg_rxbuf) ||
            len == sizeof(struct virtio_net_hdr));

@@ -260,7 +260,7 @@ static void tap_using_vnet_hdr(NetClientState *nc, bool using_vnet_hdr)
 {
     TAPState *s = DO_UPCAST(TAPState, nc, nc);

-    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
+    assert(nc->info->type == NET_CLIENT_DRIVER_TAP);
     assert(!!s->host_vnet_hdr_len == using_vnet_hdr);

     s->using_vnet_hdr = using_vnet_hdr;
@@ -326,14 +326,14 @@ static void tap_poll(NetClientState *nc, bool enable)
 int tap_get_fd(NetClientState *nc)
 {
     TAPState *s = DO_UPCAST(TAPState, nc, nc);
-    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
+    assert(nc->info->type == NET_CLIENT_DRIVER_TAP);
     return s->fd;
 }

 /* fd support */

 static NetClientInfo net_tap_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_TAP,
+    .type = NET_CLIENT_DRIVER_TAP,
     .size = sizeof(TAPState),
     .receive = tap_receive,
     .receive_raw = tap_receive_raw,
@@ -565,8 +565,8 @@ int net_init_bridge(const Netdev *netdev, const char *name,
     TAPState *s;
     int fd, vnet_hdr;

-    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_BRIDGE);
-    bridge = netdev->opts->bridge;
+    assert(netdev->type == NET_CLIENT_DRIVER_BRIDGE);
+    bridge = netdev->bridge;

     helper = bridge->has_helper ? bridge->helper : DEFAULT_BRIDGE_HELPER;
     br     = bridge->has_br     ? bridge->br     : DEFAULT_BRIDGE_INTERFACE;
@@ -728,8 +728,8 @@ int net_init_tap(const Netdev *netdev, const char *name,
     const char *vhostfdname;
     char ifname[128];

-    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_TAP);
-    tap = netdev->opts->tap;
+    assert(netdev->type == NET_CLIENT_DRIVER_TAP);
+    tap = netdev->tap;
     queues = tap->has_queues ? tap->queues : 1;
     vhostfdname = tap->has_vhostfd ? tap->vhostfd : NULL;

@@ -890,7 +890,7 @@ int net_init_tap(const Netdev *netdev, const char *name,
 VHostNetState *tap_get_vhost_net(NetClientState *nc)
 {
     TAPState *s = DO_UPCAST(TAPState, nc, nc);
-    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
+    assert(nc->info->type == NET_CLIENT_DRIVER_TAP);
     return s->vhost_net;
 }

diff --git a/net/vde.c b/net/vde.c
index 0ac2525..7a6d4f0 100644
--- a/net/vde.c
+++ b/net/vde.c
@@ -68,7 +68,7 @@ static void vde_cleanup(NetClientState *nc)
 }

 static NetClientInfo net_vde_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_VDE,
+    .type = NET_CLIENT_DRIVER_VDE,
     .size = sizeof(VDEState),
     .receive = vde_receive,
     .cleanup = vde_cleanup,
@@ -115,8 +115,8 @@ int net_init_vde(const Netdev *netdev, const char *name,
     /* FIXME error_setg(errp, ...) on failure */
     const NetdevVdeOptions *vde;

-    assert(netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_VDE);
-    vde = netdev->opts->vde;
+    assert(netdev->type == NET_CLIENT_DRIVER_VDE);
+    vde = netdev->vde;

     /* missing optional values have been initialized to "all bits zero" */
     if (net_vde_init(peer, "vde", name, vde->sock, vde->port, vde->group,
diff --git a/net/vhost-user.c b/net/vhost-user.c
index 8597ec4..fef89ca 100644
--- a/net/vhost-user.c
+++ b/net/vhost-user.c
@@ -30,7 +30,7 @@ typedef struct VhostUserChardevProps {
 VHostNetState *vhost_user_get_vhost_net(NetClientState *nc)
 {
     VhostUserState *s = DO_UPCAST(VhostUserState, nc, nc);
-    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
+    assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
     return s->vhost_net;
 }

@@ -75,20 +75,20 @@ static void vhost_user_cleanup(NetClientState *nc)

 static bool vhost_user_has_vnet_hdr(NetClientState *nc)
 {
-    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
+    assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);

     return true;
 }

 static bool vhost_user_has_ufo(NetClientState *nc)
 {
-    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
+    assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);

     return true;
 }

 static NetClientInfo net_vhost_user_info = {
-        .type = NET_CLIENT_OPTIONS_KIND_VHOST_USER,
+        .type = NET_CLIENT_DRIVER_VHOST_USER,
         .size = sizeof(VhostUserState),
         .cleanup = vhost_user_cleanup,
         .has_vnet_hdr = vhost_user_has_vnet_hdr,
@@ -229,8 +229,8 @@ int net_init_vhost_user(const Netdev *netdev, const char *name,
     const NetdevVhostUserOptions *vhost_user_opts;
     CharDriverState *chr;

-    assert(netdev->opts->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
-    vhost_user_opts = netdev->opts->vhost_user;
+    assert(netdev->type == NET_CLIENT_DRIVER_VHOST_USER);
+    vhost_user_opts = netdev->vhost_user;

     chr = net_vhost_parse_chardev(vhost_user_opts, errp);
     if (!chr) {
diff --git a/qapi-schema.json b/qapi-schema.json
index 821362d..b325c42 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -2489,16 +2489,42 @@
     '*vhostforce':    'bool' } }

 ##
-# @NetClientOptions
+# @NetClientDriver
 #
-# A discriminated record of network device traits.
+# Available netdev drivers.
+#
+# Since 2.5
+##
+{ 'enum': 'NetClientDriver',
+  'data': [ 'none', 'nic', 'user', 'tap', 'l2tpv3', 'socket', 'vde', 'dump',
+            'bridge', 'hubport', 'netmap', 'vhost-user' ] }
+
+##
+# @NetdevBase
+#
+# Captures the common configuration of a network device.
+#
+# @id: identifier for monitor commands.
+#
+# @type: Specify the driver used for interpreting remaining arguments.
+#
+# Since 1.2
+##
+{ 'struct': 'NetdevBase',
+  'data': { 'id': 'str', 'type': 'NetClientDriver' } }
+
+##
+# @Netdev
+#
+# Captures the configuration of a network device.
 #
 # Since 1.2
 #
 # 'l2tpv3' - since 2.1
-#
 ##
-{ 'union': 'NetClientOptions',
+{ 'union': 'Netdev',
+  'base': 'NetdevBase',
+  'discriminator': 'type',
   'data': {
     'none':     'NetdevNoneOptions',
     'nic':      'NetLegacyNicOptions',
@@ -2536,20 +2562,25 @@
     'opts':  'NetClientOptions' } }

 ##
-# @Netdev
+# @NetClientOptions
 #
-# Captures the configuration of a network device.
-#
-# @id: identifier for monitor commands.
-#
-# @opts: device type specific properties
+# Like Netdev, but for use only by the legacy command line options
 #
 # Since 1.2
 ##
-{ 'struct': 'Netdev',
+{ 'union': 'NetClientOptions',
   'data': {
-    'id':   'str',
-    'opts': 'NetClientOptions' } }
+    'none':     'NetdevNoneOptions',
+    'nic':      'NetLegacyNicOptions',
+    'user':     'NetdevUserOptions',
+    'tap':      'NetdevTapOptions',
+    'l2tpv3':   'NetdevL2TPv3Options',
+    'socket':   'NetdevSocketOptions',
+    'vde':      'NetdevVdeOptions',
+    'dump':     'NetdevDumpOptions',
+    'bridge':   'NetdevBridgeOptions',
+    'netmap':   'NetdevNetmapOptions',
+    'vhost-user': 'NetdevVhostUserOptions' } }

 ##
 # @InetSocketAddress
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 44/46] net: Use correct type for bool flag
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (42 preceding siblings ...)
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 43/46] qapi: Change Netdev into a flat union Eric Blake
@ 2015-09-21 21:58 ` Eric Blake
  2015-09-21 21:58 ` [Qemu-devel] [PATCH v5 45/46] net: Complete qapi-fication of netdev_add Eric Blake
                   ` (2 subsequent siblings)
  46 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:58 UTC (permalink / raw
  To: qemu-devel
  Cc: ehabkost, Jason Wang, armbru, Gerd Hoffmann, DirtY.iCE.hu,
	marcandre.lureau

is_netdev is only used as a bool, so make it one.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 hw/usb/dev-network.c |  2 +-
 include/net/net.h    |  2 +-
 net/net.c            | 12 ++++++------
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/hw/usb/dev-network.c b/hw/usb/dev-network.c
index 97b2c2a..174bad4 100644
--- a/hw/usb/dev-network.c
+++ b/hw/usb/dev-network.c
@@ -1392,7 +1392,7 @@ static USBDevice *usb_net_init(USBBus *bus, const char *cmdline)
     qemu_opt_set(opts, "type", "nic", &error_abort);
     qemu_opt_set(opts, "model", "usb", &error_abort);

-    idx = net_client_init(opts, 0, &local_err);
+    idx = net_client_init(opts, false, &local_err);
     if (local_err) {
         error_report_err(local_err);
         return NULL;
diff --git a/include/net/net.h b/include/net/net.h
index c0e00ef..8c12eff 100644
--- a/include/net/net.h
+++ b/include/net/net.h
@@ -189,7 +189,7 @@ extern const char *host_net_devices[];
 extern const char *legacy_tftp_prefix;
 extern const char *legacy_bootp_filename;

-int net_client_init(QemuOpts *opts, int is_netdev, Error **errp);
+int net_client_init(QemuOpts *opts, bool is_netdev, Error **errp);
 int net_client_parse(QemuOptsList *opts_list, const char *str);
 int net_init_clients(void);
 void net_check_clients(void);
diff --git a/net/net.c b/net/net.c
index c1d407e..d1ec9aa 100644
--- a/net/net.c
+++ b/net/net.c
@@ -911,7 +911,7 @@ static int (* const net_client_init_fun[NET_CLIENT_DRIVER_MAX])(
 };


-static int net_client_init1(const void *object, int is_netdev, Error **errp)
+static int net_client_init1(const void *object, bool is_netdev, Error **errp)
 {
     Netdev legacy = {0};
     const Netdev *netdev;
@@ -1021,7 +1021,7 @@ static void net_visit(Visitor *v, int is_netdev, void **object, Error **errp)
 }


-int net_client_init(QemuOpts *opts, int is_netdev, Error **errp)
+int net_client_init(QemuOpts *opts, bool is_netdev, Error **errp)
 {
     void *object = NULL;
     Error *err = NULL;
@@ -1083,7 +1083,7 @@ void hmp_host_net_add(Monitor *mon, const QDict *qdict)

     qemu_opt_set(opts, "type", device, &error_abort);

-    net_client_init(opts, 0, &local_err);
+    net_client_init(opts, false, &local_err);
     if (local_err) {
         error_report_err(local_err);
         monitor_printf(mon, "adding host network device %s failed\n", device);
@@ -1113,7 +1113,7 @@ void hmp_host_net_remove(Monitor *mon, const QDict *qdict)

 void netdev_add(QemuOpts *opts, Error **errp)
 {
-    net_client_init(opts, 1, errp);
+    net_client_init(opts, true, errp);
 }

 void qmp_netdev_add(QDict *qdict, QObject **ret, Error **errp)
@@ -1381,7 +1381,7 @@ static int net_init_client(void *dummy, QemuOpts *opts, Error **errp)
 {
     Error *local_err = NULL;

-    net_client_init(opts, 0, &local_err);
+    net_client_init(opts, false, &local_err);
     if (local_err) {
         error_report_err(local_err);
         return -1;
@@ -1395,7 +1395,7 @@ static int net_init_netdev(void *dummy, QemuOpts *opts, Error **errp)
     Error *local_err = NULL;
     int ret;

-    ret = net_client_init(opts, 1, &local_err);
+    ret = net_client_init(opts, true, &local_err);
     if (local_err) {
         error_report_err(local_err);
         return -1;
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 45/46] net: Complete qapi-fication of netdev_add
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (43 preceding siblings ...)
  2015-09-21 21:58 ` [Qemu-devel] [PATCH v5 44/46] net: Use correct type for bool flag Eric Blake
@ 2015-09-21 21:58 ` Eric Blake
  2015-09-23 15:40   ` Paolo Bonzini
  2015-09-21 21:58 ` [Qemu-devel] [PATCH v5 46/46] qapi: Allow anonymous base for flat union Eric Blake
  2015-09-28 13:07 ` [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Markus Armbruster
  46 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:58 UTC (permalink / raw
  To: qemu-devel; +Cc: marcandre.lureau, Jason Wang, DirtY.iCE.hu, armbru, ehabkost

We finally have all the required pieces for doing a type-safe
representation of netdev_add as a flat union, where the
discriminator 'type' now selects which additional members may
appear in the "arguments" JSON object sent over QMP, while
making no changes to the set of previously-valid QMP commands
that would work, and without breaking command line parsing.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 include/net/net.h |  1 -
 net/net.c         | 25 ++-----------------------
 qapi-schema.json  | 15 +++------------
 qmp-commands.hx   |  2 +-
 4 files changed, 6 insertions(+), 37 deletions(-)

diff --git a/include/net/net.h b/include/net/net.h
index 8c12eff..475f1e8 100644
--- a/include/net/net.h
+++ b/include/net/net.h
@@ -197,7 +197,6 @@ void net_cleanup(void);
 void hmp_host_net_add(Monitor *mon, const QDict *qdict);
 void hmp_host_net_remove(Monitor *mon, const QDict *qdict);
 void netdev_add(QemuOpts *opts, Error **errp);
-void qmp_netdev_add(QDict *qdict, QObject **ret, Error **errp);

 int net_hub_id_for_client(NetClientState *nc, int *id);
 NetClientState *net_hub_port_find(int hub_id);
diff --git a/net/net.c b/net/net.c
index d1ec9aa..69456b2 100644
--- a/net/net.c
+++ b/net/net.c
@@ -1116,30 +1116,9 @@ void netdev_add(QemuOpts *opts, Error **errp)
     net_client_init(opts, true, errp);
 }

-void qmp_netdev_add(QDict *qdict, QObject **ret, Error **errp)
+void qmp_netdev_add(Netdev *netdev, Error **errp)
 {
-    Error *local_err = NULL;
-    QemuOptsList *opts_list;
-    QemuOpts *opts;
-
-    opts_list = qemu_find_opts_err("netdev", &local_err);
-    if (local_err) {
-        goto out;
-    }
-
-    opts = qemu_opts_from_qdict(opts_list, qdict, &local_err);
-    if (local_err) {
-        goto out;
-    }
-
-    netdev_add(opts, &local_err);
-    if (local_err) {
-        qemu_opts_del(opts);
-        goto out;
-    }
-
-out:
-    error_propagate(errp, local_err);
+    net_client_init1(netdev, true, errp);
 }

 void qmp_netdev_del(const char *id, Error **errp)
diff --git a/qapi-schema.json b/qapi-schema.json
index b325c42..263053d 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -2077,26 +2077,17 @@
 #
 # Add a network backend.
 #
-# @type: the type of network backend.  Current valid values are 'user', 'tap',
-#        'vde', 'socket', 'dump' and 'bridge'
+# @type: the type of network backend; determines which additional arguments
+#        are valid. See Netdev documentation for more details.
 #
 # @id: the name of the new network backend
 #
-# Additional arguments depend on the type.
-#
-# TODO This command effectively bypasses QAPI completely due to its
-# "additional arguments" business.  It shouldn't have been added to
-# the schema in this form.  It should be qapified properly, or
-# replaced by a properly qapified command.
-#
 # Since: 0.14.0
 #
 # Returns: Nothing on success
 #          If @type is not a valid network backend, DeviceNotFound
 ##
-{ 'command': 'netdev_add',
-  'data': {'type': 'str', 'id': 'str'},
-  'gen': false }                # so we can get the additional arguments
+{ 'command': 'netdev_add', 'data': 'Netdev', 'box': true }

 ##
 # @netdev_del:
diff --git a/qmp-commands.hx b/qmp-commands.hx
index 66f0300..eecfa86 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -900,7 +900,7 @@ EQMP
     {
         .name       = "netdev_add",
         .args_type  = "netdev:O",
-        .mhandler.cmd_new = qmp_netdev_add,
+        .mhandler.cmd_new = qmp_marshal_netdev_add,
     },

 SQMP
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* [Qemu-devel] [PATCH v5 46/46] qapi: Allow anonymous base for flat union
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (44 preceding siblings ...)
  2015-09-21 21:58 ` [Qemu-devel] [PATCH v5 45/46] net: Complete qapi-fication of netdev_add Eric Blake
@ 2015-09-21 21:58 ` Eric Blake
  2015-09-23 20:59   ` Eric Blake
  2015-09-28 13:07 ` [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Markus Armbruster
  46 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-21 21:58 UTC (permalink / raw
  To: qemu-devel; +Cc: Michael Roth, marcandre.lureau, DirtY.iCE.hu, armbru, ehabkost

Rather than requiring all flat unions to explicitly create
a separate base struct, we want to allow the qapi schema
to specify the common fields via an inline dictionary. This
is similar to how commands can specify inline types for the
arguments.

Now that the feature is legal, we can drop the former
flat-union-bad-base negative test, and instead change the
positive tests in qapi-schema-test to use it.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 docs/qapi-code-gen.txt                     | 28 ++++++++++++++--------------
 scripts/qapi-commands.py                   |  2 +-
 scripts/qapi-event.py                      |  2 +-
 scripts/qapi-types.py                      |  2 +-
 scripts/qapi-visit.py                      | 15 +++++++++++----
 scripts/qapi.py                            | 20 +++++++++++++-------
 tests/Makefile                             |  1 -
 tests/qapi-schema/flat-union-bad-base.err  |  1 -
 tests/qapi-schema/flat-union-bad-base.exit |  1 -
 tests/qapi-schema/flat-union-bad-base.json | 13 -------------
 tests/qapi-schema/flat-union-bad-base.out  |  0
 tests/qapi-schema/qapi-schema-test.json    |  2 +-
 tests/qapi-schema/qapi-schema-test.out     |  5 ++++-
 13 files changed, 46 insertions(+), 46 deletions(-)
 delete mode 100644 tests/qapi-schema/flat-union-bad-base.err
 delete mode 100644 tests/qapi-schema/flat-union-bad-base.exit
 delete mode 100644 tests/qapi-schema/flat-union-bad-base.json
 delete mode 100644 tests/qapi-schema/flat-union-bad-base.out

diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index 49e3586..b14dc98 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -278,7 +278,7 @@ better than open-coding the field to be type 'str'.
 === Union types ===

 Usage: { 'union': STRING, 'data': DICT }
-or:    { 'union': STRING, 'data': DICT, 'base': STRUCT-NAME,
+or:    { 'union': STRING, 'data': DICT, 'base': STRUCT-NAME-OR-DICT,
          'discriminator': ENUM-MEMBER-OF-BASE }

 Union types are used to let the user choose between several different
@@ -314,13 +314,16 @@ an implicit C enum 'NameKind' is created, corresponding to the union
 the union can be named 'max', as this would collide with the implicit
 enum.  The value for each branch can be of any type.

-A flat union definition specifies a struct as its base, and
-avoids nesting on the wire.  All branches of the union must be
-complex types, and the top-level fields of the union dictionary on
-the wire will be combination of fields from both the base type and the
-appropriate branch type (when merging two dictionaries, there must be
-no keys in common).  The 'discriminator' field must be the name of an
-enum-typed member of the base struct.
+A flat union definition avoids nesting on the wire, and specifies a
+set of common fields that occur in all variants of the union.  The
+'base' key must specifiy either a type name (the type must be a
+struct, not a union), or a dictionary representing an anonymous type.
+All branches of the union must be complex types, and the top-level
+fields of the union dictionary on the wire will be combination of
+fields from both the base type and the appropriate branch type (when
+merging two dictionaries, there must be no keys in common).  The
+'discriminator' field must be the name of an enum-typed member of the
+base struct.

 The following example enhances the above simple union example by
 adding a common field 'readonly', renaming the discriminator to
@@ -328,10 +331,8 @@ something more applicable, and reducing the number of {} required on
 the wire:

  { 'enum': 'BlockdevDriver', 'data': [ 'file', 'qcow2' ] }
- { 'struct': 'BlockdevCommonOptions',
-   'data': { 'driver': 'BlockdevDriver', 'readonly': 'bool' } }
  { 'union': 'BlockdevOptions',
-   'base': 'BlockdevCommonOptions',
+   'base': { 'driver': 'BlockdevDriver', 'readonly': 'bool' },
    'discriminator': 'driver',
    'data': { 'file': 'FileOptions',
              'qcow2': 'Qcow2Options' } }
@@ -349,7 +350,7 @@ code generator can ensure that branches exist for all values of the
 enum (although the order of the keys need not match the declaration of
 the enum).  In the resulting generated C data types, a flat union is
 represented as a struct with the base member fields included directly,
-and then a union of structures for each branch of the struct.
+and then a union of pointers to structures for each branch of the struct.

 A simple union can always be re-written as a flat union where the base
 class has a single member named 'type', and where each branch of the
@@ -360,10 +361,9 @@ union has a struct with a single member named 'data'.  That is,
 is identical on the wire to:

  { 'enum': 'Enum', 'data': ['one', 'two'] }
- { 'struct': 'Base', 'data': { 'type': 'Enum' } }
  { 'struct': 'Branch1', 'data': { 'data': 'str' } }
  { 'struct': 'Branch2', 'data': { 'data': 'int' } }
- { 'union': 'Flat', 'base': 'Base', 'discriminator': 'type',
+ { 'union': 'Flat': 'base': { 'type': 'Enum' }, 'discriminator': 'type',
    'data': { 'one': 'Branch1', 'two': 'Branch2' } }


diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
index 0256c27..9d2708f 100644
--- a/scripts/qapi-commands.py
+++ b/scripts/qapi-commands.py
@@ -139,7 +139,7 @@ visit_type_%(c_name)s(v, &arg, NULL, %(errp)s);
                      c_name=arg_type.c_name(), errp=errparg)
         ret += gen_err_check(errarg)
     else:
-        ret += gen_visit_fields(arg_type.members, '', False, errarg)
+        ret += gen_visit_fields(arg_type.members, '', False, errarg, 'out')

     if dealloc:
         ret += mcgen('''
diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py
index b46989c..203ba3b 100644
--- a/scripts/qapi-event.py
+++ b/scripts/qapi-event.py
@@ -90,7 +90,7 @@ def gen_event_send(name, arg_type, box):
 ''',
                          name=name)
             push_indent()
-            ret += gen_visit_fields(arg_type.members, '', True, 'err')
+            ret += gen_visit_fields(arg_type.members, '', True, 'err', 'out')
             pop_indent()
             ret += mcgen('''

diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
index e7f7c36..31eccfe 100644
--- a/scripts/qapi-types.py
+++ b/scripts/qapi-types.py
@@ -102,7 +102,7 @@ struct %(c_name)s {
 ''',
                 c_name=c_name(name))
     if base:
-        ret += gen_struct_fields([], base)
+        ret += gen_struct_fields([], base, base.is_implicit())
         tag_name = variants.tag_member.name
     elif not variants.tag_member:
         ret += mcgen('''
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 00be0dc..3321255 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -98,7 +98,7 @@ if (err) {
 ''',
                      c_type=base.c_name())

-    ret += gen_visit_fields(members, '(*obj)->', False, 'err')
+    ret += gen_visit_fields(members, '(*obj)->', False, 'err', 'out')

     pop_indent()
     if base or members:
@@ -244,7 +244,7 @@ out:
 def gen_visit_union(name, base, variants):
     ret = ''

-    if base:
+    if base and not base.is_implicit():
         ret += gen_visit_struct_fields(base.name, base.base,
                                        base.local_members)

@@ -272,10 +272,17 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error

     tag_key = variants.tag_member.name
     if base:
-        ret += mcgen('''
+        if not base.is_implicit():
+            ret += mcgen('''
     visit_type_%(c_name)s_fields(v, (%(c_name)s **)obj, &err);
 ''',
-                     c_name=c_name(base.name))
+                         c_name=c_name(base.name))
+        else:
+            push_indent()
+            ret += gen_visit_fields(base.members, '(*obj)->', False,
+                                    'err', 'out_obj')
+            pop_indent()
+        tag_key = variants.tag_member.name
     else:
         ret += mcgen('''
     visit_type_%(c_type)s(v, &(*obj)->%(c_name)s, "%(name)s", &err);
diff --git a/scripts/qapi.py b/scripts/qapi.py
index 66d9000..0640e2b 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -317,6 +317,8 @@ class QAPISchemaParser(object):


 def find_base_fields(base):
+    if isinstance(base, dict):
+        return base
     base_struct_define = find_struct(base)
     if not base_struct_define:
         return None
@@ -557,9 +559,9 @@ def check_union(expr, expr_info):

     # Else, it's a flat union.
     else:
-        # The object must have a string member 'base'.
+        # The object must have a string or dictionary 'base'.
         check_type(expr_info, "'base' for union '%s'" % name,
-                   base, allow_metas=['struct'])
+                   base, allow_dict=True, allow_metas=['struct'])
         if not base:
             raise QAPIExprError(expr_info,
                                 "Flat union '%s' must have a valid base"
@@ -1277,6 +1279,10 @@ class QAPISchema(object):
         base = expr.get('base')
         tag_name = expr.get('discriminator')
         tag_enum = None
+        if isinstance(base, dict):
+            base = self._make_implicit_object_type(name, info, 'base',
+                                                   self._make_members(base,
+                                                                      name))
         if tag_name:
             variants = [self._make_variant(key, value, name)
                         for (key, value) in data.iteritems()]
@@ -1596,18 +1602,18 @@ def gen_params(arg_type, box, extra):
     return ret


-def gen_err_check(err):
+def gen_err_check(err, label='out'):
     if not err:
         return ''
     return mcgen('''
 if (%(err)s) {
-    goto out;
+    goto %(label)s;
 }
 ''',
-                 err=err)
+                 err=err, label=label)


-def gen_visit_fields(members, prefix, need_cast, errarg):
+def gen_visit_fields(members, prefix, need_cast, errarg, label):
     ret = ''
     if errarg:
         errparg = '&' + errarg
@@ -1634,7 +1640,7 @@ visit_type_%(c_type)s(v, %(cast)s&%(prefix)s%(c_name)s, "%(name)s", %(errp)s);
                      c_type=memb.type.c_name(), prefix=prefix, cast=cast,
                      c_name=c_name(memb.name), name=memb.name,
                      errp=errparg)
-        ret += gen_err_check(errarg)
+        ret += gen_err_check(errarg, label)

         if memb.optional:
             pop_indent()
diff --git a/tests/Makefile b/tests/Makefile
index 8ce3665..7276b54 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -275,7 +275,6 @@ qapi-schema += event-case.json
 qapi-schema += event-max.json
 qapi-schema += event-nest-struct.json
 qapi-schema += flat-union-array-branch.json
-qapi-schema += flat-union-bad-base.json
 qapi-schema += flat-union-bad-discriminator.json
 qapi-schema += flat-union-base-any.json
 qapi-schema += flat-union-base-union.json
diff --git a/tests/qapi-schema/flat-union-bad-base.err b/tests/qapi-schema/flat-union-bad-base.err
deleted file mode 100644
index 79b8a71..0000000
--- a/tests/qapi-schema/flat-union-bad-base.err
+++ /dev/null
@@ -1 +0,0 @@
-tests/qapi-schema/flat-union-bad-base.json:9: 'base' for union 'TestUnion' should be a type name
diff --git a/tests/qapi-schema/flat-union-bad-base.exit b/tests/qapi-schema/flat-union-bad-base.exit
deleted file mode 100644
index d00491f..0000000
--- a/tests/qapi-schema/flat-union-bad-base.exit
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/tests/qapi-schema/flat-union-bad-base.json b/tests/qapi-schema/flat-union-bad-base.json
deleted file mode 100644
index e2e622b..0000000
--- a/tests/qapi-schema/flat-union-bad-base.json
+++ /dev/null
@@ -1,13 +0,0 @@
-# we require the base to be an existing struct
-# TODO: should we allow an anonymous inline base type?
-{ 'enum': 'TestEnum',
-  'data': [ 'value1', 'value2' ] }
-{ 'struct': 'TestTypeA',
-  'data': { 'string': 'str' } }
-{ 'struct': 'TestTypeB',
-  'data': { 'integer': 'int' } }
-{ 'union': 'TestUnion',
-  'base': { 'enum1': 'TestEnum', 'kind': 'str' },
-  'discriminator': 'enum1',
-  'data': { 'value1': 'TestTypeA',
-            'value2': 'TestTypeB' } }
diff --git a/tests/qapi-schema/flat-union-bad-base.out b/tests/qapi-schema/flat-union-bad-base.out
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index 4d7fb07..ad37013 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -56,7 +56,7 @@
 # this variant of UserDefFlatUnion defaults to a union that uses fields with
 # allocated types to test corner cases in the cleanup/dealloc visitor
 { 'union': 'UserDefFlatUnion2',
-  'base': 'UserDefUnionBase',
+  'base': { 'string': 'str', 'enum1': 'EnumOne' },
   'discriminator': 'enum1',
   'data': { 'value1' : 'UserDefC', # intentional forward reference
             'value2' : 'UserDefB',
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index a8730fc..b3b4cb8 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -8,6 +8,9 @@ object :obj-EVENT_D-arg
     member b: str optional=False
     member c: str optional=True
     member enum3: EnumOne optional=True
+object :obj-UserDefFlatUnion2-base
+    member string: str optional=False
+    member enum1: EnumOne optional=False
 object :obj-__org.qemu_x-command-arg
     member a: __org.qemu_x-EnumList optional=False
     member b: __org.qemu_x-StructList optional=False
@@ -113,7 +116,7 @@ object UserDefFlatUnion
     case value2: UserDefB
     case value3: UserDefB
 object UserDefFlatUnion2
-    base UserDefUnionBase
+    base :obj-UserDefFlatUnion2-base
     tag enum1
     case value1: UserDefC
     case value2: UserDefB
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 02/46] qapi: Clean up qapi.py per pep8
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 02/46] qapi: Clean up qapi.py per pep8 Eric Blake
@ 2015-09-22 14:00   ` Markus Armbruster
  2015-09-22 14:58     ` Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-22 14:00 UTC (permalink / raw
  To: Eric Blake
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Silence pep8, and make pylint a bit happier.  Just style cleanups;
> no semantic changes.

I had planned to clean it up after resolving the TODO fold into
QAPISchema, but I'm fine with doing it right away.  I think we'll want
to do a bit more for pylint, but limiting ourselves to really obvious
changes now makes sense.

> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>  scripts/qapi.py | 165 ++++++++++++++++++++++++++++++++++++--------------------
>  1 file changed, 107 insertions(+), 58 deletions(-)
>
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 06478bb..31c4bcc 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -71,6 +71,7 @@ all_names = {}
>  # Parsing the schema into expressions
>  #
>
> +
>  def error_path(parent):
>      res = ""
>      while parent:
> @@ -79,8 +80,10 @@ def error_path(parent):
>          parent = parent['parent']
>      return res
>
> +
>  class QAPISchemaError(Exception):
>      def __init__(self, schema, msg):
> +        Exception.__init__(self)

Not a style change.  One more below.  Separate patch?

>          self.fname = schema.fname
>          self.msg = msg
>          self.col = 1
> @@ -96,8 +99,10 @@ class QAPISchemaError(Exception):
>          return error_path(self.info) + \
>              "%s:%d:%d: %s" % (self.fname, self.line, self.col, self.msg)
>
> +
>  class QAPIExprError(Exception):
>      def __init__(self, expr_info, msg):
> +        Exception.__init__(self)
>          self.info = expr_info
>          self.msg = msg
>
> @@ -105,9 +110,10 @@ class QAPIExprError(Exception):
>          return error_path(self.info['parent']) + \
>              "%s:%d: %s" % (self.info['file'], self.info['line'], self.msg)
>
> +
>  class QAPISchemaParser(object):
>
> -    def __init__(self, fp, previously_included = [], incl_info = None):
> +    def __init__(self, fp, previously_included=[], incl_info=None):
>          abs_fname = os.path.abspath(fp.name)
>          fname = fp.name
>          self.fname = fname
> @@ -122,18 +128,18 @@ class QAPISchemaParser(object):
>          self.exprs = []
>          self.accept()
>
> -        while self.tok != None:
> +        while self.tok is not None:
>              expr_info = {'file': fname, 'line': self.line,
>                           'parent': self.incl_info}
>              expr = self.get_expr(False)
>              if isinstance(expr, dict) and "include" in expr:
>                  if len(expr) != 1:
> -                    raise QAPIExprError(expr_info, "Invalid 'include' directive")
> +                    raise QAPIExprError(expr_info,
> +                                        "Invalid 'include' directive")
>                  include = expr["include"]
>                  if not isinstance(include, str):
> -                    raise QAPIExprError(expr_info,
> -                                        'Expected a file name (string), got: %s'
> -                                        % include)
> +                    raise QAPIExprError(expr_info, 'Expected a file name '
> +                                        '(string), got: %s' % include)

I don't like breaking lines in the middle of an argument when another
argument shares a line with a part.  Perhaps:

                    raise QAPIExprError(expr_info,
                                'Expected a file name (string), got: %s'
                                % include)

>                  incl_abs_fname = os.path.join(os.path.dirname(abs_fname),
>                                                include)
>                  # catch inclusion cycle
[...]
> @@ -1292,6 +1323,7 @@ def camel_case(name):
>              new_name += ch.lower()
>      return new_name
>
> +
>  # ENUMName -> ENUM_NAME, EnumName1 -> ENUM_NAME1
>  # ENUM_NAME -> ENUM_NAME, ENUM_NAME1 -> ENUM_NAME1, ENUM_Name2 -> ENUM_NAME2
>  # ENUM24_Name -> ENUM24_NAME
> @@ -1308,12 +1340,13 @@ def camel_to_upper(value):
>          if c.isupper() and (i > 0) and c_fun_str[i - 1] != "_":
>              # Case 1: next string is lower
>              # Case 2: previous string is digit
> -            if (i < (l - 1) and c_fun_str[i + 1].islower()) or \
> -            c_fun_str[i - 1].isdigit():
> +            next_lower = i < (l - 1) and c_fun_str[i + 1].islower()
> +            if next_lower or c_fun_str[i - 1].isdigit():
>                  new_name += '_'
>          new_name += c

Dunno.  Perhaps:

            if i < (l - 1) and c_fun_str[i + 1].islower():
                new_name += '_'
            elif c_fun_str[i - 1].isdigit():
                new_name += '_'

Differently ugly, I guess.

The two comment lines are pretty worthless.

>      return new_name.lstrip('_').upper()
>
> +
>  def c_enum_const(type_name, const_name, prefix=None):
>      if prefix is not None:
>          type_name = prefix
[...]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 02/46] qapi: Clean up qapi.py per pep8
  2015-09-22 14:00   ` Markus Armbruster
@ 2015-09-22 14:58     ` Eric Blake
  2015-09-23  9:20       ` Markus Armbruster
  0 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-22 14:58 UTC (permalink / raw
  To: Markus Armbruster
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

[-- Attachment #1: Type: text/plain, Size: 3560 bytes --]

On 09/22/2015 08:00 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Silence pep8, and make pylint a bit happier.  Just style cleanups;
>> no semantic changes.
> 
> I had planned to clean it up after resolving the TODO fold into
> QAPISchema, but I'm fine with doing it right away.  I think we'll want
> to do a bit more for pylint, but limiting ourselves to really obvious
> changes now makes sense.
> 
>> Signed-off-by: Eric Blake <eblake@redhat.com>
>> ---
>>  scripts/qapi.py | 165 ++++++++++++++++++++++++++++++++++++--------------------
>>  1 file changed, 107 insertions(+), 58 deletions(-)
>>

>> +
>>  class QAPISchemaError(Exception):
>>      def __init__(self, schema, msg):
>> +        Exception.__init__(self)
> 
> Not a style change.  One more below.  Separate patch?

pep8 didn't mind, but pylint did.  Personally, I don't know what happens
if you don't call the superclass constructor.  But since pep8 didn't
flag it, I can agree to split into a separate patch.


>>                  if not isinstance(include, str):
>> -                    raise QAPIExprError(expr_info,
>> -                                        'Expected a file name (string), got: %s'
>> -                                        % include)
>> +                    raise QAPIExprError(expr_info, 'Expected a file name '
>> +                                        '(string), got: %s' % include)
> 
> I don't like breaking lines in the middle of an argument when another
> argument shares a line with a part.  Perhaps:
> 
>                     raise QAPIExprError(expr_info,
>                                 'Expected a file name (string), got: %s'
>                                 % include)

Hmm - I touch the same lines again in patch 6:

|      include = expr["include"]
|      if not isinstance(include, str):
| -        raise QAPIExprError(expr_info, 'Expected a file name '
| -                            '(string), got: %s' % include)
| +        raise QAPIExprError(expr_info,
| +                            "Expected a string for 'include'")

Looks like I should reshuffle the series to avoid the churn.

>> @@ -1308,12 +1340,13 @@ def camel_to_upper(value):
>>          if c.isupper() and (i > 0) and c_fun_str[i - 1] != "_":
>>              # Case 1: next string is lower
>>              # Case 2: previous string is digit
>> -            if (i < (l - 1) and c_fun_str[i + 1].islower()) or \
>> -            c_fun_str[i - 1].isdigit():
>> +            next_lower = i < (l - 1) and c_fun_str[i + 1].islower()
>> +            if next_lower or c_fun_str[i - 1].isdigit():
>>                  new_name += '_'
>>          new_name += c
> 
> Dunno.  Perhaps:
> 
>             if i < (l - 1) and c_fun_str[i + 1].islower():
>                 new_name += '_'
>             elif c_fun_str[i - 1].isdigit():
>                 new_name += '_'
> 
> Differently ugly, I guess.

The complaints from pep8 were the \ continuation, and the fact that the
continued if condition was at the same indentation as the body of the
if; another ugly solution would be another layer of ():

if (((i < (l - 1) and c_fun_str[i + 1].islower()) or
     c_fun_str[i - 1].isdigit())):
    new_name += '_'

or maybe reverse the conditional:

Your suggestion looks the least bad to me.

> 
> The two comment lines are pretty worthless.

I can drop them if you'd like.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 03/46] qapi: Test for C member name collisions
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 03/46] qapi: Test for C member name collisions Eric Blake
@ 2015-09-22 15:23   ` Markus Armbruster
  2015-09-22 17:52     ` Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-22 15:23 UTC (permalink / raw
  To: Eric Blake
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Expose some weaknesses in the generator: we don't always forbid
> the generation of structs that contain multiple members that map

Slightly misleading.  args-name-clash is a clash between command
arguments.  These are a struct internally, but we don't currently
generate an actual struct for it, only an argument list.

> to the same C name.  This has already been marked FIXME in
> qapi.py, but having more tests will make sure future patches
> produce desired behavior.

Point to commit d90675f?

> Some of these tests will be deleted later, and a positive test
> added to qapi-schema-test.json in its place, when the code is

"in their place"?

> reworked so that the collision no longer occurs.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>  tests/Makefile                                  |  6 ++++++
>  tests/qapi-schema/args-name-clash.err           |  0
>  tests/qapi-schema/args-name-clash.exit          |  1 +
>  tests/qapi-schema/args-name-clash.json          |  2 ++
>  tests/qapi-schema/args-name-clash.out           |  6 ++++++
>  tests/qapi-schema/flat-union-branch-clash.json  |  2 +-
>  tests/qapi-schema/flat-union-branch-clash2.err  |  0
>  tests/qapi-schema/flat-union-branch-clash2.exit |  1 +
>  tests/qapi-schema/flat-union-branch-clash2.json | 14 ++++++++++++++
>  tests/qapi-schema/flat-union-branch-clash2.out  | 14 ++++++++++++++
>  tests/qapi-schema/flat-union-cycle.err          |  1 +
>  tests/qapi-schema/flat-union-cycle.exit         |  1 +
>  tests/qapi-schema/flat-union-cycle.json         |  6 ++++++
>  tests/qapi-schema/flat-union-cycle.out          |  0
>  tests/qapi-schema/qapi-schema-test.json         |  5 +++--
>  tests/qapi-schema/qapi-schema-test.out          |  2 ++
>  tests/qapi-schema/struct-base-clash2.err        |  0
>  tests/qapi-schema/struct-base-clash2.exit       |  1 +
>  tests/qapi-schema/struct-base-clash2.json       |  5 +++++
>  tests/qapi-schema/struct-base-clash2.out        |  5 +++++
>  tests/qapi-schema/union-clash.err               |  1 +
>  tests/qapi-schema/union-clash.exit              |  1 +
>  tests/qapi-schema/union-clash.json              |  3 +++
>  tests/qapi-schema/union-clash.out               |  0
>  tests/qapi-schema/union-clash2.err              |  0
>  tests/qapi-schema/union-clash2.exit             |  1 +
>  tests/qapi-schema/union-clash2.json             |  3 +++
>  tests/qapi-schema/union-clash2.out              |  6 ++++++
>  28 files changed, 84 insertions(+), 3 deletions(-)
>  create mode 100644 tests/qapi-schema/args-name-clash.err
>  create mode 100644 tests/qapi-schema/args-name-clash.exit
>  create mode 100644 tests/qapi-schema/args-name-clash.json
>  create mode 100644 tests/qapi-schema/args-name-clash.out
>  create mode 100644 tests/qapi-schema/flat-union-branch-clash2.err
>  create mode 100644 tests/qapi-schema/flat-union-branch-clash2.exit
>  create mode 100644 tests/qapi-schema/flat-union-branch-clash2.json
>  create mode 100644 tests/qapi-schema/flat-union-branch-clash2.out
>  create mode 100644 tests/qapi-schema/flat-union-cycle.err
>  create mode 100644 tests/qapi-schema/flat-union-cycle.exit
>  create mode 100644 tests/qapi-schema/flat-union-cycle.json
>  create mode 100644 tests/qapi-schema/flat-union-cycle.out
>  create mode 100644 tests/qapi-schema/struct-base-clash2.err
>  create mode 100644 tests/qapi-schema/struct-base-clash2.exit
>  create mode 100644 tests/qapi-schema/struct-base-clash2.json
>  create mode 100644 tests/qapi-schema/struct-base-clash2.out
>  create mode 100644 tests/qapi-schema/union-clash.err
>  create mode 100644 tests/qapi-schema/union-clash.exit
>  create mode 100644 tests/qapi-schema/union-clash.json
>  create mode 100644 tests/qapi-schema/union-clash.out
>  create mode 100644 tests/qapi-schema/union-clash2.err
>  create mode 100644 tests/qapi-schema/union-clash2.exit
>  create mode 100644 tests/qapi-schema/union-clash2.json
>  create mode 100644 tests/qapi-schema/union-clash2.out
>
> diff --git a/tests/Makefile b/tests/Makefile
> index 6fd5885..97434f6 100644
> --- a/tests/Makefile
> +++ b/tests/Makefile
> @@ -238,6 +238,7 @@ qapi-schema += args-invalid.json
>  qapi-schema += args-member-array-bad.json
>  qapi-schema += args-member-array.json
>  qapi-schema += args-member-unknown.json
> +qapi-schema += args-name-clash.json
>  qapi-schema += args-union.json
>  qapi-schema += args-unknown.json
>  qapi-schema += bad-base.json
> @@ -274,6 +275,8 @@ qapi-schema += flat-union-bad-discriminator.json
>  qapi-schema += flat-union-base-any.json
>  qapi-schema += flat-union-base-union.json
>  qapi-schema += flat-union-branch-clash.json
> +qapi-schema += flat-union-branch-clash2.json
> +qapi-schema += flat-union-cycle.json
>  qapi-schema += flat-union-inline.json
>  qapi-schema += flat-union-int-branch.json
>  qapi-schema += flat-union-invalid-branch-key.json
> @@ -317,6 +320,7 @@ qapi-schema += returns-unknown.json
>  qapi-schema += returns-whitelist.json
>  qapi-schema += struct-base-clash-deep.json
>  qapi-schema += struct-base-clash.json
> +qapi-schema += struct-base-clash2.json
>  qapi-schema += struct-data-invalid.json
>  qapi-schema += struct-member-invalid.json
>  qapi-schema += trailing-comma-list.json
> @@ -328,6 +332,8 @@ qapi-schema += unclosed-string.json
>  qapi-schema += unicode-str.json
>  qapi-schema += union-bad-branch.json
>  qapi-schema += union-base-no-discriminator.json
> +qapi-schema += union-clash.json
> +qapi-schema += union-clash2.json
>  qapi-schema += union-invalid-base.json
>  qapi-schema += union-max.json
>  qapi-schema += union-optional-branch.json
> diff --git a/tests/qapi-schema/args-name-clash.err b/tests/qapi-schema/args-name-clash.err
> new file mode 100644
> index 0000000..e69de29
> diff --git a/tests/qapi-schema/args-name-clash.exit b/tests/qapi-schema/args-name-clash.exit
> new file mode 100644
> index 0000000..573541a
> --- /dev/null
> +++ b/tests/qapi-schema/args-name-clash.exit
> @@ -0,0 +1 @@
> +0
> diff --git a/tests/qapi-schema/args-name-clash.json b/tests/qapi-schema/args-name-clash.json
> new file mode 100644
> index 0000000..19bf792
> --- /dev/null
> +++ b/tests/qapi-schema/args-name-clash.json
> @@ -0,0 +1,2 @@
> +# FIXME - we should reject data with members that clash when mapped to C names
> +{ 'command': 'oops', 'data': { 'a-b': 'str', 'a_b': 'str' } }
> diff --git a/tests/qapi-schema/args-name-clash.out b/tests/qapi-schema/args-name-clash.out
> new file mode 100644
> index 0000000..9b2f6e4
> --- /dev/null
> +++ b/tests/qapi-schema/args-name-clash.out
> @@ -0,0 +1,6 @@
> +object :empty
> +object :obj-oops-arg
> +    member a-b: str optional=False
> +    member a_b: str optional=False
> +command oops :obj-oops-arg -> None
> +   gen=True success_response=True
> diff --git a/tests/qapi-schema/flat-union-branch-clash.json b/tests/qapi-schema/flat-union-branch-clash.json
> index 8fb054f..3d7e6c6 100644
> --- a/tests/qapi-schema/flat-union-branch-clash.json
> +++ b/tests/qapi-schema/flat-union-branch-clash.json
> @@ -1,4 +1,4 @@
> -# we check for no duplicate keys between branches and base
> +# we check for no duplicate keys between branch members and base
>  { 'enum': 'TestEnum',
>    'data': [ 'value1', 'value2' ] }
>  { 'struct': 'Base',

This clashing business is awfully confusing as soon as unions come into
play.  When I'm confused, I need to think in writing.

The basic case is clash between local, non-variant members.  Needs test
coverage.  args-name-clash.json provides it, because internally the
arguments are just another object type.

With a base, the members inherited from base get added to the mix.  We
need to test a clash betwen local, non-variant member and a member
inherited from base.

With unions, things get complicated, because we have multiple kinds of
clashes.  Best explained with an example.  Let's use UserDefFlatUnion
from qapi-schema-test.json.

    { 'union': 'UserDefFlatUnion',
      'base': 'UserDefUnionBase',   # intentional forward reference
      'discriminator': 'enum1',
      'data': { 'value1' : 'UserDefA',
                'value2' : 'UserDefB',
                'value3' : 'UserDefB' } }

    { 'struct': 'UserDefUnionBase',
      'base': 'UserDefZero',
      'data': { 'string': 'str', 'enum1': 'EnumOne' } }

Generated C looks like this:

    struct UserDefFlatUnion {
        /* Members inherited from UserDefUnionBase: */
        int64_t integer;
        char *string;
        EnumOne enum1;
        /* Own members: */
        // if the schema language supported adding non-variant local
        // members, they'd go right here
        union { /* union tag is @enum1 */
            void *data;
            UserDefA *value1;
            UserDefB *value2;
            UserDefB *value3;
        };
    };

Thus, what can clash in C is the tag values value1, value2, value3 with
the non-variant members integer, string, enum1.

On the wire, the union members are unboxed, i.e. we get just

    "boolean": false

instead of

    "value1": { "boolean": false }

Thus what can clash on the wire is the variant members with the
non-variant members: boolean with integer, string, enum1 when enum1 is
value1, and so forth.

This is the clash flat-union-branch-clash.json tests.  Its error message
is "Member name 'name' of branch 'value1' clashes with base 'Base'".
Suboptimal, it should say "with member 'name' of base 'Base'".

> diff --git a/tests/qapi-schema/flat-union-branch-clash2.err b/tests/qapi-schema/flat-union-branch-clash2.err
> new file mode 100644
> index 0000000..e69de29
> diff --git a/tests/qapi-schema/flat-union-branch-clash2.exit b/tests/qapi-schema/flat-union-branch-clash2.exit
> new file mode 100644
> index 0000000..573541a
> --- /dev/null
> +++ b/tests/qapi-schema/flat-union-branch-clash2.exit
> @@ -0,0 +1 @@
> +0
> diff --git a/tests/qapi-schema/flat-union-branch-clash2.json b/tests/qapi-schema/flat-union-branch-clash2.json
> new file mode 100644
> index 0000000..b3eefb3
> --- /dev/null
> +++ b/tests/qapi-schema/flat-union-branch-clash2.json
> @@ -0,0 +1,14 @@
> +# FIXME: we should check for no duplicate C names between branches and base
> +{ 'enum': 'TestEnum',
> +  'data': [ 'base', 'c-d' ] }
> +{ 'struct': 'Base',
> +  'data': { 'enum1': 'TestEnum', '*c_d': 'str' } }
> +{ 'struct': 'Branch1',
> +  'data': { 'string': 'str' } }
> +{ 'struct': 'Branch2',
> +  'data': { 'value': 'int' } }
> +{ 'union': 'TestUnion',
> +  'base': 'Base',
> +  'discriminator': 'enum1',
> +  'data': { 'base': 'Branch1',
> +            'c-d': 'Branch2' } }

This tests the other kind of clash: tag value 'c-d' clashes with
non-variant member name 'c_d'.

Please add a comment explaining what clash should be reported here.

> diff --git a/tests/qapi-schema/flat-union-branch-clash2.out b/tests/qapi-schema/flat-union-branch-clash2.out
> new file mode 100644
> index 0000000..8e0da73
> --- /dev/null
> +++ b/tests/qapi-schema/flat-union-branch-clash2.out
> @@ -0,0 +1,14 @@
> +object :empty
> +object Base
> +    member enum1: TestEnum optional=False
> +    member c_d: str optional=True
> +object Branch1
> +    member string: str optional=False
> +object Branch2
> +    member value: int optional=False
> +enum TestEnum ['base', 'c-d']
> +object TestUnion
> +    base Base
> +    tag enum1
> +    case base: Branch1
> +    case c-d: Branch2
> diff --git a/tests/qapi-schema/flat-union-cycle.err b/tests/qapi-schema/flat-union-cycle.err
> new file mode 100644
> index 0000000..152c6f0
> --- /dev/null
> +++ b/tests/qapi-schema/flat-union-cycle.err
> @@ -0,0 +1 @@
> +tests/qapi-schema/flat-union-cycle.json:5: Member name 'switch' of branch 'loop' clashes with base 'Base'
> diff --git a/tests/qapi-schema/flat-union-cycle.exit b/tests/qapi-schema/flat-union-cycle.exit
> new file mode 100644
> index 0000000..d00491f
> --- /dev/null
> +++ b/tests/qapi-schema/flat-union-cycle.exit
> @@ -0,0 +1 @@
> +1
> diff --git a/tests/qapi-schema/flat-union-cycle.json b/tests/qapi-schema/flat-union-cycle.json
> new file mode 100644
> index 0000000..8f6cd93
> --- /dev/null
> +++ b/tests/qapi-schema/flat-union-cycle.json
> @@ -0,0 +1,6 @@
> +# we reject a loop in flat unions, due to member collision
> +{ 'enum': 'Enum', 'data': [ 'okay', 'loop' ] }
> +{ 'struct': 'Base', 'data': { 'switch': 'Enum' } }
> +{ 'struct': 'Okay', 'data': { 'int': 'int' } }
> +{ 'union': 'Union', 'base': 'Base', 'discriminator': 'switch',
> +  'data': { 'okay': 'Okay', 'loop': 'Base' } }

This isn't a loop, it's a fork: we get the members of Base via its use
as base, and again via its use as type of a variant case.

What does it add over flat-union-branch-clash.json?

> diff --git a/tests/qapi-schema/flat-union-cycle.out b/tests/qapi-schema/flat-union-cycle.out
> new file mode 100644
> index 0000000..e69de29
> diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
> index 6897a6e..c904db4 100644
> --- a/tests/qapi-schema/qapi-schema-test.json
> +++ b/tests/qapi-schema/qapi-schema-test.json
> @@ -32,11 +32,12 @@
>              'dict1': 'UserDefTwoDict' } }
>
>  # for testing unions
> +# name collisions between branches should not clash
>  { 'struct': 'UserDefA',
> -  'data': { 'boolean': 'bool' } }
> +  'data': { 'boolean': 'bool', '*a_b': 'int' } }
>
>  { 'struct': 'UserDefB',
> -  'data': { 'intb': 'int' } }
> +  'data': { 'intb': 'int', '*a-b': 'bool' } }
>
>  { 'union': 'UserDefFlatUnion',
>    'base': 'UserDefUnionBase',   # intentional forward reference

This tests that different variants may have clashing names.  Okay.

I'm afraid the comment is a bit too terse.  Not sure I'd make the
connection from it to member a_b and to UserDefB's a-b a fortnight from
now.

> diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
> index 1f6e858..28a0b3c 100644
> --- a/tests/qapi-schema/qapi-schema-test.out
> +++ b/tests/qapi-schema/qapi-schema-test.out
> @@ -71,6 +71,7 @@ enum QEnumTwo ['value1', 'value2']
>      prefix QENUM_TWO
>  object UserDefA
>      member boolean: bool optional=False
> +    member a_b: int optional=True
>  alternate UserDefAlternate
>      case uda: UserDefA
>      case s: str
> @@ -78,6 +79,7 @@ alternate UserDefAlternate
>  enum UserDefAlternateKind ['uda', 's', 'i']
>  object UserDefB
>      member intb: int optional=False
> +    member a-b: bool optional=True
>  object UserDefC
>      member string1: str optional=False
>      member string2: str optional=False
> diff --git a/tests/qapi-schema/struct-base-clash2.err b/tests/qapi-schema/struct-base-clash2.err
> new file mode 100644
> index 0000000..e69de29
> diff --git a/tests/qapi-schema/struct-base-clash2.exit b/tests/qapi-schema/struct-base-clash2.exit
> new file mode 100644
> index 0000000..573541a
> --- /dev/null
> +++ b/tests/qapi-schema/struct-base-clash2.exit
> @@ -0,0 +1 @@
> +0
> diff --git a/tests/qapi-schema/struct-base-clash2.json b/tests/qapi-schema/struct-base-clash2.json
> new file mode 100644
> index 0000000..56166e0
> --- /dev/null
> +++ b/tests/qapi-schema/struct-base-clash2.json
> @@ -0,0 +1,5 @@
> +# FIXME - a base class collides with a member named base
> +{ 'struct': 'Base', 'data': {} }
> +{ 'struct': 'Sub',
> +  'base': 'Base',
> +  'data': { 'base': 'str' } }

What's this about?  Hmm, I think it's about the way we do a struct
type's base.  For a union type, we add the base's members, as shown
above.  For a struct type, we add the base *boxed*, like this:

    struct Sub {
        // The base type
        Base *base;
        // Own members
        char *base;
    };

Therefore, a struct type with a base can't have a member named base.
But that's simply daft.  As soon as we change it to match union types,
this test case goes away.  If we change it soon, do we still need this
test?  Will it be done later in this series?

> diff --git a/tests/qapi-schema/struct-base-clash2.out b/tests/qapi-schema/struct-base-clash2.out
> new file mode 100644
> index 0000000..e69a416
> --- /dev/null
> +++ b/tests/qapi-schema/struct-base-clash2.out
> @@ -0,0 +1,5 @@
> +object :empty
> +object Base
> +object Sub
> +    base Base
> +    member base: str optional=False
> diff --git a/tests/qapi-schema/union-clash.err b/tests/qapi-schema/union-clash.err
> new file mode 100644
> index 0000000..64637ed
> --- /dev/null
> +++ b/tests/qapi-schema/union-clash.err
> @@ -0,0 +1 @@
> +tests/qapi-schema/union-clash.json:2: Union 'TestUnion' member 'a_b' clashes with 'a-b'
> diff --git a/tests/qapi-schema/union-clash.exit b/tests/qapi-schema/union-clash.exit
> new file mode 100644
> index 0000000..d00491f
> --- /dev/null
> +++ b/tests/qapi-schema/union-clash.exit
> @@ -0,0 +1 @@
> +1
> diff --git a/tests/qapi-schema/union-clash.json b/tests/qapi-schema/union-clash.json
> new file mode 100644
> index 0000000..0393ed8
> --- /dev/null
> +++ b/tests/qapi-schema/union-clash.json
> @@ -0,0 +1,3 @@
> +# we reject unions where branch names clash when mapped to C
> +{ 'union': 'TestUnion',
> +  'data': { 'a-b': 'int', 'a_b': 'str' } }

Is it possible for branch names to clash in C when the enumeration (be
it implicit or explicit) passes clash checks?

> diff --git a/tests/qapi-schema/union-clash.out b/tests/qapi-schema/union-clash.out
> new file mode 100644
> index 0000000..e69de29
> diff --git a/tests/qapi-schema/union-clash2.err b/tests/qapi-schema/union-clash2.err
> new file mode 100644
> index 0000000..e69de29
> diff --git a/tests/qapi-schema/union-clash2.exit b/tests/qapi-schema/union-clash2.exit
> new file mode 100644
> index 0000000..573541a
> --- /dev/null
> +++ b/tests/qapi-schema/union-clash2.exit
> @@ -0,0 +1 @@
> +0
> diff --git a/tests/qapi-schema/union-clash2.json b/tests/qapi-schema/union-clash2.json
> new file mode 100644
> index 0000000..b2d45fb
> --- /dev/null
> +++ b/tests/qapi-schema/union-clash2.json
> @@ -0,0 +1,3 @@
> +# FIXME - a union branch named 'data' collides with generated C code
> +{ 'union': 'TestUnion',
> +  'data': { 'data': 'int' } }

This tests another stupid clash: we put a member named data in our
generated unions.  As soon as we stop doing that, this test will go
away.  If we stop soon, do we still need this test?  Will we stop later
in this series?

> diff --git a/tests/qapi-schema/union-clash2.out b/tests/qapi-schema/union-clash2.out
> new file mode 100644
> index 0000000..6277239
> --- /dev/null
> +++ b/tests/qapi-schema/union-clash2.out
> @@ -0,0 +1,6 @@
> +object :empty
> +object :obj-int-wrapper
> +    member data: int optional=False
> +object TestUnion
> +    case data: :obj-int-wrapper
> +enum TestUnionKind ['data']

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 03/46] qapi: Test for C member name collisions
  2015-09-22 15:23   ` Markus Armbruster
@ 2015-09-22 17:52     ` Eric Blake
  2015-09-23  9:43       ` Markus Armbruster
  0 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-22 17:52 UTC (permalink / raw
  To: Markus Armbruster
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

[-- Attachment #1: Type: text/plain, Size: 11575 bytes --]

On 09/22/2015 09:23 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Expose some weaknesses in the generator: we don't always forbid
>> the generation of structs that contain multiple members that map
> 
> Slightly misleading.  args-name-clash is a clash between command
> arguments.  These are a struct internally, but we don't currently
> generate an actual struct for it, only an argument list.

Maybe struct-member-clash?  Renames are easy enough, but only if patch
1/46 is okay to go in first. :)

> 
>> to the same C name.  This has already been marked FIXME in
>> qapi.py, but having more tests will make sure future patches
>> produce desired behavior.
> 
> Point to commit d90675f?

Sure, now that it finally landed.

> 
>> Some of these tests will be deleted later, and a positive test
>> added to qapi-schema-test.json in its place, when the code is
> 
> "in their place"?
> 

Yep. (Perils of editing, I started with one test, then added more later
and merged into one patch)

>> reworked so that the collision no longer occurs.
>>
>> Signed-off-by: Eric Blake <eblake@redhat.com>
>> ---

>> +++ b/tests/qapi-schema/flat-union-branch-clash.json
>> @@ -1,4 +1,4 @@
>> -# we check for no duplicate keys between branches and base
>> +# we check for no duplicate keys between branch members and base
>>  { 'enum': 'TestEnum',
>>    'data': [ 'value1', 'value2' ] }
>>  { 'struct': 'Base',
> 
> This clashing business is awfully confusing as soon as unions come into
> play.  When I'm confused, I need to think in writing.

No kidding.  We already attempted to detect clashes, and caught some,
but not all, types of clashes.  And there are indeed two types of member
name clashes: those where the generated C struct has duplicate members
(either because 2 user names map to the same C name, or because the
generated code injects a C name for a purpose other than a "key":value
name), and those where the qapi type would specify the same "key":value
name more than once in the same {} object on the wire (even if the names
would not collide in C because one is accessed through a box pointer).
By patch 16/46, we should be catching all cases of member name clashes,
but there's still work to do to catch collisions in 'command' and/or
'event' names.

Also, by the time 16/46 is in, there are cases where we reject "clashes"
where two member names with different spellings would map to the same C
name, but where the corresponding C struct does not have a clash because
the members are boxed behind different pointers.  Technically, we would
not have to reject such cases, but the case is still confusing enough
that rejecting it forces the qapi writer to consider a naming convention
that is less confusing in the first place.

> 
> The basic case is clash between local, non-variant members.  Needs test
> coverage.  args-name-clash.json provides it, because internally the
> arguments are just another object type.

Correct.  The test proves we don't yet catch the clash, and is fixed
when later commits add the check.

> 
> With a base, the members inherited from base get added to the mix.  We
> need to test a clash betwen local, non-variant member and a member
> inherited from base.

True for both structs and flat unions (the two places where we use
'base').  More on this below.

> 
> With unions, things get complicated, because we have multiple kinds of
> clashes.  Best explained with an example.  Let's use UserDefFlatUnion
> from qapi-schema-test.json.
> 
>     { 'union': 'UserDefFlatUnion',
>       'base': 'UserDefUnionBase',   # intentional forward reference
>       'discriminator': 'enum1',
>       'data': { 'value1' : 'UserDefA',
>                 'value2' : 'UserDefB',
>                 'value3' : 'UserDefB' } }
> 
>     { 'struct': 'UserDefUnionBase',
>       'base': 'UserDefZero',
>       'data': { 'string': 'str', 'enum1': 'EnumOne' } }
> 
> Generated C looks like this:
> 
>     struct UserDefFlatUnion {
>         /* Members inherited from UserDefUnionBase: */
>         int64_t integer;
>         char *string;
>         EnumOne enum1;
>         /* Own members: */
>         // if the schema language supported adding non-variant local
>         // members, they'd go right here
>         union { /* union tag is @enum1 */
>             void *data;
>             UserDefA *value1;
>             UserDefB *value2;
>             UserDefB *value3;
>         };
>     };
> 
> Thus, what can clash in C is the tag values value1, value2, value3 with
> the non-variant members integer, string, enum1.

That is, the tag values now appear as C member names, even though they
did not correspond to QMP "key":value names.  Likewise, the 'data' C
member name can cause a clash.

Was even worse before commit 0f61af3e, where we were also burning the C
name 'kind'.

Commit 1e6c1616 was where we quit burning the C member name 'base'.
Prior to that time, members of base classes did not clash with variant
names because of the C boxing.

If we run into a situation where the enum values collide with base
member names (both of which are ABI), we could still solve the collision
by renaming the C member names for the enum values to something that
don't collide (such as _tag_value1 rather than value1); this is because
the C member names are not ABI and can be changed.  But we can cross
that bridge later if the situation ever arises; for now, it's just
easier to patch the generator to reject qapi where such a collision
would occur.

> 
> On the wire, the union members are unboxed, i.e. we get just
> 
>     "boolean": false
> 
> instead of
> 
>     "value1": { "boolean": false }
> 
> Thus what can clash on the wire is the variant members with the
> non-variant members: boolean with integer, string, enum1 when enum1 is
> value1, and so forth.
> 
> This is the clash flat-union-branch-clash.json tests.  Its error message
> is "Member name 'name' of branch 'value1' clashes with base 'Base'".
> Suboptimal, it should say "with member 'name' of base 'Base'".

Indeed, this is the other type of clash (QMP wire clashes, whether or
not they cause a C member clash).

>> +++ b/tests/qapi-schema/flat-union-branch-clash2.json
>> @@ -0,0 +1,14 @@
>> +# FIXME: we should check for no duplicate C names between branches and base
>> +{ 'enum': 'TestEnum',
>> +  'data': [ 'base', 'c-d' ] }
>> +{ 'struct': 'Base',
>> +  'data': { 'enum1': 'TestEnum', '*c_d': 'str' } }
>> +{ 'struct': 'Branch1',
>> +  'data': { 'string': 'str' } }
>> +{ 'struct': 'Branch2',
>> +  'data': { 'value': 'int' } }
>> +{ 'union': 'TestUnion',
>> +  'base': 'Base',
>> +  'discriminator': 'enum1',
>> +  'data': { 'base': 'Branch1',
>> +            'c-d': 'Branch2' } }
> 
> This tests the other kind of clash: tag value 'c-d' clashes with
> non-variant member name 'c_d'.
> 
> Please add a comment explaining what clash should be reported here.

Will do; and by the end of the series the error is properly reported.

>> +++ b/tests/qapi-schema/flat-union-cycle.json
>> @@ -0,0 +1,6 @@
>> +# we reject a loop in flat unions, due to member collision
>> +{ 'enum': 'Enum', 'data': [ 'okay', 'loop' ] }
>> +{ 'struct': 'Base', 'data': { 'switch': 'Enum' } }
>> +{ 'struct': 'Okay', 'data': { 'int': 'int' } }
>> +{ 'union': 'Union', 'base': 'Base', 'discriminator': 'switch',
>> +  'data': { 'okay': 'Okay', 'loop': 'Base' } }
> 
> This isn't a loop, it's a fork: we get the members of Base via its use
> as base, and again via its use as type of a variant case.
> 
> What does it add over flat-union-branch-clash.json?

I wrote this test when I discovered the assertion failure in the parser
bug as covered by patch 16/46 (a struct attempting to inherit directly
or indirectly from itself is not nice). When I first wrote the test, I
was trying to make sure that a flat union cannot inherit from itself,
but then ran into the problem that a base class must be a struct and not
a union.  So I changed the test to make sure that QMP cannot reuse the
base class as a variant type, since that would require the members of
the base type to occur in QMP more than once, without seeing if any
other test already did that.

You may have a point that this doesn't cover anything beyond
flat-union-branch-clash, and since my later changes to detect
self-inheritance didn't change the error message flagged for this case,
we can probably safely drop this test as not adding anything.

And I guess I should still test that self-inheritance attempts are
rejected, even if we later relax things to allow a non-struct as a base
class.


>> +++ b/tests/qapi-schema/qapi-schema-test.json
>> @@ -32,11 +32,12 @@
>>              'dict1': 'UserDefTwoDict' } }
>>
>>  # for testing unions
>> +# name collisions between branches should not clash
>>  { 'struct': 'UserDefA',
>> -  'data': { 'boolean': 'bool' } }
>> +  'data': { 'boolean': 'bool', '*a_b': 'int' } }
>>
>>  { 'struct': 'UserDefB',
>> -  'data': { 'intb': 'int' } }
>> +  'data': { 'intb': 'int', '*a-b': 'bool' } }
>>
>>  { 'union': 'UserDefFlatUnion',
>>    'base': 'UserDefUnionBase',   # intentional forward reference
> 
> This tests that different variants may have clashing names.  Okay.

That is, even though the variant is accepted at the same QMP {} level,
only one variant at a time can be active, so clashes in names between
variants is not fatal to either QMP or to the generated C code.

> 
> I'm afraid the comment is a bit too terse.  Not sure I'd make the
> connection from it to member a_b and to UserDefB's a-b a fortnight from
> now.
> 

Then I get to beef it up for the next round :)


>> +++ b/tests/qapi-schema/struct-base-clash2.json
>> @@ -0,0 +1,5 @@
>> +# FIXME - a base class collides with a member named base
>> +{ 'struct': 'Base', 'data': {} }
>> +{ 'struct': 'Sub',
>> +  'base': 'Base',
>> +  'data': { 'base': 'str' } }
> 
> What's this about?  Hmm, I think it's about the way we do a struct
> type's base.  For a union type, we add the base's members, as shown
> above.  For a struct type, we add the base *boxed*, like this:
> 
>     struct Sub {
>         // The base type
>         Base *base;
>         // Own members
>         char *base;
>     };
> 
> Therefore, a struct type with a base can't have a member named base.
> But that's simply daft.  As soon as we change it to match union types,
> this test case goes away.  If we change it soon, do we still need this
> test?  Will it be done later in this series?

Yes, we fix it up later in the series, at which point this test
disappears. But having the test now makes it easier to see what the
later patch is changing.

>> +++ b/tests/qapi-schema/union-clash2.json
>> @@ -0,0 +1,3 @@
>> +# FIXME - a union branch named 'data' collides with generated C code
>> +{ 'union': 'TestUnion',
>> +  'data': { 'data': 'int' } }
> 
> This tests another stupid clash: we put a member named data in our
> generated unions.  As soon as we stop doing that, this test will go
> away.  If we stop soon, do we still need this test?  Will we stop later
> in this series?

Yes, we fix it up later in the series, at which point this test
disappears. But having the test now makes it easier to see what the
later patch is changing.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 02/46] qapi: Clean up qapi.py per pep8
  2015-09-22 14:58     ` Eric Blake
@ 2015-09-23  9:20       ` Markus Armbruster
  0 siblings, 0 replies; 108+ messages in thread
From: Markus Armbruster @ 2015-09-23  9:20 UTC (permalink / raw
  To: Eric Blake
  Cc: Michael Roth, marcandre.lureau, qemu-devel, ehabkost,
	DirtY.iCE.hu

Eric Blake <eblake@redhat.com> writes:

> On 09/22/2015 08:00 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> Silence pep8, and make pylint a bit happier.  Just style cleanups;
>>> no semantic changes.
>> 
>> I had planned to clean it up after resolving the TODO fold into
>> QAPISchema, but I'm fine with doing it right away.  I think we'll want
>> to do a bit more for pylint, but limiting ourselves to really obvious
>> changes now makes sense.
>> 
>>> Signed-off-by: Eric Blake <eblake@redhat.com>
>>> ---
>>>  scripts/qapi.py | 165 ++++++++++++++++++++++++++++++++++++--------------------
>>>  1 file changed, 107 insertions(+), 58 deletions(-)
>>>
>
>>> +
>>>  class QAPISchemaError(Exception):
>>>      def __init__(self, schema, msg):
>>> +        Exception.__init__(self)
>> 
>> Not a style change.  One more below.  Separate patch?
>
> pep8 didn't mind, but pylint did.  Personally, I don't know what happens
> if you don't call the superclass constructor.  But since pep8 didn't
> flag it, I can agree to split into a separate patch.

I figure that'll be easier than explaining the fixing the commit message
not to claim "just style" ;)

>>>                  if not isinstance(include, str):
>>> -                    raise QAPIExprError(expr_info,
>>> - 'Expected a file name (string), got: %s'
>>> -                                        % include)
>>> +                    raise QAPIExprError(expr_info, 'Expected a file name '
>>> +                                        '(string), got: %s' % include)
>> 
>> I don't like breaking lines in the middle of an argument when another
>> argument shares a line with a part.  Perhaps:
>> 
>>                     raise QAPIExprError(expr_info,
>>                                 'Expected a file name (string), got: %s'
>>                                 % include)
>
> Hmm - I touch the same lines again in patch 6:
>
> |      include = expr["include"]
> |      if not isinstance(include, str):
> | -        raise QAPIExprError(expr_info, 'Expected a file name '
> | -                            '(string), got: %s' % include)
> | +        raise QAPIExprError(expr_info,
> | +                            "Expected a string for 'include'")
>
> Looks like I should reshuffle the series to avoid the churn.

I appreciate less churn, but I'm aware of diminishing returns.
Reshuffling may be more trouble than it's worth.  Your call.

>>> @@ -1308,12 +1340,13 @@ def camel_to_upper(value):
>>>          if c.isupper() and (i > 0) and c_fun_str[i - 1] != "_":
>>>              # Case 1: next string is lower
>>>              # Case 2: previous string is digit
>>> -            if (i < (l - 1) and c_fun_str[i + 1].islower()) or \
>>> -            c_fun_str[i - 1].isdigit():
>>> +            next_lower = i < (l - 1) and c_fun_str[i + 1].islower()
>>> +            if next_lower or c_fun_str[i - 1].isdigit():
>>>                  new_name += '_'
>>>          new_name += c
>> 
>> Dunno.  Perhaps:
>> 
>>             if i < (l - 1) and c_fun_str[i + 1].islower():
>>                 new_name += '_'
>>             elif c_fun_str[i - 1].isdigit():
>>                 new_name += '_'
>> 
>> Differently ugly, I guess.
>
> The complaints from pep8 were the \ continuation, and the fact that the
> continued if condition was at the same indentation as the body of the
> if; another ugly solution would be another layer of ():
>
> if (((i < (l - 1) and c_fun_str[i + 1].islower()) or
>      c_fun_str[i - 1].isdigit())):
>     new_name += '_'
>
> or maybe reverse the conditional:
>
> Your suggestion looks the least bad to me.
>
>> 
>> The two comment lines are pretty worthless.
>
> I can drop them if you'd like.

Yes, please.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 03/46] qapi: Test for C member name collisions
  2015-09-22 17:52     ` Eric Blake
@ 2015-09-23  9:43       ` Markus Armbruster
  2015-09-23 12:45         ` Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-23  9:43 UTC (permalink / raw
  To: Eric Blake
  Cc: Michael Roth, marcandre.lureau, qemu-devel, ehabkost,
	DirtY.iCE.hu

Eric Blake <eblake@redhat.com> writes:

> On 09/22/2015 09:23 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> Expose some weaknesses in the generator: we don't always forbid
>>> the generation of structs that contain multiple members that map
>> 
>> Slightly misleading.  args-name-clash is a clash between command
>> arguments.  These are a struct internally, but we don't currently
>> generate an actual struct for it, only an argument list.
>
> Maybe struct-member-clash?  Renames are easy enough, but only if patch
> 1/46 is okay to go in first. :)

Naming the test args-member-clash is fine, because that's what it tests.
It also covers struct member clashes due to they way arguments work.
The slightly misleading part is the commit message.

>>> to the same C name.  This has already been marked FIXME in
>>> qapi.py, but having more tests will make sure future patches
>>> produce desired behavior.
>> 
>> Point to commit d90675f?
>
> Sure, now that it finally landed.
>
>> 
>>> Some of these tests will be deleted later, and a positive test
>>> added to qapi-schema-test.json in its place, when the code is
>> 
>> "in their place"?
>> 
>
> Yep. (Perils of editing, I started with one test, then added more later
> and merged into one patch)
>
>>> reworked so that the collision no longer occurs.
>>>
>>> Signed-off-by: Eric Blake <eblake@redhat.com>
>>> ---
>
>>> +++ b/tests/qapi-schema/flat-union-branch-clash.json
>>> @@ -1,4 +1,4 @@
>>> -# we check for no duplicate keys between branches and base
>>> +# we check for no duplicate keys between branch members and base
>>>  { 'enum': 'TestEnum',
>>>    'data': [ 'value1', 'value2' ] }
>>>  { 'struct': 'Base',
>> 
>> This clashing business is awfully confusing as soon as unions come into
>> play.  When I'm confused, I need to think in writing.
>
> No kidding.  We already attempted to detect clashes, and caught some,
> but not all, types of clashes.  And there are indeed two types of member
> name clashes: those where the generated C struct has duplicate members
> (either because 2 user names map to the same C name, or because the
> generated code injects a C name for a purpose other than a "key":value
> name), and those where the qapi type would specify the same "key":value
> name more than once in the same {} object on the wire (even if the names
> would not collide in C because one is accessed through a box pointer).
> By patch 16/46, we should be catching all cases of member name clashes,
> but there's still work to do to catch collisions in 'command' and/or
> 'event' names.
>
> Also, by the time 16/46 is in, there are cases where we reject "clashes"
> where two member names with different spellings would map to the same C
> name, but where the corresponding C struct does not have a clash because
> the members are boxed behind different pointers.  Technically, we would
> not have to reject such cases, but the case is still confusing enough
> that rejecting it forces the qapi writer to consider a naming convention
> that is less confusing in the first place.

That's fair.

>> The basic case is clash between local, non-variant members.  Needs test
>> coverage.  args-name-clash.json provides it, because internally the
>> arguments are just another object type.
>
> Correct.  The test proves we don't yet catch the clash, and is fixed
> when later commits add the check.
>
>> 
>> With a base, the members inherited from base get added to the mix.  We
>> need to test a clash betwen local, non-variant member and a member
>> inherited from base.
>
> True for both structs and flat unions (the two places where we use
> 'base').  More on this below.
>
>> 
>> With unions, things get complicated, because we have multiple kinds of
>> clashes.  Best explained with an example.  Let's use UserDefFlatUnion
>> from qapi-schema-test.json.
>> 
>>     { 'union': 'UserDefFlatUnion',
>>       'base': 'UserDefUnionBase',   # intentional forward reference
>>       'discriminator': 'enum1',
>>       'data': { 'value1' : 'UserDefA',
>>                 'value2' : 'UserDefB',
>>                 'value3' : 'UserDefB' } }
>> 
>>     { 'struct': 'UserDefUnionBase',
>>       'base': 'UserDefZero',
>>       'data': { 'string': 'str', 'enum1': 'EnumOne' } }
>> 
>> Generated C looks like this:
>> 
>>     struct UserDefFlatUnion {
>>         /* Members inherited from UserDefUnionBase: */
>>         int64_t integer;
>>         char *string;
>>         EnumOne enum1;
>>         /* Own members: */
>>         // if the schema language supported adding non-variant local
>>         // members, they'd go right here
>>         union { /* union tag is @enum1 */
>>             void *data;
>>             UserDefA *value1;
>>             UserDefB *value2;
>>             UserDefB *value3;
>>         };
>>     };
>> 
>> Thus, what can clash in C is the tag values value1, value2, value3 with
>> the non-variant members integer, string, enum1.
>
> That is, the tag values now appear as C member names, even though they
> did not correspond to QMP "key":value names.  Likewise, the 'data' C
> member name can cause a clash.
>
> Was even worse before commit 0f61af3e, where we were also burning the C
> name 'kind'.
>
> Commit 1e6c1616 was where we quit burning the C member name 'base'.
> Prior to that time, members of base classes did not clash with variant
> names because of the C boxing.

For union types.  For struct types, we still box the base.  I'd like to
get rid of that.

Even when the base is boxed, the members still clash in QMP.

We also box the variants (e.g. UserDefA *value1 in the example above).
Would be nice to get rid of that, too.

> If we run into a situation where the enum values collide with base
> member names (both of which are ABI), we could still solve the collision
> by renaming the C member names for the enum values to something that
> don't collide (such as _tag_value1 rather than value1); this is because
> the C member names are not ABI and can be changed.  But we can cross
> that bridge later if the situation ever arises; for now, it's just
> easier to patch the generator to reject qapi where such a collision
> would occur.

Agreed.

>> On the wire, the union members are unboxed, i.e. we get just
>> 
>>     "boolean": false
>> 
>> instead of
>> 
>>     "value1": { "boolean": false }
>> 
>> Thus what can clash on the wire is the variant members with the
>> non-variant members: boolean with integer, string, enum1 when enum1 is
>> value1, and so forth.
>> 
>> This is the clash flat-union-branch-clash.json tests.  Its error message
>> is "Member name 'name' of branch 'value1' clashes with base 'Base'".
>> Suboptimal, it should say "with member 'name' of base 'Base'".
>
> Indeed, this is the other type of clash (QMP wire clashes, whether or
> not they cause a C member clash).
>
>>> +++ b/tests/qapi-schema/flat-union-branch-clash2.json
>>> @@ -0,0 +1,14 @@
>>> +# FIXME: we should check for no duplicate C names between branches and base
>>> +{ 'enum': 'TestEnum',
>>> +  'data': [ 'base', 'c-d' ] }
>>> +{ 'struct': 'Base',
>>> +  'data': { 'enum1': 'TestEnum', '*c_d': 'str' } }
>>> +{ 'struct': 'Branch1',
>>> +  'data': { 'string': 'str' } }
>>> +{ 'struct': 'Branch2',
>>> +  'data': { 'value': 'int' } }
>>> +{ 'union': 'TestUnion',
>>> +  'base': 'Base',
>>> +  'discriminator': 'enum1',
>>> +  'data': { 'base': 'Branch1',
>>> +            'c-d': 'Branch2' } }
>> 
>> This tests the other kind of clash: tag value 'c-d' clashes with
>> non-variant member name 'c_d'.
>> 
>> Please add a comment explaining what clash should be reported here.
>
> Will do; and by the end of the series the error is properly reported.
>
>>> +++ b/tests/qapi-schema/flat-union-cycle.json
>>> @@ -0,0 +1,6 @@
>>> +# we reject a loop in flat unions, due to member collision
>>> +{ 'enum': 'Enum', 'data': [ 'okay', 'loop' ] }
>>> +{ 'struct': 'Base', 'data': { 'switch': 'Enum' } }
>>> +{ 'struct': 'Okay', 'data': { 'int': 'int' } }
>>> +{ 'union': 'Union', 'base': 'Base', 'discriminator': 'switch',
>>> +  'data': { 'okay': 'Okay', 'loop': 'Base' } }
>> 
>> This isn't a loop, it's a fork: we get the members of Base via its use
>> as base, and again via its use as type of a variant case.
>> 
>> What does it add over flat-union-branch-clash.json?
>
> I wrote this test when I discovered the assertion failure in the parser
> bug as covered by patch 16/46 (a struct attempting to inherit directly
> or indirectly from itself is not nice).

Yes, that needs to be caught.

>                                         When I first wrote the test, I
> was trying to make sure that a flat union cannot inherit from itself,
> but then ran into the problem that a base class must be a struct and not
> a union.

Yes.  That means base loops can only consist of struct types right now.

>           So I changed the test to make sure that QMP cannot reuse the
> base class as a variant type, since that would require the members of
> the base type to occur in QMP more than once, without seeing if any
> other test already did that.
>
> You may have a point that this doesn't cover anything beyond
> flat-union-branch-clash, and since my later changes to detect
> self-inheritance didn't change the error message flagged for this case,
> we can probably safely drop this test as not adding anything.
>
> And I guess I should still test that self-inheritance attempts are
> rejected, even if we later relax things to allow a non-struct as a base
> class.

Replacing this test by one that actually tests base loops sounds good to
me.  If we ever accept other kinds of base types, we'll have to update
the loop test, but that's normal.

>>> +++ b/tests/qapi-schema/qapi-schema-test.json
>>> @@ -32,11 +32,12 @@
>>>              'dict1': 'UserDefTwoDict' } }
>>>
>>>  # for testing unions
>>> +# name collisions between branches should not clash
>>>  { 'struct': 'UserDefA',
>>> -  'data': { 'boolean': 'bool' } }
>>> +  'data': { 'boolean': 'bool', '*a_b': 'int' } }
>>>
>>>  { 'struct': 'UserDefB',
>>> -  'data': { 'intb': 'int' } }
>>> +  'data': { 'intb': 'int', '*a-b': 'bool' } }
>>>
>>>  { 'union': 'UserDefFlatUnion',
>>>    'base': 'UserDefUnionBase',   # intentional forward reference
>> 
>> This tests that different variants may have clashing names.  Okay.
>
> That is, even though the variant is accepted at the same QMP {} level,
> only one variant at a time can be active, so clashes in names between
> variants is not fatal to either QMP or to the generated C code.

Yes.

>> I'm afraid the comment is a bit too terse.  Not sure I'd make the
>> connection from it to member a_b and to UserDefB's a-b a fortnight from
>> now.
>> 
>
> Then I get to beef it up for the next round :)
>
>
>>> +++ b/tests/qapi-schema/struct-base-clash2.json
>>> @@ -0,0 +1,5 @@
>>> +# FIXME - a base class collides with a member named base
>>> +{ 'struct': 'Base', 'data': {} }
>>> +{ 'struct': 'Sub',
>>> +  'base': 'Base',
>>> +  'data': { 'base': 'str' } }
>> 
>> What's this about?  Hmm, I think it's about the way we do a struct
>> type's base.  For a union type, we add the base's members, as shown
>> above.  For a struct type, we add the base *boxed*, like this:
>> 
>>     struct Sub {
>>         // The base type
>>         Base *base;
>>         // Own members
>>         char *base;
>>     };
>> 
>> Therefore, a struct type with a base can't have a member named base.
>> But that's simply daft.  As soon as we change it to match union types,
>> this test case goes away.  If we change it soon, do we still need this
>> test?  Will it be done later in this series?
>
> Yes, we fix it up later in the series, at which point this test
> disappears. But having the test now makes it easier to see what the
> later patch is changing.

I'm not sure I'd bother myself, but I gladly defer to your judgement
here.

>>> +++ b/tests/qapi-schema/union-clash2.json
>>> @@ -0,0 +1,3 @@
>>> +# FIXME - a union branch named 'data' collides with generated C code
>>> +{ 'union': 'TestUnion',
>>> +  'data': { 'data': 'int' } }
>> 
>> This tests another stupid clash: we put a member named data in our
>> generated unions.  As soon as we stop doing that, this test will go
>> away.  If we stop soon, do we still need this test?  Will we stop later
>> in this series?
>
> Yes, we fix it up later in the series, at which point this test
> disappears. But having the test now makes it easier to see what the
> later patch is changing.

Likewise.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 03/46] qapi: Test for C member name collisions
  2015-09-23  9:43       ` Markus Armbruster
@ 2015-09-23 12:45         ` Eric Blake
  2015-09-23 14:02           ` Markus Armbruster
  0 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-23 12:45 UTC (permalink / raw
  To: Markus Armbruster
  Cc: Michael Roth, marcandre.lureau, qemu-devel, ehabkost,
	DirtY.iCE.hu

[-- Attachment #1: Type: text/plain, Size: 1693 bytes --]

On 09/23/2015 03:43 AM, Markus Armbruster wrote:

>> Commit 1e6c1616 was where we quit burning the C member name 'base'.
>> Prior to that time, members of base classes did not clash with variant
>> names because of the C boxing.
> 
> For union types.  For struct types, we still box the base.  I'd like to
> get rid of that.

Patch 34/46 :)

> 
> Even when the base is boxed, the members still clash in QMP.
> 
> We also box the variants (e.g. UserDefA *value1 in the example above).
> Would be nice to get rid of that, too.

What do you mean? Here's an example of current boxed code:

enum EnumType {
    ENUM_TYPE_ONE,
    ENUM_TYPE_TWO,
};
struct One {
    int a;
};
struct Two {
    char *a;
};
struct Union {
    EnumType type;
    /* union tag is @type */
    union {
        One *one;
        Two *two;
    };
};

Is this what you envision for unboxed? Note that we still have to
namespace things properly (we have to have union.one.a and union.two.a,
and not a direct union.a), so all we'd be saving is the additional
allocation of the variant pointers.

struct Union {
    EnumType type;
    /* union tag is @type */
    union {
        struct {
            int a;
        } one;
        struct {
            char *a;
        } two;
    };
};

However, I'm not sure it would always help.  The conversion of
netdev_add to full qapi relies on being able to access the variant
through a named struct (such as NetdevTapOptions); unboxing the variant
would get rid of the convenient access to these named sub-structs.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 03/46] qapi: Test for C member name collisions
  2015-09-23 12:45         ` Eric Blake
@ 2015-09-23 14:02           ` Markus Armbruster
  2015-09-23 14:19             ` Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-23 14:02 UTC (permalink / raw
  To: Eric Blake
  Cc: marcandre.lureau, DirtY.iCE.hu, Michael Roth, ehabkost,
	qemu-devel

Eric Blake <eblake@redhat.com> writes:

> On 09/23/2015 03:43 AM, Markus Armbruster wrote:
>
>>> Commit 1e6c1616 was where we quit burning the C member name 'base'.
>>> Prior to that time, members of base classes did not clash with variant
>>> names because of the C boxing.
>> 
>> For union types.  For struct types, we still box the base.  I'd like to
>> get rid of that.
>
> Patch 34/46 :)

Okay :)

>> Even when the base is boxed, the members still clash in QMP.
>> 
>> We also box the variants (e.g. UserDefA *value1 in the example above).
>> Would be nice to get rid of that, too.
>
> What do you mean? Here's an example of current boxed code:
>
> enum EnumType {
>     ENUM_TYPE_ONE,
>     ENUM_TYPE_TWO,
> };
> struct One {
>     int a;
> };
> struct Two {
>     char *a;
> };
> struct Union {
>     EnumType type;
>     /* union tag is @type */
>     union {
>         One *one;
>         Two *two;
>     };
> };
>
> Is this what you envision for unboxed? Note that we still have to
> namespace things properly (we have to have union.one.a and union.two.a,
> and not a direct union.a), so all we'd be saving is the additional
> allocation of the variant pointers.
>
> struct Union {
>     EnumType type;
>     /* union tag is @type */
>     union {
>         struct {
>             int a;
>         } one;
>         struct {
>             char *a;
>         } two;
>     };
> };
>
> However, I'm not sure it would always help.  The conversion of
> netdev_add to full qapi relies on being able to access the variant
> through a named struct (such as NetdevTapOptions); unboxing the variant
> would get rid of the convenient access to these named sub-structs.

struct Union {
    EnumType type;
    /* union tag is @type */
    union {
        One one;
        Two two;
    };
};

For base, we go one step further and peel off the struct, to save some
notational overhead.  Pointless for unions.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 03/46] qapi: Test for C member name collisions
  2015-09-23 14:02           ` Markus Armbruster
@ 2015-09-23 14:19             ` Eric Blake
  2015-09-23 15:12               ` Markus Armbruster
  0 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-23 14:19 UTC (permalink / raw
  To: Markus Armbruster
  Cc: marcandre.lureau, DirtY.iCE.hu, Michael Roth, ehabkost,
	qemu-devel

[-- Attachment #1: Type: text/plain, Size: 1157 bytes --]

On 09/23/2015 08:02 AM, Markus Armbruster wrote:

>> However, I'm not sure it would always help.  The conversion of
>> netdev_add to full qapi relies on being able to access the variant
>> through a named struct (such as NetdevTapOptions); unboxing the variant
>> would get rid of the convenient access to these named sub-structs.
> 
> struct Union {
>     EnumType type;
>     /* union tag is @type */
>     union {
>         One one;
>         Two two;
>     };
> };
> 
> For base, we go one step further and peel off the struct, to save some
> notational overhead.  Pointless for unions.

Ah, I see. Instead of malloc'ing a sub-struct and calling (roughly)

ptr = visit_start_struct(Union) // mallocs
subptr = visit_start_implicit_struct(One) // also mallocs
visit_type_fields(subptr)
visit_end_implicit_struct()

we would instead use inline allocation, with:

ptr = visit_start_struct(Union) // mallocs
visit_type_fields(&ptr->one)

seems straightforward enough; I'll play with the idea on top of my series.


-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 01/46] qapi: Sort qapi-schema tests
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 01/46] qapi: Sort qapi-schema tests Eric Blake
@ 2015-09-23 14:26   ` Eric Blake
  2015-09-23 15:09     ` Markus Armbruster
  0 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-23 14:26 UTC (permalink / raw
  To: qemu-devel; +Cc: marcandre.lureau, armbru, ehabkost, DirtY.iCE.hu

[-- Attachment #1: Type: text/plain, Size: 1143 bytes --]

On 09/21/2015 03:57 PM, Eric Blake wrote:
> Recent changes to qapi have provided quite a bit of churn in
> the makefile, because we are inconsistent on what order test
> names appear in, and on whether to re-wrap the list of tests or
> just add arbitrary line lengths.  Writing the list in a sorted
> fashion, one test per line, will make future patches easier
> to see what tests are being added or removed by a patch.
> 
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>  tests/Makefile | 160 ++++++++++++++++++++++++++++++++++++++++-----------------
>  1 file changed, 114 insertions(+), 46 deletions(-)
> 

> +qapi-schema += alternate-array.json
> +qapi-schema += alternate-base.json

Hmm, I just realized we require GNU make, and that we already use
$(wildcard) when building up other tests.  Would it be worth writing
this patch to merely use $(wildcard qapi-tests/*.json)?  Then further
additions (and removals) of .json files would automatically be picked up
without requiring Makefile tweaking.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 01/46] qapi: Sort qapi-schema tests
  2015-09-23 14:26   ` Eric Blake
@ 2015-09-23 15:09     ` Markus Armbruster
  2015-09-23 15:19       ` Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-23 15:09 UTC (permalink / raw
  To: Eric Blake; +Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost

Eric Blake <eblake@redhat.com> writes:

> On 09/21/2015 03:57 PM, Eric Blake wrote:
>> Recent changes to qapi have provided quite a bit of churn in
>> the makefile, because we are inconsistent on what order test
>> names appear in, and on whether to re-wrap the list of tests or
>> just add arbitrary line lengths.  Writing the list in a sorted
>> fashion, one test per line, will make future patches easier
>> to see what tests are being added or removed by a patch.
>> 
>> Signed-off-by: Eric Blake <eblake@redhat.com>
>> ---
>>  tests/Makefile | 160 ++++++++++++++++++++++++++++++++++++++++-----------------
>>  1 file changed, 114 insertions(+), 46 deletions(-)
>> 
>
>> +qapi-schema += alternate-array.json
>> +qapi-schema += alternate-base.json
>
> Hmm, I just realized we require GNU make, and that we already use
> $(wildcard) when building up other tests.  Would it be worth writing
> this patch to merely use $(wildcard qapi-tests/*.json)?  Then further
> additions (and removals) of .json files would automatically be picked up
> without requiring Makefile tweaking.

I really dislike picking up source files with $(wildcard), because it
can also pick up random junk.

Something like $(shell git ls-files tests/qapi-schema/*.json) avoids
random junk, but doesn't work when you build a tarball.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 03/46] qapi: Test for C member name collisions
  2015-09-23 14:19             ` Eric Blake
@ 2015-09-23 15:12               ` Markus Armbruster
  0 siblings, 0 replies; 108+ messages in thread
From: Markus Armbruster @ 2015-09-23 15:12 UTC (permalink / raw
  To: Eric Blake
  Cc: qemu-devel, marcandre.lureau, Michael Roth, ehabkost,
	DirtY.iCE.hu

Eric Blake <eblake@redhat.com> writes:

> On 09/23/2015 08:02 AM, Markus Armbruster wrote:
>
>>> However, I'm not sure it would always help.  The conversion of
>>> netdev_add to full qapi relies on being able to access the variant
>>> through a named struct (such as NetdevTapOptions); unboxing the variant
>>> would get rid of the convenient access to these named sub-structs.
>> 
>> struct Union {
>>     EnumType type;
>>     /* union tag is @type */
>>     union {
>>         One one;
>>         Two two;
>>     };
>> };
>> 
>> For base, we go one step further and peel off the struct, to save some
>> notational overhead.  Pointless for unions.
>
> Ah, I see. Instead of malloc'ing a sub-struct and calling (roughly)
>
> ptr = visit_start_struct(Union) // mallocs
> subptr = visit_start_implicit_struct(One) // also mallocs
> visit_type_fields(subptr)
> visit_end_implicit_struct()
>
> we would instead use inline allocation, with:
>
> ptr = visit_start_struct(Union) // mallocs
> visit_type_fields(&ptr->one)
>
> seems straightforward enough; I'll play with the idea on top of my series.

I should be careful what I wish for lest the series grows faster than I
can review it!

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 01/46] qapi: Sort qapi-schema tests
  2015-09-23 15:09     ` Markus Armbruster
@ 2015-09-23 15:19       ` Eric Blake
  0 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-23 15:19 UTC (permalink / raw
  To: Markus Armbruster; +Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost

[-- Attachment #1: Type: text/plain, Size: 2514 bytes --]

On 09/23/2015 09:09 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> On 09/21/2015 03:57 PM, Eric Blake wrote:
>>> Recent changes to qapi have provided quite a bit of churn in
>>> the makefile, because we are inconsistent on what order test
>>> names appear in, and on whether to re-wrap the list of tests or
>>> just add arbitrary line lengths.  Writing the list in a sorted
>>> fashion, one test per line, will make future patches easier
>>> to see what tests are being added or removed by a patch.
>>>
>>> Signed-off-by: Eric Blake <eblake@redhat.com>
>>> ---
>>>  tests/Makefile | 160 ++++++++++++++++++++++++++++++++++++++++-----------------
>>>  1 file changed, 114 insertions(+), 46 deletions(-)
>>>
>>
>>> +qapi-schema += alternate-array.json
>>> +qapi-schema += alternate-base.json
>>
>> Hmm, I just realized we require GNU make, and that we already use
>> $(wildcard) when building up other tests.  Would it be worth writing
>> this patch to merely use $(wildcard qapi-tests/*.json)?  Then further
>> additions (and removals) of .json files would automatically be picked up
>> without requiring Makefile tweaking.
> 
> I really dislike picking up source files with $(wildcard), because it
> can also pick up random junk.

I agree that it is dangerous (the automake manual specifically
recommends against wildcarding for this reason, even when done without
relying on GNU $(wildcard) syntax).  It was more a question of "since we
are already doing it, should we do it more?"

Looking closer, the existing uses of $(wildcard) in tests/Makefile are
for including .d files (those are generated on the fly, and while still
prone to accidentally including leftover garbage files, such extra
inclusions tend to have no negative impact to make dependency tracking
for the targets we still care about), and for the SYSEMU_TARGET_LIST
(again something that relies on the just-generated default-configs/*.mak
magic).  Whereas choosing which tests to run does cause problems if
bogus tests are added.

> 
> Something like $(shell git ls-files tests/qapi-schema/*.json) avoids
> random junk, but doesn't work when you build a tarball.

Then it sounds like my approach of keeping a verbose list is still best
after all, and at most I should update the commit message to say _why_ I
specifically chose not to use $(wildcard) here.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 45/46] net: Complete qapi-fication of netdev_add
  2015-09-21 21:58 ` [Qemu-devel] [PATCH v5 45/46] net: Complete qapi-fication of netdev_add Eric Blake
@ 2015-09-23 15:40   ` Paolo Bonzini
  2015-09-23 16:37     ` Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Paolo Bonzini @ 2015-09-23 15:40 UTC (permalink / raw
  To: Eric Blake, qemu-devel
  Cc: marcandre.lureau, Jason Wang, armbru, ehabkost, DirtY.iCE.hu



On 21/09/2015 23:58, Eric Blake wrote:
> We finally have all the required pieces for doing a type-safe
> representation of netdev_add as a flat union, where the
> discriminator 'type' now selects which additional members may
> appear in the "arguments" JSON object sent over QMP, while
> making no changes to the set of previously-valid QMP commands
> that would work, and without breaking command line parsing.

Is this still type-unsafe like the old netdev_add (e.g. accepts a string
for an integer)?

Paolo

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 45/46] net: Complete qapi-fication of netdev_add
  2015-09-23 15:40   ` Paolo Bonzini
@ 2015-09-23 16:37     ` Eric Blake
  2015-09-25 16:48       ` Paolo Bonzini
  0 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-23 16:37 UTC (permalink / raw
  To: Paolo Bonzini, qemu-devel
  Cc: marcandre.lureau, Jason Wang, armbru, ehabkost, DirtY.iCE.hu

[-- Attachment #1: Type: text/plain, Size: 1185 bytes --]

On 09/23/2015 09:40 AM, Paolo Bonzini wrote:
> 
> 
> On 21/09/2015 23:58, Eric Blake wrote:
>> We finally have all the required pieces for doing a type-safe
>> representation of netdev_add as a flat union, where the
>> discriminator 'type' now selects which additional members may
>> appear in the "arguments" JSON object sent over QMP, while
>> making no changes to the set of previously-valid QMP commands
>> that would work, and without breaking command line parsing.
> 
> Is this still type-unsafe like the old netdev_add (e.g. accepts a string
> for an integer)?

I did not address that yet; it still needs further patches to accept an
integer as a port number. I can investigate what additional patches are
needed along those lines, while still preserving back-compat to
previously-accepted QMP command lines (it may require the use of an
'alternate' type that accepts both int and string).  The main goal here
was that the command line is unchanged, and that now the QMP command is
introspectable, even if what introspection shows is ugly types.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 35/46] qapi-visit: Remove redundant functions for flat union base
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 35/46] qapi-visit: Remove redundant functions for flat union base Eric Blake
@ 2015-09-23 20:55   ` Eric Blake
  0 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-23 20:55 UTC (permalink / raw
  To: qemu-devel; +Cc: marcandre.lureau, ehabkost, Michael Roth, armbru, DirtY.iCE.hu

[-- Attachment #1: Type: text/plain, Size: 1803 bytes --]

On 09/21/2015 03:57 PM, Eric Blake wrote:
> The code for visiting the base class of a child struct created
> visit_type_Base_fields(); the code for visiting the base class
> of a flat union created visit_type_Union_fields(). If the same
> type is shared between a struct and a union, the two functions
> differed only by whether they visited the discriminator used by
> the union. But if the base class always visits all its fields,
> then we don't need to revisit the discriminator specially for
> a union.  By consistently visiting the base class fields under
> the name of the base class, we can eliminate some redundant
> code.
> 
> Now that gen_visit_struct_fields() can potentially collect more
> than one function into 'ret', a regular expression searching for
> whether a label was used may hit a false positive within the
> body of the first function.  But using a regex was overkill,
> since we can easily determine when we jumped to a label.
> 

>      visit_type_%(c_type)s(v, &(*obj)->%(c_name)s, "%(name)s", &err);
> -    if (err) {
> -        goto out_obj;
> -    }
> -    switch ((*obj)->%(c_name)s) {
>  ''',
> -                 c_type=variants.tag_member.type.c_name(),
> -                 c_name=c_name(tag_key), name=tag_key)
> +                     c_type=variants.tag_member.type.c_name(),
> +                     c_name=c_name(tag_key), name=tag_key)
> +    ret += mcgen('''
> +        if (err) {
> +            goto out_obj;
> +        }
> +        switch ((*obj)->%(c_name)s) {

Rebase snafu - I botched the generated indentation within this mcgen()
by 4 spaces. Shouldn't affect a technical review, and I will fix it up
for v6.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 46/46] qapi: Allow anonymous base for flat union
  2015-09-21 21:58 ` [Qemu-devel] [PATCH v5 46/46] qapi: Allow anonymous base for flat union Eric Blake
@ 2015-09-23 20:59   ` Eric Blake
  0 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-23 20:59 UTC (permalink / raw
  To: qemu-devel; +Cc: marcandre.lureau, ehabkost, Michael Roth, armbru, DirtY.iCE.hu

[-- Attachment #1: Type: text/plain, Size: 1923 bytes --]

On 09/21/2015 03:58 PM, Eric Blake wrote:
> Rather than requiring all flat unions to explicitly create
> a separate base struct, we want to allow the qapi schema
> to specify the common fields via an inline dictionary. This
> is similar to how commands can specify inline types for the
> arguments.
> 
> Now that the feature is legal, we can drop the former
> flat-union-bad-base negative test, and instead change the
> positive tests in qapi-schema-test to use it.
> 
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---

> +++ b/scripts/qapi-visit.py

> @@ -272,10 +272,17 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
> 
>      tag_key = variants.tag_member.name
>      if base:
> -        ret += mcgen('''
> +        if not base.is_implicit():
> +            ret += mcgen('''
>      visit_type_%(c_name)s_fields(v, (%(c_name)s **)obj, &err);
>  ''',
> -                     c_name=c_name(base.name))
> +                         c_name=c_name(base.name))
> +        else:
> +            push_indent()
> +            ret += gen_visit_fields(base.members, '(*obj)->', False,
> +                                    'err', 'out_obj')
> +            pop_indent()
> +        tag_key = variants.tag_member.name
>      else:
>          ret += mcgen('''
>      visit_type_%(c_type)s(v, &(*obj)->%(c_name)s, "%(name)s", &err);
>  ''',
>                      c_type=variants.tag_member.type.c_name(),
>                      c_name=c_name(tag_key), name=tag_key)
>     ret += mcgen('''
>         if (err) {
>             goto out_obj;
>         }

Whoops - this results in two back-to-back 'if (err)' clauses in the
generated output when an implicit base is used (harmless, other than
wasted effort). I'll avoid the duplication in v6.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 04/46] qapi: Add tests for empty unions
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 04/46] qapi: Add tests for empty unions Eric Blake
@ 2015-09-24 14:16   ` Markus Armbruster
  2015-09-24 15:52     ` Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-24 14:16 UTC (permalink / raw
  To: Eric Blake
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

Eric Blake <eblake@redhat.com> writes:

> The documentation claims that alternates are useful for
> allowing two types, although nothing enforces this.  Meanwhile,
> it is silent on whether empty unions are allowed.  In practice,
> the generated code will compile, in part because we have a
> 'void *data' branch; but attempting to visit such a type will
> cause an abort().  Add some tests to expose the problems, and

There's nothing really wrong with degenerate alternates or unions, but I
don't want to spend time on making them work.  Outlawing them will do.
The commit message could perhaps be rephrased a bit to better convey
that.

What about empty structs and enums?

> adjust existing tests that should be failing for other reasons.

I had to read this a few times until I understood "should be failing for
other reasons" means something like "are meant to test something else,
but could fail for the wrong reason if we reject degenerate alternates /
unions".

> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>  tests/Makefile                           | 3 +++
>  tests/qapi-schema/alternate-empty.err    | 0
>  tests/qapi-schema/alternate-empty.exit   | 1 +
>  tests/qapi-schema/alternate-empty.json   | 2 ++
>  tests/qapi-schema/alternate-empty.out    | 4 ++++
>  tests/qapi-schema/alternate-nested.json  | 2 +-
>  tests/qapi-schema/alternate-unknown.json | 2 +-
>  tests/qapi-schema/flat-union-empty.err   | 0
>  tests/qapi-schema/flat-union-empty.exit  | 1 +
>  tests/qapi-schema/flat-union-empty.json  | 4 ++++
>  tests/qapi-schema/flat-union-empty.out   | 7 +++++++
>  tests/qapi-schema/union-empty.err        | 0
>  tests/qapi-schema/union-empty.exit       | 1 +
>  tests/qapi-schema/union-empty.json       | 2 ++
>  tests/qapi-schema/union-empty.out        | 3 +++
>  15 files changed, 30 insertions(+), 2 deletions(-)
>  create mode 100644 tests/qapi-schema/alternate-empty.err
>  create mode 100644 tests/qapi-schema/alternate-empty.exit
>  create mode 100644 tests/qapi-schema/alternate-empty.json
>  create mode 100644 tests/qapi-schema/alternate-empty.out
>  create mode 100644 tests/qapi-schema/flat-union-empty.err
>  create mode 100644 tests/qapi-schema/flat-union-empty.exit
>  create mode 100644 tests/qapi-schema/flat-union-empty.json
>  create mode 100644 tests/qapi-schema/flat-union-empty.out
>  create mode 100644 tests/qapi-schema/union-empty.err
>  create mode 100644 tests/qapi-schema/union-empty.exit
>  create mode 100644 tests/qapi-schema/union-empty.json
>  create mode 100644 tests/qapi-schema/union-empty.out
>
> diff --git a/tests/Makefile b/tests/Makefile
> index 97434f6..df16c9c 100644
> --- a/tests/Makefile
> +++ b/tests/Makefile
> @@ -226,6 +226,7 @@ qapi-schema += alternate-base.json
>  qapi-schema += alternate-clash.json
>  qapi-schema += alternate-conflict-dict.json
>  qapi-schema += alternate-conflict-string.json
> +qapi-schema += alternate-empty.json
>  qapi-schema += alternate-good.json
>  qapi-schema += alternate-nested.json
>  qapi-schema += alternate-unknown.json
> @@ -277,6 +278,7 @@ qapi-schema += flat-union-base-union.json
>  qapi-schema += flat-union-branch-clash.json
>  qapi-schema += flat-union-branch-clash2.json
>  qapi-schema += flat-union-cycle.json
> +qapi-schema += flat-union-empty.json
>  qapi-schema += flat-union-inline.json
>  qapi-schema += flat-union-int-branch.json
>  qapi-schema += flat-union-invalid-branch-key.json
> @@ -334,6 +336,7 @@ qapi-schema += union-bad-branch.json
>  qapi-schema += union-base-no-discriminator.json
>  qapi-schema += union-clash.json
>  qapi-schema += union-clash2.json
> +qapi-schema += union-empty.json
>  qapi-schema += union-invalid-base.json
>  qapi-schema += union-max.json
>  qapi-schema += union-optional-branch.json
> diff --git a/tests/qapi-schema/alternate-empty.err b/tests/qapi-schema/alternate-empty.err
> new file mode 100644
> index 0000000..e69de29
> diff --git a/tests/qapi-schema/alternate-empty.exit b/tests/qapi-schema/alternate-empty.exit
> new file mode 100644
> index 0000000..573541a
> --- /dev/null
> +++ b/tests/qapi-schema/alternate-empty.exit
> @@ -0,0 +1 @@
> +0
> diff --git a/tests/qapi-schema/alternate-empty.json b/tests/qapi-schema/alternate-empty.json
> new file mode 100644
> index 0000000..db3820f
> --- /dev/null
> +++ b/tests/qapi-schema/alternate-empty.json
> @@ -0,0 +1,2 @@
> +# FIXME - alternates should list at least two types to be useful
> +{ 'alternate': 'Alt', 'data': { 'i': 'int' } }
> diff --git a/tests/qapi-schema/alternate-empty.out b/tests/qapi-schema/alternate-empty.out
> new file mode 100644
> index 0000000..0f153b6
> --- /dev/null
> +++ b/tests/qapi-schema/alternate-empty.out
> @@ -0,0 +1,4 @@
> +object :empty
> +alternate Alt
> +    case i: int
> +enum AltKind ['i']
> diff --git a/tests/qapi-schema/alternate-nested.json b/tests/qapi-schema/alternate-nested.json
> index c4233b9..8e22186 100644
> --- a/tests/qapi-schema/alternate-nested.json
> +++ b/tests/qapi-schema/alternate-nested.json
> @@ -2,4 +2,4 @@
>  { 'alternate': 'Alt1',
>    'data': { 'name': 'str', 'value': 'int' } }
>  { 'alternate': 'Alt2',
> -  'data': { 'nested': 'Alt1' } }
> +  'data': { 'nested': 'Alt1', 'b': 'bool' } }
> diff --git a/tests/qapi-schema/alternate-unknown.json b/tests/qapi-schema/alternate-unknown.json
> index ad5c103..08c80dc 100644
> --- a/tests/qapi-schema/alternate-unknown.json
> +++ b/tests/qapi-schema/alternate-unknown.json
> @@ -1,3 +1,3 @@
>  # we reject an alternate with unknown type in branch
>  { 'alternate': 'Alt',
> -  'data': { 'unknown': 'MissingType' } }
> +  'data': { 'unknown': 'MissingType', 'i': 'int' } }
> diff --git a/tests/qapi-schema/flat-union-empty.err b/tests/qapi-schema/flat-union-empty.err
> new file mode 100644
> index 0000000..e69de29
> diff --git a/tests/qapi-schema/flat-union-empty.exit b/tests/qapi-schema/flat-union-empty.exit
> new file mode 100644
> index 0000000..573541a
> --- /dev/null
> +++ b/tests/qapi-schema/flat-union-empty.exit
> @@ -0,0 +1 @@
> +0
> diff --git a/tests/qapi-schema/flat-union-empty.json b/tests/qapi-schema/flat-union-empty.json
> new file mode 100644
> index 0000000..67dd297
> --- /dev/null
> +++ b/tests/qapi-schema/flat-union-empty.json
> @@ -0,0 +1,4 @@
> +# FIXME - flat unions should not be empty
> +{ 'enum': 'Empty', 'data': [ ] }
> +{ 'struct': 'Base', 'data': { 'type': 'Empty' } }
> +{ 'union': 'Union', 'base': 'Base', 'discriminator': 'type', 'data': { } }

Here you can see how empty flat unions are connected to empty enums.

> diff --git a/tests/qapi-schema/flat-union-empty.out b/tests/qapi-schema/flat-union-empty.out
> new file mode 100644
> index 0000000..0e0665a
> --- /dev/null
> +++ b/tests/qapi-schema/flat-union-empty.out
> @@ -0,0 +1,7 @@
> +object :empty
> +object Base
> +    member type: Empty optional=False
> +enum Empty []
> +object Union
> +    base Base
> +    tag type
> diff --git a/tests/qapi-schema/union-empty.err b/tests/qapi-schema/union-empty.err
> new file mode 100644
> index 0000000..e69de29
> diff --git a/tests/qapi-schema/union-empty.exit b/tests/qapi-schema/union-empty.exit
> new file mode 100644
> index 0000000..573541a
> --- /dev/null
> +++ b/tests/qapi-schema/union-empty.exit
> @@ -0,0 +1 @@
> +0
> diff --git a/tests/qapi-schema/union-empty.json b/tests/qapi-schema/union-empty.json
> new file mode 100644
> index 0000000..1785007
> --- /dev/null
> +++ b/tests/qapi-schema/union-empty.json
> @@ -0,0 +1,2 @@
> +# FIXME - unions should not be empty
> +{ 'union': 'Union', 'data': { } }
> diff --git a/tests/qapi-schema/union-empty.out b/tests/qapi-schema/union-empty.out
> new file mode 100644
> index 0000000..8b5a7bf
> --- /dev/null
> +++ b/tests/qapi-schema/union-empty.out
> @@ -0,0 +1,3 @@
> +object :empty
> +object Union
> +enum UnionKind []

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 05/46] qapi: Test use of 'number' within alternates
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 05/46] qapi: Test use of 'number' within alternates Eric Blake
@ 2015-09-24 14:36   ` Markus Armbruster
  2015-09-24 16:00     ` Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-24 14:36 UTC (permalink / raw
  To: Eric Blake
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Add some testsuite exposure for use of a 'number' as part of
> an alternate.  The current state of the tree has a few bugs
> exposed by this: our input parser depends on the ordering of
> how the qapi schema declared the alternate, and the parser
> does not accept integers for a 'number' in an alternate even
> though it does for numbers outside of an alternate.
>
> Mixing 'int' and 'number' in the same alternate is unusual,
> since both are supplied by json-numbers, but there does not
> seem to be a technical reason to forbid it given that our
> json lexer distinguishes between json-numbers that can be
> represented as an int vs. those that cannot.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>  tests/qapi-schema/qapi-schema-test.json |   8 ++
>  tests/qapi-schema/qapi-schema-test.out  |  24 ++++++
>  tests/test-qmp-input-visitor.c          | 129 +++++++++++++++++++++++++++++++-
>  3 files changed, 158 insertions(+), 3 deletions(-)
>
> diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
> index c904db4..7593ecb 100644
> --- a/tests/qapi-schema/qapi-schema-test.json
> +++ b/tests/qapi-schema/qapi-schema-test.json
> @@ -65,6 +65,14 @@
>  { 'struct': 'UserDefC',
>    'data': { 'string1': 'str', 'string2': 'str' } }
>
> +# for testing use of 'number' within alternates
> +{ 'alternate': 'AltOne', 'data': { 's': 'str', 'b': 'bool' } }
> +{ 'alternate': 'AltTwo', 'data': { 's': 'str', 'n': 'number' } }
> +{ 'alternate': 'AltThree', 'data': { 'n': 'number', 's': 'str' } }
> +{ 'alternate': 'AltFour', 'data': { 's': 'str', 'i': 'int' } }
> +{ 'alternate': 'AltFive', 'data': { 'i': 'int', 'n': 'number' } }
> +{ 'alternate': 'AltSix', 'data': { 'n': 'number', 'i': 'int' } }
> +
>  # for testing native lists
>  { 'union': 'UserDefNativeListUnion',
>    'data': { 'integer': ['int'],
> diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
> index 28a0b3c..de29a45 100644
> --- a/tests/qapi-schema/qapi-schema-test.out
> +++ b/tests/qapi-schema/qapi-schema-test.out
> @@ -53,6 +53,30 @@ object :obj-user_def_cmd2-arg
>  object :obj-user_def_cmd3-arg
>      member a: int optional=False
>      member b: int optional=True
> +alternate AltFive
> +    case i: int
> +    case n: number
> +enum AltFiveKind ['i', 'n']
> +alternate AltFour
> +    case s: str
> +    case i: int
> +enum AltFourKind ['s', 'i']
> +alternate AltOne
> +    case s: str
> +    case b: bool
> +enum AltOneKind ['s', 'b']
> +alternate AltSix
> +    case n: number
> +    case i: int
> +enum AltSixKind ['n', 'i']
> +alternate AltThree
> +    case n: number
> +    case s: str
> +enum AltThreeKind ['n', 's']
> +alternate AltTwo
> +    case s: str
> +    case n: number
> +enum AltTwoKind ['s', 'n']
>  event EVENT_A None
>  event EVENT_B None
>  event EVENT_C :obj-EVENT_C-arg
> diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
> index 61715b3..cd41847 100644
> --- a/tests/test-qmp-input-visitor.c
> +++ b/tests/test-qmp-input-visitor.c
> @@ -368,15 +368,136 @@ static void test_visitor_in_alternate(TestInputVisitorData *data,
>  {
>      Visitor *v;
>      Error *err = NULL;
> -    UserDefAlternate *tmp;
> +    UserDefAlternate *tmp = NULL;

Any particular reason for adding the initializer?

>
>      v = visitor_input_test_init(data, "42");
>
> -    visit_type_UserDefAlternate(v, &tmp, NULL, &err);
> -    g_assert(err == NULL);
> +    visit_type_UserDefAlternate(v, &tmp, NULL, &error_abort);

The pattern

       foo(..., &err);
       g_assert(err == NULL);

is pretty common in tests.  Can't see what it buys us over straight
&error_abort.  Perhaps I'll spatch it away.

>      g_assert_cmpint(tmp->kind, ==, USER_DEF_ALTERNATE_KIND_I);
>      g_assert_cmpint(tmp->i, ==, 42);
>      qapi_free_UserDefAlternate(tmp);
> +    tmp = NULL;

Why do you need to clear tmp?

> +
> +    v = visitor_input_test_init(data, "'string'");
> +
> +    visit_type_UserDefAlternate(v, &tmp, NULL, &error_abort);
> +    g_assert_cmpint(tmp->kind, ==, USER_DEF_ALTERNATE_KIND_S);
> +    g_assert_cmpstr(tmp->s, ==, "string");
> +    qapi_free_UserDefAlternate(tmp);
> +    tmp = NULL;
> +
> +    v = visitor_input_test_init(data, "false");
> +
> +    visit_type_UserDefAlternate(v, &tmp, NULL, &err);
> +    g_assert(err);
> +    error_free(err);
> +    err = NULL;
> +    qapi_free_UserDefAlternate(tmp);

Okay, here you merely make the test of existing UserDefAlternate more
thorough.  Could be a separate patch, but keeping it in this one is fine
with me, too.

> +}
> +
> +static void test_visitor_in_alternate_number(TestInputVisitorData *data,
> +                                             const void *unused)
> +{
> +    Visitor *v;
> +    Error *err = NULL;
> +    AltOne *one = NULL;
> +    AltTwo *two = NULL;
> +    AltThree *three = NULL;
> +    AltFour *four = NULL;
> +    AltFive *five = NULL;
> +    AltSix *six = NULL;
> +
> +    /* Parsing an int */
> +
> +    v = visitor_input_test_init(data, "42");
> +    visit_type_AltOne(v, &one, NULL, &err);
> +    g_assert(err);
> +    qapi_free_AltOne(one);
> +    one = NULL;

Similar to the new last test in test_visitor_in_alternate().  Not sure
that one's needed.

> +
> +    /* FIXME: Integers should parse as numbers */

Suggest to augment or replace this comment...

> +    v = visitor_input_test_init(data, "42");
> +    visit_type_AltTwo(v, &two, NULL, &err);

... with

       /* FIXME g_assert_cmpint(two->kind, ==, ALT_TWO_KIND_N); */
       /* FIXME g_assert_cmpfloat(two->n, ==, 42); */

> +    g_assert(err);
> +    error_free(err);
> +    err = NULL;
> +    qapi_free_AltTwo(two);
> +    one = NULL;

*chuckle*  Why do you clear one here?  More of the same below.

> +
> +    /* FIXME: Order of alternate should not affect semantics */

Inhowfar does it affect semantics?  Or asked differently: what exactly
is wrong with this test now?

> +    v = visitor_input_test_init(data, "42");
> +    visit_type_AltThree(v, &three, NULL, &error_abort);
> +    g_assert_cmpint(three->kind, ==, ALT_THREE_KIND_N);
> +    g_assert_cmpfloat(three->n, ==, 42);
> +    qapi_free_AltThree(three);
> +    one = NULL;
> +
> +    v = visitor_input_test_init(data, "42");
> +    visit_type_AltFour(v, &four, NULL, &error_abort);
> +    g_assert_cmpint(four->kind, ==, ALT_FOUR_KIND_I);
> +    g_assert_cmpint(four->i, ==, 42);
> +    qapi_free_AltFour(four);
> +    one = NULL;
> +
> +    v = visitor_input_test_init(data, "42");
> +    visit_type_AltFive(v, &five, NULL, &error_abort);
> +    g_assert_cmpint(five->kind, ==, ALT_FIVE_KIND_I);
> +    g_assert_cmpint(five->i, ==, 42);
> +    qapi_free_AltFive(five);
> +    one = NULL;
> +
> +    v = visitor_input_test_init(data, "42");
> +    visit_type_AltSix(v, &six, NULL, &error_abort);
> +    g_assert_cmpint(six->kind, ==, ALT_SIX_KIND_I);
> +    g_assert_cmpint(six->i, ==, 42);
> +    qapi_free_AltSix(six);
> +    one = NULL;
> +
> +    /* Parsing a double */
> +
> +    v = visitor_input_test_init(data, "42.5");
> +    visit_type_AltOne(v, &one, NULL, &err);
> +    g_assert(err);
> +    error_free(err);
> +    err = NULL;
> +    qapi_free_AltOne(one);
> +    one = NULL;
> +
> +    v = visitor_input_test_init(data, "42.5");
> +    visit_type_AltTwo(v, &two, NULL, &error_abort);
> +    g_assert_cmpint(two->kind, ==, ALT_TWO_KIND_N);
> +    g_assert_cmpfloat(two->n, ==, 42.5);
> +    qapi_free_AltTwo(two);
> +    two = NULL;
> +
> +    v = visitor_input_test_init(data, "42.5");
> +    visit_type_AltThree(v, &three, NULL, &error_abort);
> +    g_assert_cmpint(three->kind, ==, ALT_THREE_KIND_N);
> +    g_assert_cmpfloat(three->n, ==, 42.5);
> +    qapi_free_AltThree(three);
> +    three = NULL;
> +
> +    v = visitor_input_test_init(data, "42.5");
> +    visit_type_AltFour(v, &four, NULL, &err);
> +    g_assert(err);
> +    error_free(err);
> +    err = NULL;
> +    qapi_free_AltFour(four);
> +    four = NULL;
> +
> +    v = visitor_input_test_init(data, "42.5");
> +    visit_type_AltFive(v, &five, NULL, &error_abort);
> +    g_assert_cmpint(five->kind, ==, ALT_FIVE_KIND_N);
> +    g_assert_cmpfloat(five->n, ==, 42.5);
> +    qapi_free_AltFive(five);
> +    five = NULL;
> +
> +    v = visitor_input_test_init(data, "42.5");
> +    visit_type_AltSix(v, &six, NULL, &error_abort);
> +    g_assert_cmpint(six->kind, ==, ALT_SIX_KIND_N);
> +    g_assert_cmpint(six->n, ==, 42.5);
> +    qapi_free_AltSix(six);
> +    six = NULL;

Reading this, I had to refer back to the definition of AltOne, ...,
AltSix all the time.  Let's rename them to AltStrBool, AltStrNum, ...,
AltNumInt.

>  }
>
>  static void test_native_list_integer_helper(TestInputVisitorData *data,
> @@ -720,6 +841,8 @@ int main(int argc, char **argv)
>                             &in_visitor_data, test_visitor_in_alternate);
>      input_visitor_test_add("/visitor/input/errors",
>                             &in_visitor_data, test_visitor_in_errors);
> +    input_visitor_test_add("/visitor/input/alternate-number",
> +                           &in_visitor_data, test_visitor_in_alternate_number);
>      input_visitor_test_add("/visitor/input/native_list/int",
>                             &in_visitor_data,
>                             test_visitor_in_native_list_int);

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 06/46] qapi: Improve 'include' error message
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 06/46] qapi: Improve 'include' error message Eric Blake
@ 2015-09-24 14:39   ` Markus Armbruster
  2015-09-24 16:04     ` Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-24 14:39 UTC (permalink / raw
  To: Eric Blake
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Use of '"...%s" % include' to print non-strings can lead to
> ugly messages, such as this (if the .json change is applied
> without the qapi.py change):
>  Expected a file name (string), got: OrderedDict()
>
> Better is to just omit the actual non-string value in the
> message.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>  scripts/qapi.py                         | 4 ++--
>  tests/qapi-schema/include-non-file.err  | 2 +-
>  tests/qapi-schema/include-non-file.json | 2 +-
>  3 files changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 31c4bcc..007349e 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -138,8 +138,8 @@ class QAPISchemaParser(object):
>                                          "Invalid 'include' directive")
>                  include = expr["include"]
>                  if not isinstance(include, str):
> -                    raise QAPIExprError(expr_info, 'Expected a file name '
> -                                        '(string), got: %s' % include)
> +                    raise QAPIExprError(expr_info,
> +                                        "Expected a string for 'include'")
>                  incl_abs_fname = os.path.join(os.path.dirname(abs_fname),
>                                                include)
>                  # catch inclusion cycle
> diff --git a/tests/qapi-schema/include-non-file.err b/tests/qapi-schema/include-non-file.err
> index 9658c78..079120b 100644
> --- a/tests/qapi-schema/include-non-file.err
> +++ b/tests/qapi-schema/include-non-file.err
> @@ -1 +1 @@
> -tests/qapi-schema/include-non-file.json:1: Expected a file name (string), got: ['foo', 'bar']
> +tests/qapi-schema/include-non-file.json:1: Expected a string for 'include'
> diff --git a/tests/qapi-schema/include-non-file.json b/tests/qapi-schema/include-non-file.json
> index cd43c3f..4711aa4 100644
> --- a/tests/qapi-schema/include-non-file.json
> +++ b/tests/qapi-schema/include-non-file.json
> @@ -1 +1 @@
> -{ 'include': [ 'foo', 'bar' ] }
> +{ 'include': {} }

What about "Value of 'include' must be a string'?

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call Eric Blake
@ 2015-09-24 14:58   ` Markus Armbruster
  2015-09-24 16:14     ` Eric Blake
  2015-09-26 21:41     ` [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call Eric Blake
  0 siblings, 2 replies; 108+ messages in thread
From: Markus Armbruster @ 2015-09-24 14:58 UTC (permalink / raw
  To: Eric Blake
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Due to the existing semantics of the error_set() family,
> generated sequences in the qapi visitors such as:
>
>     visit_start_implicit_struct(m, (void **)obj, sizeof(FOO), &err);
>         if (!err) {
>             visit_type_FOO_fields(m, obj, errp);
>             visit_end_implicit_struct(m, &err);
>         }
>     error_propagate(errp, err);

Indentation seems off.  Intentional?

>
> are risky: if visit_type_FOO_fields() sets errp, and then
> visit_end_implicit_struct() also encounters an error, the
> attempt to overwrite the first error will cause an abort().
> Obviously, we weren't triggering this situation (none of the
> existing callbacks for visit_end_implicit_struct() currently
> try to set an error), but it is better to not even cause the
> problem in the first place.

The code works, but it sets a problematic example.

> Meanwhile, in spite of the poor documentation of the qapi
> visitors, we want to guarantee that if a visit_start_*()
> succeeds, then the matching visit_end_*() will be called.

Agreed.

> The options are to either propagate and clear a local err
> multiple times:
>
>     visit_start_implicit_struct(m, (void **)obj, sizeof(FOO), &err);
>         if (!err) {
>             visit_type_FOO_fields(m, obj, &err);
>             if (err) {
>                 error_propagate(errp, err);
>                 err = NULL;
>             }
>             visit_end_implicit_struct(m, &err);
>         }
>     error_propagate(errp, err);
>
> or, as this patch does, just pass in NULL to ignore further
> errors once an error has occurred.
>
>     visit_start_implicit_struct(m, (void **)obj, sizeof(FOO), &err);
>         if (!err) {
>             visit_type_FOO_fields(m, obj, &err);
>             visit_end_implicit_struct(m, err ? NULL : &err);
>         }
>     error_propagate(errp, err);

Hmmmmm... not sure we do this anywhere else, yet.  The ternary isn't
exactly pretty, but the intent to ignore additional error is clear
enough, I think.

If we elect to adopt this new error handling pattern, we should perhaps
document it in error.h.

Third option: drop visit_end_implicit_struct()'s errp parameter.  If we
find a compelling use for it, we'll put it back and solve the problem.

>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>  scripts/qapi-visit.py | 20 +++++++-------------
>  1 file changed, 7 insertions(+), 13 deletions(-)
>
> diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
> index 97343cf..d911b20 100644
> --- a/scripts/qapi-visit.py
> +++ b/scripts/qapi-visit.py
> @@ -51,8 +51,8 @@ static void visit_type_implicit_%(c_type)s(Visitor *m, %(c_type)s **obj, Error *
>
>      visit_start_implicit_struct(m, (void **)obj, sizeof(%(c_type)s), &err);
>      if (!err) {
> -        visit_type_%(c_type)s_fields(m, obj, errp);
> -        visit_end_implicit_struct(m, &err);
> +        visit_type_%(c_type)s_fields(m, obj, &err);
> +        visit_end_implicit_struct(m, err ? NULL : &err);
>      }
>      error_propagate(errp, err);
>  }
> @@ -143,9 +143,9 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error
>      visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err);
>      if (!err) {
>          if (*obj) {
> -            visit_type_%(c_name)s_fields(m, obj, errp);
> +            visit_type_%(c_name)s_fields(m, obj, &err);
>          }
> -        visit_end_struct(m, &err);
> +        visit_end_struct(m, err ? NULL : &err);
>      }
>      error_propagate(errp, err);
>  }

Oh, it's about visit_end_struct(), too.  Commit message only talks about
visit_end_implicit_struct().

In particular, "none of the existing callbacks for
visit_end_implicit_struct() currently try to set an error".  Does that
hold for visit_end_struct() callbacks, too?

> @@ -175,9 +175,7 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error
>          visit_type_%(c_elt_type)s(m, &native_i->value, NULL, &err);
>      }
>
> -    error_propagate(errp, err);
> -    err = NULL;
> -    visit_end_list(m, &err);
> +    visit_end_list(m, err ? NULL : &err);
>  out:
>      error_propagate(errp, err);
>  }

Likewise.  Does it hold for visit_end_list() callbacks, too?

Looks like you switch from option 1 to option 2 here.  Your slate isn't
as clean as the commit message suggests :)

> @@ -231,9 +229,7 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error
>          abort();
>      }
>  out_end:
> -    error_propagate(errp, err);
> -    err = NULL;
> -    visit_end_implicit_struct(m, &err);
> +    visit_end_implicit_struct(m, err ? NULL : &err);
>  out:
>      error_propagate(errp, err);
>  }

Looks like another switch from option 1 to option 2.

> @@ -330,10 +326,8 @@ out_obj:
>          error_propagate(errp, err);
>          err = NULL;
>          visit_end_union(m, !!(*obj)->data, &err);
> -        error_propagate(errp, err);
> -        err = NULL;
>      }
> -    visit_end_struct(m, &err);
> +    visit_end_struct(m, err ? NULL : &err);
>  out:
>      error_propagate(errp, err);
>  }

Again.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 04/46] qapi: Add tests for empty unions
  2015-09-24 14:16   ` Markus Armbruster
@ 2015-09-24 15:52     ` Eric Blake
  0 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-24 15:52 UTC (permalink / raw
  To: Markus Armbruster
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

[-- Attachment #1: Type: text/plain, Size: 1497 bytes --]

On 09/24/2015 08:16 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> The documentation claims that alternates are useful for
>> allowing two types, although nothing enforces this.  Meanwhile,
>> it is silent on whether empty unions are allowed.  In practice,
>> the generated code will compile, in part because we have a
>> 'void *data' branch; but attempting to visit such a type will
>> cause an abort().  Add some tests to expose the problems, and
> 
> There's nothing really wrong with degenerate alternates or unions, but I
> don't want to spend time on making them work.  Outlawing them will do.
> The commit message could perhaps be rephrased a bit to better convey
> that.

Sure, that sounds like a better wording to weave in.

> 
> What about empty structs and enums?

Empty structs: required support (for example, 'Abort' as a member of
'transaction's union).

Empty enums: Documented as not very useful but currently supported, and
we already have code that tests that we don't choke on them.

> 
>> adjust existing tests that should be failing for other reasons.
> 
> I had to read this a few times until I understood "should be failing for
> other reasons" means something like "are meant to test something else,
> but could fail for the wrong reason if we reject degenerate alternates /
> unions".

I like it.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 05/46] qapi: Test use of 'number' within alternates
  2015-09-24 14:36   ` Markus Armbruster
@ 2015-09-24 16:00     ` Eric Blake
  2015-09-24 16:29       ` Markus Armbruster
  0 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-24 16:00 UTC (permalink / raw
  To: Markus Armbruster
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

[-- Attachment #1: Type: text/plain, Size: 4578 bytes --]

On 09/24/2015 08:36 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Add some testsuite exposure for use of a 'number' as part of
>> an alternate.  The current state of the tree has a few bugs
>> exposed by this: our input parser depends on the ordering of
>> how the qapi schema declared the alternate, and the parser
>> does not accept integers for a 'number' in an alternate even
>> though it does for numbers outside of an alternate.
>>
>> Mixing 'int' and 'number' in the same alternate is unusual,
>> since both are supplied by json-numbers, but there does not
>> seem to be a technical reason to forbid it given that our
>> json lexer distinguishes between json-numbers that can be
>> represented as an int vs. those that cannot.
>>
>> Signed-off-by: Eric Blake <eblake@redhat.com>
>> ---
>>  tests/qapi-schema/qapi-schema-test.json |   8 ++
>>  tests/qapi-schema/qapi-schema-test.out  |  24 ++++++
>>  tests/test-qmp-input-visitor.c          | 129 +++++++++++++++++++++++++++++++-
>>  3 files changed, 158 insertions(+), 3 deletions(-)
>>

>> +++ b/tests/test-qmp-input-visitor.c
>> @@ -368,15 +368,136 @@ static void test_visitor_in_alternate(TestInputVisitorData *data,
>>  {
>>      Visitor *v;
>>      Error *err = NULL;
>> -    UserDefAlternate *tmp;
>> +    UserDefAlternate *tmp = NULL;
> 
> Any particular reason for adding the initializer?
> 
>>
>>      v = visitor_input_test_init(data, "42");
>>
>> -    visit_type_UserDefAlternate(v, &tmp, NULL, &err);
>> -    g_assert(err == NULL);
>> +    visit_type_UserDefAlternate(v, &tmp, NULL, &error_abort);

Hmm - I don't know if we have a clear contract for what happens if you
call visit_type_FOO on an uninitialized pointer.  It may have been
succeeding by mere luck.

> 
> The pattern
> 
>        foo(..., &err);
>        g_assert(err == NULL);
> 
> is pretty common in tests.  Can't see what it buys us over straight
> &error_abort.  Perhaps I'll spatch it away.
> 
>>      g_assert_cmpint(tmp->kind, ==, USER_DEF_ALTERNATE_KIND_I);
>>      g_assert_cmpint(tmp->i, ==, 42);
>>      qapi_free_UserDefAlternate(tmp);
>> +    tmp = NULL;
> 
> Why do you need to clear tmp?

If we were succeeding on a single call by mere luck where tmp started
life as all 0 due to stack contents, but the second call has tmp
pointing to stale memory, then that would be an obvious reason.  I'll
have to revisit what happens, because I don't recall any specific reason
for why I did this other than the symmetry of making sure each parse had
clean state (that is, I don't recall a crash happening if I didn't do
it, and haven't yet tested under valgrind to see if we are provably
using memory incorrectly if we don't initialize).


>> +
>> +    /* FIXME: Integers should parse as numbers */
> 
> Suggest to augment or replace this comment...
> 
>> +    v = visitor_input_test_init(data, "42");
>> +    visit_type_AltTwo(v, &two, NULL, &err);
> 
> ... with
> 
>        /* FIXME g_assert_cmpint(two->kind, ==, ALT_TWO_KIND_N); */
>        /* FIXME g_assert_cmpfloat(two->n, ==, 42); */

Ah, to better document what the test will look like in the future when
the bugs are fixed. Sure, I can do that.

> 
>> +    g_assert(err);
>> +    error_free(err);
>> +    err = NULL;
>> +    qapi_free_AltTwo(two);
>> +    one = NULL;
> 
> *chuckle*  Why do you clear one here?  More of the same below.

Too much copy-and-paste.  Will fix.

> 
>> +
>> +    /* FIXME: Order of alternate should not affect semantics */
> 
> Inhowfar does it affect semantics?  Or asked differently: what exactly
> is wrong with this test now?
> 
>> +    v = visitor_input_test_init(data, "42");
>> +    visit_type_AltThree(v, &three, NULL, &error_abort);
>> +    g_assert_cmpint(three->kind, ==, ALT_THREE_KIND_N);
>> +    g_assert_cmpfloat(three->n, ==, 42);
>> +    qapi_free_AltThree(three);
>> +    one = NULL;


AltTwo and AltThree are ostensibly the same struct (two branches, one
for 'str' and one for 'number', just in a different order), but they
parsed differently (AltTwo failed, AltThree succeeded).  The bug is
fixed later when the order of the branch declaration no longer impacts
the result of the parse.

> 
> Reading this, I had to refer back to the definition of AltOne, ...,
> AltSix all the time.  Let's rename them to AltStrBool, AltStrNum, ...,
> AltNumInt.

Good idea, will do.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 06/46] qapi: Improve 'include' error message
  2015-09-24 14:39   ` Markus Armbruster
@ 2015-09-24 16:04     ` Eric Blake
  0 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-24 16:04 UTC (permalink / raw
  To: Markus Armbruster
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

[-- Attachment #1: Type: text/plain, Size: 1147 bytes --]

On 09/24/2015 08:39 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Use of '"...%s" % include' to print non-strings can lead to
>> ugly messages, such as this (if the .json change is applied
>> without the qapi.py change):
>>  Expected a file name (string), got: OrderedDict()
>>
>> Better is to just omit the actual non-string value in the
>> message.
>>
>> Signed-off-by: Eric Blake <eblake@redhat.com>
>> ---

>> +                    raise QAPIExprError(expr_info,
>> +                                        "Expected a string for 'include'")

>> +++ b/tests/qapi-schema/include-non-file.json
>> @@ -1 +1 @@
>> -{ 'include': [ 'foo', 'bar' ] }
>> +{ 'include': {} }
> 
> What about "Value of 'include' must be a string'?

Sure, the bikeshed looks better in green :)

Actually, I like your wording better, so I'll go with it (not too hard,
since I already am hoisting this patch to occur before the pep8 cleanups
to reduce churn on the message, due to your feedback on 2/46).

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call
  2015-09-24 14:58   ` Markus Armbruster
@ 2015-09-24 16:14     ` Eric Blake
  2015-09-26 21:05       ` Eric Blake
  2015-09-26 21:41     ` [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call Eric Blake
  1 sibling, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-24 16:14 UTC (permalink / raw
  To: Markus Armbruster
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

[-- Attachment #1: Type: text/plain, Size: 5004 bytes --]

On 09/24/2015 08:58 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Due to the existing semantics of the error_set() family,
>> generated sequences in the qapi visitors such as:
>>
>>     visit_start_implicit_struct(m, (void **)obj, sizeof(FOO), &err);
>>         if (!err) {
>>             visit_type_FOO_fields(m, obj, errp);
>>             visit_end_implicit_struct(m, &err);
>>         }
>>     error_propagate(errp, err);
> 
> Indentation seems off.  Intentional?

No, probably due to rebase churn (I reindented generated code in 9/46,
but the series as I worked on it wasn't always in the order presented
here).  Will fix.

> 
>>
>> are risky: if visit_type_FOO_fields() sets errp, and then
>> visit_end_implicit_struct() also encounters an error, the
>> attempt to overwrite the first error will cause an abort().
>> Obviously, we weren't triggering this situation (none of the
>> existing callbacks for visit_end_implicit_struct() currently
>> try to set an error), but it is better to not even cause the
>> problem in the first place.
> 
> The code works, but it sets a problematic example.
> 
>> Meanwhile, in spite of the poor documentation of the qapi
>> visitors, we want to guarantee that if a visit_start_*()
>> succeeds, then the matching visit_end_*() will be called.
> 
> Agreed.
> 
>> The options are to either propagate and clear a local err
>> multiple times:
>>
>>     visit_start_implicit_struct(m, (void **)obj, sizeof(FOO), &err);
>>         if (!err) {
>>             visit_type_FOO_fields(m, obj, &err);
>>             if (err) {
>>                 error_propagate(errp, err);
>>                 err = NULL;
>>             }
>>             visit_end_implicit_struct(m, &err);
>>         }
>>     error_propagate(errp, err);

More poor indentation on my part.

>>
>> or, as this patch does, just pass in NULL to ignore further
>> errors once an error has occurred.
>>
>>     visit_start_implicit_struct(m, (void **)obj, sizeof(FOO), &err);
>>         if (!err) {
>>             visit_type_FOO_fields(m, obj, &err);
>>             visit_end_implicit_struct(m, err ? NULL : &err);
>>         }
>>     error_propagate(errp, err);
> 
> Hmmmmm... not sure we do this anywhere else, yet.  The ternary isn't
> exactly pretty, but the intent to ignore additional error is clear
> enough, I think.
> 
> If we elect to adopt this new error handling pattern, we should perhaps
> document it in error.h.
> 
> Third option: drop visit_end_implicit_struct()'s errp parameter.  If we
> find a compelling use for it, we'll put it back and solve the problem.
> 

Ooh, interesting idea.  It changes the contract - but since the contract
isn't (yet) documented, and happens to work with existing uses without a
contract, it could indeed be nicer.  It would have knock-on effects to
24/46 where I first try documenting the contract.

>>      visit_start_implicit_struct(m, (void **)obj, sizeof(%(c_type)s), &err);
>>      if (!err) {
>> -        visit_type_%(c_type)s_fields(m, obj, errp);
>> -        visit_end_implicit_struct(m, &err);
>> +        visit_type_%(c_type)s_fields(m, obj, &err);
>> +        visit_end_implicit_struct(m, err ? NULL : &err);

>>      visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err);
>>      if (!err) {
>>          if (*obj) {
>> -            visit_type_%(c_name)s_fields(m, obj, errp);
>> +            visit_type_%(c_name)s_fields(m, obj, &err);
>>          }
>> -        visit_end_struct(m, &err);
>> +        visit_end_struct(m, err ? NULL : &err);
>>      }
>>      error_propagate(errp, err);
>>  }
> 
> Oh, it's about visit_end_struct(), too.  Commit message only talks about
> visit_end_implicit_struct().
> 
> In particular, "none of the existing callbacks for
> visit_end_implicit_struct() currently try to set an error".  Does that
> hold for visit_end_struct() callbacks, too?

I'm fairly certain that ALL of the visit_end_* callbacks were similar in
nature, but you've prompted me to re-audit things and update the commit
message to be absolutely clear about it.

> 
>> @@ -175,9 +175,7 @@ void visit_type_%(c_name)s(Visitor *m, %(c_name)s **obj, const char *name, Error
>>          visit_type_%(c_elt_type)s(m, &native_i->value, NULL, &err);
>>      }
>>
>> -    error_propagate(errp, err);
>> -    err = NULL;
>> -    visit_end_list(m, &err);
>> +    visit_end_list(m, err ? NULL : &err);
>>  out:
>>      error_propagate(errp, err);
>>  }
> 
> Likewise.  Does it hold for visit_end_list() callbacks, too?
> 
> Looks like you switch from option 1 to option 2 here.  Your slate isn't
> as clean as the commit message suggests :)

Consistency is nice, but documenting where we started from to get to the
consistent state would be even nicer. Point taken.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 05/46] qapi: Test use of 'number' within alternates
  2015-09-24 16:00     ` Eric Blake
@ 2015-09-24 16:29       ` Markus Armbruster
  2015-09-25 22:32         ` Eric Blake
  2015-09-25 22:50         ` Eric Blake
  0 siblings, 2 replies; 108+ messages in thread
From: Markus Armbruster @ 2015-09-24 16:29 UTC (permalink / raw
  To: Eric Blake
  Cc: Michael Roth, marcandre.lureau, qemu-devel, ehabkost,
	DirtY.iCE.hu

Eric Blake <eblake@redhat.com> writes:

> On 09/24/2015 08:36 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> Add some testsuite exposure for use of a 'number' as part of
>>> an alternate.  The current state of the tree has a few bugs
>>> exposed by this: our input parser depends on the ordering of
>>> how the qapi schema declared the alternate, and the parser
>>> does not accept integers for a 'number' in an alternate even
>>> though it does for numbers outside of an alternate.
>>>
>>> Mixing 'int' and 'number' in the same alternate is unusual,
>>> since both are supplied by json-numbers, but there does not
>>> seem to be a technical reason to forbid it given that our
>>> json lexer distinguishes between json-numbers that can be
>>> represented as an int vs. those that cannot.
>>>
>>> Signed-off-by: Eric Blake <eblake@redhat.com>
>>> ---
>>>  tests/qapi-schema/qapi-schema-test.json |   8 ++
>>>  tests/qapi-schema/qapi-schema-test.out  |  24 ++++++
>>>  tests/test-qmp-input-visitor.c          | 129 +++++++++++++++++++++++++++++++-
>>>  3 files changed, 158 insertions(+), 3 deletions(-)
>>>
>
>>> +++ b/tests/test-qmp-input-visitor.c
>>> @@ -368,15 +368,136 @@ static void test_visitor_in_alternate(TestInputVisitorData *data,
>>>  {
>>>      Visitor *v;
>>>      Error *err = NULL;
>>> -    UserDefAlternate *tmp;
>>> +    UserDefAlternate *tmp = NULL;
>> 
>> Any particular reason for adding the initializer?
>> 
>>>
>>>      v = visitor_input_test_init(data, "42");
>>>
>>> -    visit_type_UserDefAlternate(v, &tmp, NULL, &err);
>>> -    g_assert(err == NULL);
>>> +    visit_type_UserDefAlternate(v, &tmp, NULL, &error_abort);
>
> Hmm - I don't know if we have a clear contract for what happens if you
> call visit_type_FOO on an uninitialized pointer.  It may have been
> succeeding by mere luck.

I strongly suspect the "input" visitors are assignment-like: they store
something, and don't care what value they overwrite.  Let's keep that in
mind when we retrofit a contract.

>> 
>> The pattern
>> 
>>        foo(..., &err);
>>        g_assert(err == NULL);
>> 
>> is pretty common in tests.  Can't see what it buys us over straight
>> &error_abort.  Perhaps I'll spatch it away.
>> 
>>>      g_assert_cmpint(tmp->kind, ==, USER_DEF_ALTERNATE_KIND_I);
>>>      g_assert_cmpint(tmp->i, ==, 42);
>>>      qapi_free_UserDefAlternate(tmp);
>>> +    tmp = NULL;
>> 
>> Why do you need to clear tmp?
>
> If we were succeeding on a single call by mere luck where tmp started
> life as all 0 due to stack contents, but the second call has tmp
> pointing to stale memory, then that would be an obvious reason.  I'll
> have to revisit what happens, because I don't recall any specific reason
> for why I did this other than the symmetry of making sure each parse had
> clean state (that is, I don't recall a crash happening if I didn't do
> it, and haven't yet tested under valgrind to see if we are provably
> using memory incorrectly if we don't initialize).

I suspect it's just as dead as clearing an assignment's LHS before the
actual assignment is :)

>>> +
>>> +    /* FIXME: Integers should parse as numbers */
>> 
>> Suggest to augment or replace this comment...
>> 
>>> +    v = visitor_input_test_init(data, "42");
>>> +    visit_type_AltTwo(v, &two, NULL, &err);
>> 
>> ... with
>> 
>>        /* FIXME g_assert_cmpint(two->kind, ==, ALT_TWO_KIND_N); */
>>        /* FIXME g_assert_cmpfloat(two->n, ==, 42); */
>
> Ah, to better document what the test will look like in the future when
> the bugs are fixed. Sure, I can do that.
>
>> 
>>> +    g_assert(err);
>>> +    error_free(err);
>>> +    err = NULL;
>>> +    qapi_free_AltTwo(two);
>>> +    one = NULL;
>> 
>> *chuckle*  Why do you clear one here?  More of the same below.
>
> Too much copy-and-paste.  Will fix.

Supports my idea that these assignments are useless.

If they are, let's just drop them.

>>> +
>>> +    /* FIXME: Order of alternate should not affect semantics */
>> 
>> Inhowfar does it affect semantics?  Or asked differently: what exactly
>> is wrong with this test now?
>> 
>>> +    v = visitor_input_test_init(data, "42");
>>> +    visit_type_AltThree(v, &three, NULL, &error_abort);
>>> +    g_assert_cmpint(three->kind, ==, ALT_THREE_KIND_N);
>>> +    g_assert_cmpfloat(three->n, ==, 42);
>>> +    qapi_free_AltThree(three);
>>> +    one = NULL;
>
>
> AltTwo and AltThree are ostensibly the same struct (two branches, one
> for 'str' and one for 'number', just in a different order), but they
> parsed differently (AltTwo failed, AltThree succeeded).  The bug is
> fixed later when the order of the branch declaration no longer impacts
> the result of the parse.

Then nothing is wrong with this test case, and the FIXME doesn't belong
here.

>> Reading this, I had to refer back to the definition of AltOne, ...,
>> AltSix all the time.  Let's rename them to AltStrBool, AltStrNum, ...,
>> AltNumInt.
>
> Good idea, will do.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 08/46] qapi: Reuse code for flat union base validation
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 08/46] qapi: Reuse code for flat union base validation Eric Blake
@ 2015-09-25 16:30   ` Markus Armbruster
  0 siblings, 0 replies; 108+ messages in thread
From: Markus Armbruster @ 2015-09-25 16:30 UTC (permalink / raw
  To: Eric Blake
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Rather than open-code the check for a valid base type, we
> should reuse the common functionality. This allows for
> consistent error messages, and also makes it easier for a
> later patch to turn on support for inline anonymous base
> structures.
>
> Test flat-union-inline is updated to test only one feature
> (anonymous branch dictionaries), which can be implemented
> independently (test flat-union-bad-base already covers the
> idea of an anonymous base dictionary).
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>  scripts/qapi.py                             | 11 +++++------
>  tests/qapi-schema/flat-union-bad-base.err   |  2 +-
>  tests/qapi-schema/flat-union-base-any.err   |  2 +-
>  tests/qapi-schema/flat-union-base-union.err |  2 +-
>  tests/qapi-schema/flat-union-inline.err     |  2 +-
>  tests/qapi-schema/flat-union-inline.json    |  4 ++--
>  tests/qapi-schema/flat-union-no-base.err    |  2 +-
>  tests/qapi-schema/union-invalid-base.err    |  2 +-
>  8 files changed, 13 insertions(+), 14 deletions(-)
>
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 007349e..6f4e471 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -560,15 +560,14 @@ def check_union(expr, expr_info):
>      # Else, it's a flat union.
>      else:
>          # The object must have a string member 'base'.
> -        if not isinstance(base, str):
> +        check_type(expr_info, "'base' for union '%s'" % name,
> +                   base, allow_metas=['struct'])
> +        if not base:
>              raise QAPIExprError(expr_info,
> -                                "Flat union '%s' must have a string base field"
> +                                "Flat union '%s' must have a valid base"

Well, it must have a base, period.  Suggest to drop "valid".

>                                  % name)
>          base_fields = find_base_fields(base)
> -        if not base_fields:
> -            raise QAPIExprError(expr_info,
> -                                "Base '%s' is not a valid struct"
> -                                % base)
> +        assert base_fields
>
>          # The value of member 'discriminator' must name a non-optional
>          # member of the base struct.
> diff --git a/tests/qapi-schema/flat-union-bad-base.err b/tests/qapi-schema/flat-union-bad-base.err
> index f9c31b2..79b8a71 100644
> --- a/tests/qapi-schema/flat-union-bad-base.err
> +++ b/tests/qapi-schema/flat-union-bad-base.err
> @@ -1 +1 @@
> -tests/qapi-schema/flat-union-bad-base.json:9: Flat union 'TestUnion' must have a string base field
> +tests/qapi-schema/flat-union-bad-base.json:9: 'base' for union 'TestUnion' should be a type name

Improvement.

> diff --git a/tests/qapi-schema/flat-union-base-any.err b/tests/qapi-schema/flat-union-base-any.err
> index ad4d629..646f1c9 100644
> --- a/tests/qapi-schema/flat-union-base-any.err
> +++ b/tests/qapi-schema/flat-union-base-any.err
> @@ -1 +1 @@
> -tests/qapi-schema/flat-union-base-any.json:8: Base 'any' is not a valid struct
> +tests/qapi-schema/flat-union-base-any.json:8: 'base' for union 'TestUnion' cannot use built-in type 'any'

Improvement.

> diff --git a/tests/qapi-schema/flat-union-base-union.err b/tests/qapi-schema/flat-union-base-union.err
> index ede9859..d50e687 100644
> --- a/tests/qapi-schema/flat-union-base-union.err
> +++ b/tests/qapi-schema/flat-union-base-union.err
> @@ -1 +1 @@
> -tests/qapi-schema/flat-union-base-union.json:11: Base 'UnionBase' is not a valid struct
> +tests/qapi-schema/flat-union-base-union.json:11: 'base' for union 'TestUnion' cannot use union type 'UnionBase'

Improvement.

> diff --git a/tests/qapi-schema/flat-union-inline.err b/tests/qapi-schema/flat-union-inline.err
> index ec58627..2333358 100644
> --- a/tests/qapi-schema/flat-union-inline.err
> +++ b/tests/qapi-schema/flat-union-inline.err
> @@ -1 +1 @@
> -tests/qapi-schema/flat-union-inline.json:7: Flat union 'TestUnion' must have a string base field
> +tests/qapi-schema/flat-union-inline.json:7: Member 'value1' of union 'TestUnion' should be a type name
> diff --git a/tests/qapi-schema/flat-union-inline.json b/tests/qapi-schema/flat-union-inline.json
> index 6bfdd65..62c7cda 100644
> --- a/tests/qapi-schema/flat-union-inline.json
> +++ b/tests/qapi-schema/flat-union-inline.json
> @@ -1,11 +1,11 @@
>  # we require branches to be a struct name
> -# TODO: should we allow anonymous inline types?
> +# TODO: should we allow anonymous inline branch types?
>  { 'enum': 'TestEnum',
>    'data': [ 'value1', 'value2' ] }
>  { 'struct': 'Base',
>    'data': { 'enum1': 'TestEnum', 'kind': 'str' } }
>  { 'union': 'TestUnion',
> -  'base': { 'enum1': 'TestEnum', 'kind': 'str' },
> +  'base': 'Base',
>    'discriminator': 'enum1',
>    'data': { 'value1': { 'string': 'str' },
>              'value2': { 'integer': 'int' } } }

Makes sense.  Thanks for pointing to flat-union-bad-base.json in your
commit message.

> diff --git a/tests/qapi-schema/flat-union-no-base.err b/tests/qapi-schema/flat-union-no-base.err
> index bb3f708..253e251 100644
> --- a/tests/qapi-schema/flat-union-no-base.err
> +++ b/tests/qapi-schema/flat-union-no-base.err
> @@ -1 +1 @@
> -tests/qapi-schema/flat-union-no-base.json:9: Flat union 'TestUnion' must have a string base field
> +tests/qapi-schema/flat-union-no-base.json:9: Flat union 'TestUnion' must have a valid base
> diff --git a/tests/qapi-schema/union-invalid-base.err b/tests/qapi-schema/union-invalid-base.err
> index 9f63796..03d7b97 100644
> --- a/tests/qapi-schema/union-invalid-base.err
> +++ b/tests/qapi-schema/union-invalid-base.err
> @@ -1 +1 @@
> -tests/qapi-schema/union-invalid-base.json:8: Base 'int' is not a valid struct
> +tests/qapi-schema/union-invalid-base.json:8: 'base' for union 'TestUnion' cannot use built-in type 'int'

Another improvement.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 45/46] net: Complete qapi-fication of netdev_add
  2015-09-23 16:37     ` Eric Blake
@ 2015-09-25 16:48       ` Paolo Bonzini
  2015-09-28  9:31         ` Markus Armbruster
  0 siblings, 1 reply; 108+ messages in thread
From: Paolo Bonzini @ 2015-09-25 16:48 UTC (permalink / raw
  To: Eric Blake, qemu-devel
  Cc: marcandre.lureau, Jason Wang, DirtY.iCE.hu, armbru, ehabkost

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256



On 23/09/2015 18:37, Eric Blake wrote:
>>> 
>>> Is this still type-unsafe like the old netdev_add (e.g. accepts
>>> a string for an integer)?
> I did not address that yet; it still needs further patches to
> accept an integer as a port number. I can investigate what
> additional patches are needed along those lines, while still
> preserving back-compat to previously-accepted QMP command lines (it
> may require the use of an 'alternate' type that accepts both int
> and string).  The main goal here was that the command line is
> unchanged, and that now the QMP command is introspectable, even if
> what introspection shows is ugly types.

This in fact is a laudable goal.  However, while changing the command to

{ 'command': 'netdev_add', 'data': 'Netdev', 'box': true, 'gen': false }

for better introspection, you should keep 'gen':'false' and the manual
implementation based on qemu_opts_from_qdict, otherwise you break
backwards-compatibility.

Paolo
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2

iQEcBAEBCAAGBQJWBXr0AAoJEL/70l94x66DJNUH/24lvns9MJpLv0ycT0+jrwVd
BhlCBatRwISITCX+gRSL5kmC0UNeGUWUB6AaYeCxZtY+Z40lCcOakMf2ZvDOf+Iq
0CvrtXSjEzwf3DxUawso4bcBdQKg1Za1sQsvExGd84VbnsRr+HFKNclYhJGxbiG4
UNPvIFzd+mwod+i2RHooRY/CiDqYDSinlLlp4N6DqjjMQeiTcetJqfdsm6Kke9fg
ntu/YEwlqcgGFSSDOdGG1YmSmWApUOw0gAzHt+Ufyy+I/M6Mzvn9OH6E3lQtghOy
ovK230/8YIoIPBkWsRxYDp1LrmxarszMTqAWMuc3ALRxJSQ6GMrtgDby1ghw1W8=
=akeC
-----END PGP SIGNATURE-----

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 09/46] qapi: Use consistent generated code patterns
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 09/46] qapi: Use consistent generated code patterns Eric Blake
@ 2015-09-25 16:54   ` Markus Armbruster
  2015-09-25 19:06     ` Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-25 16:54 UTC (permalink / raw
  To: Eric Blake
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

Eric Blake <eblake@redhat.com> writes:

> We had some pointless differences in the generated code for visit,
> command marshalling, and events; unifying them makes it easier for
> future patches to consolidate to common helper functions.
> - Consistently name the error variable 'err'

Before the patch, qapi-event.py consistently uses local_err, and
qapi-commands.py uses both names.  I like your change.

> - Consistently use the labels 'out' and 'out_obj'

Before, qapi-event.py consistently uses 'clean', and qapi-visit.py uses
both 'out_obj' and 'out_end'.  Good change.

> - If allocation fails, jump to the right label rather than
> indenting everything else

Good idea.  Just one instance, in gen_visit_union().

Not mentioned: name the Visitor * parameter 'v' instead of 'm'.  No idea
where that 'm' comes from, but it has spilled over into qom/object.c and
qom/qom-object.c.  Let's ignore that for now.

> No change in semantics to the generated code.

Yes, but you need to update docs/qapi-code-gen.txt.

Pretty mechanical changes.  They look good to me, but as always, this
kind is easier to review when you do exactly one mechanical change per
patch.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 09/46] qapi: Use consistent generated code patterns
  2015-09-25 16:54   ` Markus Armbruster
@ 2015-09-25 19:06     ` Eric Blake
  0 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-25 19:06 UTC (permalink / raw
  To: Markus Armbruster
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

[-- Attachment #1: Type: text/plain, Size: 1801 bytes --]

On 09/25/2015 10:54 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> We had some pointless differences in the generated code for visit,
>> command marshalling, and events; unifying them makes it easier for
>> future patches to consolidate to common helper functions.
>> - Consistently name the error variable 'err'
> 
> Before the patch, qapi-event.py consistently uses local_err, and
> qapi-commands.py uses both names.  I like your change.
> 
>> - Consistently use the labels 'out' and 'out_obj'
> 
> Before, qapi-event.py consistently uses 'clean', and qapi-visit.py uses
> both 'out_obj' and 'out_end'.  Good change.
> 
>> - If allocation fails, jump to the right label rather than
>> indenting everything else
> 
> Good idea.  Just one instance, in gen_visit_union().
> 
> Not mentioned: name the Visitor * parameter 'v' instead of 'm'.

Whoops - I merged several patches from v4 into just this one for v5, but
forgot to update the commit message to match.

>  No idea
> where that 'm' comes from, but it has spilled over into qom/object.c and
> qom/qom-object.c.  Let's ignore that for now.

I didn't even notice that.

> 
>> No change in semantics to the generated code.
> 
> Yes, but you need to update docs/qapi-code-gen.txt.

Indeed.

> 
> Pretty mechanical changes.  They look good to me, but as always, this
> kind is easier to review when you do exactly one mechanical change per
> patch.

Sounds like my merge was premature; I'll split this back out into
separate patches, and update the docs to match (where things are
affected - our docs don't give any examples of the C code for visiting a
union).

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 05/46] qapi: Test use of 'number' within alternates
  2015-09-24 16:29       ` Markus Armbruster
@ 2015-09-25 22:32         ` Eric Blake
  2015-09-28  9:26           ` Markus Armbruster
  2015-09-25 22:50         ` Eric Blake
  1 sibling, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-25 22:32 UTC (permalink / raw
  To: Markus Armbruster
  Cc: Michael Roth, marcandre.lureau, qemu-devel, ehabkost,
	DirtY.iCE.hu

[-- Attachment #1: Type: text/plain, Size: 1515 bytes --]

On 09/24/2015 10:29 AM, Markus Armbruster wrote:

>>>> +
>>>> +    /* FIXME: Order of alternate should not affect semantics */
>>>
>>> Inhowfar does it affect semantics?  Or asked differently: what exactly
>>> is wrong with this test now?
>>>
>>>> +    v = visitor_input_test_init(data, "42");
>>>> +    visit_type_AltThree(v, &three, NULL, &error_abort);
>>>> +    g_assert_cmpint(three->kind, ==, ALT_THREE_KIND_N);
>>>> +    g_assert_cmpfloat(three->n, ==, 42);
>>>> +    qapi_free_AltThree(three);
>>>> +    one = NULL;
>>
>>
>> AltTwo and AltThree are ostensibly the same struct (two branches, one
>> for 'str' and one for 'number', just in a different order), but they
>> parsed differently (AltTwo failed, AltThree succeeded).  The bug is
>> fixed later when the order of the branch declaration no longer impacts
>> the result of the parse.
> 
> Then nothing is wrong with this test case, and the FIXME doesn't belong
> here.

Actually, the test for AltThree succeeds only by accident. There are two
bugs at play; when I fix the first bug (order shouldn't matter: AltTwo
and AltThree should parse identically), then the second bug is finally
exposed (integers aren't being parsed as numbers, in either AltTwo or
AltThree). But I can certainly rework the FIXMEs both here and on the
first fix (19/46) to make it more obvious what the second fix (20/46) is
good for.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 05/46] qapi: Test use of 'number' within alternates
  2015-09-24 16:29       ` Markus Armbruster
  2015-09-25 22:32         ` Eric Blake
@ 2015-09-25 22:50         ` Eric Blake
  1 sibling, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-25 22:50 UTC (permalink / raw
  To: Markus Armbruster
  Cc: Michael Roth, marcandre.lureau, qemu-devel, ehabkost,
	DirtY.iCE.hu

[-- Attachment #1: Type: text/plain, Size: 2178 bytes --]

On 09/24/2015 10:29 AM, Markus Armbruster wrote:

>>> Any particular reason for adding the initializer?
>>>
>>>>
>>>>      v = visitor_input_test_init(data, "42");
>>>>
>>>> -    visit_type_UserDefAlternate(v, &tmp, NULL, &err);
>>>> -    g_assert(err == NULL);
>>>> +    visit_type_UserDefAlternate(v, &tmp, NULL, &error_abort);
>>
>> Hmm - I don't know if we have a clear contract for what happens if you
>> call visit_type_FOO on an uninitialized pointer.  It may have been
>> succeeding by mere luck.
> 
> I strongly suspect the "input" visitors are assignment-like: they store
> something, and don't care what value they overwrite.  Let's keep that in
> mind when we retrofit a contract.
> 

Hmm. When I delete the initializer, valgrind starts warning at a later
point in my series:

/visitor/input/errors: OK
/visitor/input/alternate-number: ==16451== Conditional jump or move
depends on uninitialised value(s)
==16451==    at 0x14AA7E: visit_start_implicit_struct (qapi-visit-core.c:36)
==16451==    by 0x111D80: visit_type_AltStrBool (test-qapi-visit.c:207)
==16451==    by 0x10EC16: test_visitor_in_alternate_number
(test-qmp-input-visitor.c:426)
==16451==    by 0x4EBBB92: test_case_run (gtestutils.c:2124)
==16451==    by 0x4EBBB92: g_test_run_suite_internal (gtestutils.c:2185)
==16451==    by 0x4EBBD5A: g_test_run_suite_internal (gtestutils.c:2196)
==16451==    by 0x4EBBD5A: g_test_run_suite_internal (gtestutils.c:2196)
==16451==    by 0x4EBC0DA: g_test_run_suite (gtestutils.c:2249)
==16451==    by 0x4EBC110: g_test_run (gtestutils.c:1553)
==16451==    by 0x111406: main (test-qmp-input-visitor.c:901)
==16451==  Uninitialised value was created by a stack allocation
==16451==    at 0x10EBA1: test_visitor_in_alternate_number
(test-qmp-input-visitor.c:413)

I don't know if that means my changes are introducing the problems, or
if we really DO want to require users to pass in an initial NULL
pointer. I've run out of time to investigate today, but it's not turning
out to be as trivial as I had hoped.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call
  2015-09-24 16:14     ` Eric Blake
@ 2015-09-26 21:05       ` Eric Blake
  2015-09-28  9:14         ` Markus Armbruster
  0 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-26 21:05 UTC (permalink / raw
  To: Markus Armbruster
  Cc: Michael Roth, marcandre.lureau, qemu-devel, ehabkost,
	DirtY.iCE.hu

[-- Attachment #1: Type: text/plain, Size: 2535 bytes --]

On 09/24/2015 10:14 AM, Eric Blake wrote:

>>>
>>>     visit_start_implicit_struct(m, (void **)obj, sizeof(FOO), &err);
>>>         if (!err) {
>>>             visit_type_FOO_fields(m, obj, &err);
>>>             visit_end_implicit_struct(m, err ? NULL : &err);
>>>         }
>>>     error_propagate(errp, err);
>>
>> Hmmmmm... not sure we do this anywhere else, yet.  The ternary isn't
>> exactly pretty, but the intent to ignore additional error is clear
>> enough, I think.
>>
>> If we elect to adopt this new error handling pattern, we should perhaps
>> document it in error.h.
>>
>> Third option: drop visit_end_implicit_struct()'s errp parameter.  If we
>> find a compelling use for it, we'll put it back and solve the problem.
>>
> 
> Ooh, interesting idea.  It changes the contract - but since the contract
> isn't (yet) documented, and happens to work with existing uses without a
> contract, it could indeed be nicer.  It would have knock-on effects to
> 24/46 where I first try documenting the contract.

Oh well.  We do have a compelling use: qmp-input-visitor.c can set errp
during visit_end_struct() when in strict mode (basically, the mode which
warns if the input QDict has leftover key:value pairs that were not
consumed by visit_type_FOO() between the start/end call).  I don't know
if visit_end_list() or visit_end_implicit_struct() care; but then we
have the argument that it is worth keeping them consistent with
visit_end_struct() which can indeed raise an error.

One other potential alternative:  What if we split visit_end_struct()
into two visitor functions, one that checks for success, and the other
that is called unconditionally to clean up resources.  That is, go from:

    visit_start_struct(m, (void **)obj, "foo", name, sizeof(FOO), &err);
    if (!err) {
        if (*obj) {
            visit_type_FOO_fields(m, obj, errp);
        }
        visit_end_struct(m, &err);
    }
    error_propagate(errp, err);

to a form where the check for leftover key/value pairs is only done on
success with a new visit_check_struct():

    visit_start_struct(m, (void **)obj, "foo", name, sizeof(FOO), &err);
    if (!err) {
        if (*obj) {
            visit_type_FOO_fields(m, obj, &err);
        }
        if (!err) {
            visit_check_struct(m, &err);
        }
        visit_end_struct(m);
    }
    error_propagate(errp, err);

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call
  2015-09-24 14:58   ` Markus Armbruster
  2015-09-24 16:14     ` Eric Blake
@ 2015-09-26 21:41     ` Eric Blake
  2015-09-27  2:26       ` Eric Blake
  2015-09-28  9:24       ` Markus Armbruster
  1 sibling, 2 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-26 21:41 UTC (permalink / raw
  To: Markus Armbruster
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

[-- Attachment #1: Type: text/plain, Size: 2051 bytes --]

On 09/24/2015 08:58 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Due to the existing semantics of the error_set() family,
>> generated sequences in the qapi visitors such as:
>>
>>     visit_start_implicit_struct(m, (void **)obj, sizeof(FOO), &err);
>>         if (!err) {
>>             visit_type_FOO_fields(m, obj, errp);
>>             visit_end_implicit_struct(m, &err);
>>         }
>>     error_propagate(errp, err);
> 
> Indentation seems off.  Intentional?
> 
>>
>> are risky: if visit_type_FOO_fields() sets errp, and then
>> visit_end_implicit_struct() also encounters an error, the
>> attempt to overwrite the first error will cause an abort().

I didn't even read error_propagate()'s contract correctly. It
specifically specifies that if errp is already set, then err is ignored.

So the above sequence is actually just fine, because only the following
paths exist:

visit_start_implicit_struct() fails into &err,
error_propagate() passes err into caller's errp

visit_start_implicit_struct() succeeds,
visit_type_FOO_fields() fails into caller's errp,
visit_end_implicit_struct() succeeds,
error_propagate() does nothing

visit_start_implicit_struct() succeeds,
visit_type_FOO_fields() fails into caller's errp,
visit_end_implicit_struct() fails int &err,
error_propagate() does nothing (errp trumps err)

visit_start_implicit_struct() succeeds,
visit_type_FOO_fields() succeeds,
visit_end_implicit_struct() fails int &err,
error_propagate() passes err into caller's errp

visit_start_implicit_struct() succeeds,
visit_type_FOO_fields() succeeds,
visit_end_implicit_struct() succeeds,
error_propagate() does nothing


As such, I'm revisiting if anything is needed at all, other than making
the various visit_start/visit_end patterns consistent with one another
using existing idioms, and it may turn out we don't need the ternary
after all.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call
  2015-09-26 21:41     ` [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call Eric Blake
@ 2015-09-27  2:26       ` Eric Blake
  2015-09-28  9:24       ` Markus Armbruster
  1 sibling, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-27  2:26 UTC (permalink / raw
  To: Markus Armbruster
  Cc: Michael Roth, marcandre.lureau, qemu-devel, ehabkost,
	DirtY.iCE.hu

[-- Attachment #1: Type: text/plain, Size: 1919 bytes --]

On 09/26/2015 03:41 PM, Eric Blake wrote:
> On 09/24/2015 08:58 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>>
>>> Due to the existing semantics of the error_set() family,
>>> generated sequences in the qapi visitors such as:
>>>
>>>     visit_start_implicit_struct(m, (void **)obj, sizeof(FOO), &err);
>>>         if (!err) {
>>>             visit_type_FOO_fields(m, obj, errp);
>>>             visit_end_implicit_struct(m, &err);
>>>         }
>>>     error_propagate(errp, err);
>>
>> Indentation seems off.  Intentional?
>>
>>>
>>> are risky: if visit_type_FOO_fields() sets errp, and then
>>> visit_end_implicit_struct() also encounters an error, the
>>> attempt to overwrite the first error will cause an abort().
> 
> I didn't even read error_propagate()'s contract correctly. It
> specifically specifies that if errp is already set, then err is ignored.
> 
> So the above sequence is actually just fine, because only the following
> paths exist:
> 

> 
> As such, I'm revisiting if anything is needed at all, other than making
> the various visit_start/visit_end patterns consistent with one another
> using existing idioms, and it may turn out we don't need the ternary
> after all.

Turns out patch 29/46 needs the ternary.  There, I'm changing the logic
of the various visit_type_FOO() to explicitly set *obj = NULL if
something fails in between visit_start_* and visit_end_* - but to do
that, I _have_ to track everything locally in &err (since errp might be
NULL in the caller).  That is, in the above example, if
visit_type_FOO_fields() fails, we need to track that locally in order to
clean up *obj.  Meanwhile, the call to visit_end_implicit_struct() must
be unconditional, whether or not we have detected earlier failure.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 10/46] qapi: Merge generation of per-member visits
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 10/46] qapi: Merge generation of per-member visits Eric Blake
@ 2015-09-28  6:17   ` Markus Armbruster
  2015-09-28 15:40     ` Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-28  6:17 UTC (permalink / raw
  To: Eric Blake
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Consolidate the code between visit, command marshalling, and
> event generation that iterates over the members of a struct.
> It reduces code duplication in the generator, with no change to
> generated marshal code, slightly more verbose visit code:
>
> |     visit_optional(v, &(*obj)->has_device, "device", &err);
> |-    if (!err && (*obj)->has_device) {
> |-        visit_type_str(v, &(*obj)->device, "device", &err);
> |-    }
> |     if (err) {
> |         goto out;
> |     }
> |+    if ((*obj)->has_device) {
> |+        visit_type_str(v, &(*obj)->device, "device", &err);
> |+        if (err) {
> |+            goto out;
> |+        }
> |+    }

I think the more verbose code is easier to understand, because it checks
for errors exactly the same way as we do all the time, mimimizing
cognitive load.

> and slightly more verbose event code (recall that the qmp
> output visitor has a no-op visit_optional()):
>
> |+    visit_optional(v, &has_offset, "offset", &err);
> |+    if (err) {
> |+        goto out;
> |+    }

If we had a written contract, I suspect not calling visit_optional()
would be a bug.

> |     if (has_offset) {
> |         visit_type_int(v, &offset, "offset", &err);
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>  scripts/qapi-commands.py | 38 +---------------------------------
>  scripts/qapi-event.py    | 35 +++-----------------------------
>  scripts/qapi-visit.py    | 26 +-----------------------
>  scripts/qapi.py          | 53 ++++++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 58 insertions(+), 94 deletions(-)
>
> diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
> index 2151120..55287b1 100644
> --- a/scripts/qapi-commands.py
> +++ b/scripts/qapi-commands.py
> @@ -25,17 +25,6 @@ def gen_command_decl(name, arg_type, ret_type):
>                   params=gen_params(arg_type, 'Error **errp'))
>
>
> -def gen_err_check(err):
> -    if not err:
> -        return ''
> -    return mcgen('''
> -if (%(err)s) {
> -    goto out;
> -}
> -''',
> -                 err=err)
> -
> -

Only code motion.

>  def gen_call(name, arg_type, ret_type):
>      ret = ''
>
> @@ -119,7 +108,6 @@ def gen_marshal_input_visit(arg_type, dealloc=False):
>      push_indent()
>
>      if dealloc:
> -        errparg = 'NULL'
>          errarg = None
>          ret += mcgen('''
>  qmp_input_visitor_cleanup(mi);
> @@ -127,36 +115,12 @@ md = qapi_dealloc_visitor_new();
>  v = qapi_dealloc_get_visitor(md);
>  ''')
>      else:
> -        errparg = '&err'
>          errarg = 'err'
>          ret += mcgen('''
>  v = qmp_input_get_visitor(mi);
>  ''')
>
> -    for memb in arg_type.members:
> -        if memb.optional:
> -            ret += mcgen('''
> -visit_optional(v, &has_%(c_name)s, "%(name)s", %(errp)s);
> -''',
> -                         c_name=c_name(memb.name), name=memb.name,
> -                         errp=errparg)
> -            ret += gen_err_check(errarg)
> -            ret += mcgen('''
> -if (has_%(c_name)s) {
> -''',
> -                         c_name=c_name(memb.name))
> -            push_indent()
> -        ret += mcgen('''
> -visit_type_%(c_type)s(v, &%(c_name)s, "%(name)s", %(errp)s);
> -''',
> -                     c_name=c_name(memb.name), name=memb.name,
> -                     c_type=memb.type.c_name(), errp=errparg)
> -        ret += gen_err_check(errarg)
> -        if memb.optional:
> -            pop_indent()
> -            ret += mcgen('''
> -}
> -''')
> +    ret += gen_visit_fields(arg_type.members, '', False, errarg)

Perhaps a bit neater: make parameters prefix='', need_cast=False, and
say prefix=... and need_cast=True in the one call where you need it.

>
>      if dealloc:
>          ret += mcgen('''
> diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py
> index b43bbc2..6c70a06 100644
> --- a/scripts/qapi-event.py
> +++ b/scripts/qapi-event.py
> @@ -74,38 +74,9 @@ def gen_event_send(name, arg_type):
>
>  ''',
>                       name=name)
> -
> -        for memb in arg_type.members:
> -            if memb.optional:

Here's the missing visit_optional().

> -                ret += mcgen('''
> -    if (has_%(c_name)s) {
> -''',
> -                             c_name=c_name(memb.name))
> -                push_indent()
> -
> -            # Ugly: need to cast away the const
> -            if memb.type.name == "str":
> -                cast = '(char **)'
> -            else:
> -                cast = ''
> -
> -            ret += mcgen('''
> -    visit_type_%(c_type)s(v, %(cast)s&%(c_name)s, "%(name)s", &err);
> -    if (err) {
> -        goto out;
> -    }
> -''',
> -                         cast=cast,
> -                         c_name=c_name(memb.name),
> -                         c_type=memb.type.c_name(),
> -                         name=memb.name)
> -
> -            if memb.optional:
> -                pop_indent()
> -                ret += mcgen('''
> -    }
> -''')
> -
> +        push_indent()
> +        ret += gen_visit_fields(arg_type.members, '', True, 'err')
> +        pop_indent()
>          ret += mcgen('''
>
>      visit_end_struct(v, &err);
> diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
> index 9c0328d..1f287ba 100644
> --- a/scripts/qapi-visit.py
> +++ b/scripts/qapi-visit.py
> @@ -88,31 +88,7 @@ if (err) {
>  ''',
>                       c_type=base.c_name(), c_name=c_name('base'))
>
> -    for memb in members:
> -        if memb.optional:
> -            ret += mcgen('''
> -visit_optional(v, &(*obj)->has_%(c_name)s, "%(name)s", &err);
> -if (!err && (*obj)->has_%(c_name)s) {
> -''',
> -                         c_name=c_name(memb.name), name=memb.name)

Here's the unconventional error checking.

> -            push_indent()
> -
> -        ret += mcgen('''
> -visit_type_%(c_type)s(v, &(*obj)->%(c_name)s, "%(name)s", &err);
> -''',
> -                     c_type=memb.type.c_name(), c_name=c_name(memb.name),
> -                     name=memb.name)
> -
> -        if memb.optional:
> -            pop_indent()
> -            ret += mcgen('''
> -}
> -''')
> -        ret += mcgen('''
> -if (err) {
> -    goto out;
> -}
> -''')
> +    ret += gen_visit_fields(members, '(*obj)->', False, 'err')
>
>      pop_indent()
>      if re.search('^ *goto out;', ret, re.MULTILINE):
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 6f4e471..7275daa 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -1531,6 +1531,59 @@ def gen_params(arg_type, extra):
>          ret += sep + extra
>      return ret
>
> +
> +def gen_err_check(err):
> +    if not err:
> +        return ''
> +    return mcgen('''
> +if (%(err)s) {
> +    goto out;
> +}
> +''',
> +                 err=err)
> +
> +
> +def gen_visit_fields(members, prefix, need_cast, errarg):
> +    ret = ''
> +    if errarg:
> +        errparg = '&' + errarg
> +    else:
> +        errparg = 'NULL'

Suggest a blank line here, just like in the code you replace.

> +    for memb in members:
> +        if memb.optional:
> +            ret += mcgen('''
> +visit_optional(v, &%(prefix)shas_%(c_name)s, "%(name)s", %(errp)s);
> +''',
> +                         prefix=prefix, c_name=c_name(memb.name),
> +                         name=memb.name, errp=errparg)
> +            ret += gen_err_check(errarg)
> +            ret += mcgen('''
> +if (%(prefix)shas_%(c_name)s) {
> +''',
> +                         prefix=prefix, c_name=c_name(memb.name))
> +            push_indent()
> +
> +        # Ugly: sometimes we need to cast away const
> +        if need_cast and memb.type.name == 'str':
> +            cast = '(char **)'
> +        else:
> +            cast = ''
> +
> +        ret += mcgen('''
> +visit_type_%(c_type)s(v, %(cast)s&%(prefix)s%(c_name)s, "%(name)s", %(errp)s);
> +''',
> +                     c_type=memb.type.c_name(), prefix=prefix, cast=cast,
> +                     c_name=c_name(memb.name), name=memb.name,
> +                     errp=errparg)
> +        ret += gen_err_check(errarg)
> +
> +        if memb.optional:
> +            pop_indent()
> +            ret += mcgen('''
> +}
> +''')
> +    return ret
> +
>  #
>  # Common command line parsing
>  #

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call
  2015-09-26 21:05       ` Eric Blake
@ 2015-09-28  9:14         ` Markus Armbruster
  2015-10-06 21:10           ` [Qemu-devel] [RFC PATCH] qapi: split visit_end_struct() into pieces Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-28  9:14 UTC (permalink / raw
  To: Eric Blake
  Cc: marcandre.lureau, DirtY.iCE.hu, Michael Roth, ehabkost,
	qemu-devel

Eric Blake <eblake@redhat.com> writes:

> On 09/24/2015 10:14 AM, Eric Blake wrote:
>
>>>>
>>>>     visit_start_implicit_struct(m, (void **)obj, sizeof(FOO), &err);
>>>>         if (!err) {
>>>>             visit_type_FOO_fields(m, obj, &err);
>>>>             visit_end_implicit_struct(m, err ? NULL : &err);
>>>>         }
>>>>     error_propagate(errp, err);
>>>
>>> Hmmmmm... not sure we do this anywhere else, yet.  The ternary isn't
>>> exactly pretty, but the intent to ignore additional error is clear
>>> enough, I think.
>>>
>>> If we elect to adopt this new error handling pattern, we should perhaps
>>> document it in error.h.
>>>
>>> Third option: drop visit_end_implicit_struct()'s errp parameter.  If we
>>> find a compelling use for it, we'll put it back and solve the problem.
>>>
>> 
>> Ooh, interesting idea.  It changes the contract - but since the contract
>> isn't (yet) documented, and happens to work with existing uses without a
>> contract, it could indeed be nicer.  It would have knock-on effects to
>> 24/46 where I first try documenting the contract.
>
> Oh well.  We do have a compelling use: qmp-input-visitor.c can set errp
> during visit_end_struct() when in strict mode (basically, the mode which
> warns if the input QDict has leftover key:value pairs that were not
> consumed by visit_type_FOO() between the start/end call).  I don't know
> if visit_end_list() or visit_end_implicit_struct() care; but then we
> have the argument that it is worth keeping them consistent with
> visit_end_struct() which can indeed raise an error.
>
> One other potential alternative:  What if we split visit_end_struct()
> into two visitor functions, one that checks for success, and the other
> that is called unconditionally to clean up resources.  That is, go from:
>
>     visit_start_struct(m, (void **)obj, "foo", name, sizeof(FOO), &err);
>     if (!err) {
>         if (*obj) {
>             visit_type_FOO_fields(m, obj, errp);
>         }
>         visit_end_struct(m, &err);
>     }
>     error_propagate(errp, err);
>
> to a form where the check for leftover key/value pairs is only done on
> success with a new visit_check_struct():
>
>     visit_start_struct(m, (void **)obj, "foo", name, sizeof(FOO), &err);
>     if (!err) {
>         if (*obj) {
>             visit_type_FOO_fields(m, obj, &err);
>         }
>         if (!err) {
>             visit_check_struct(m, &err);
>         }
>         visit_end_struct(m);
>     }
>     error_propagate(errp, err);

I think this split could help with writing safe code: in
visit_check_struct() you can rely on "no error so far", as usual.  In
visit_end_struct(), you can't, but it should be a pure cleanup function,
where that's quite normal.

Looks like we're getting drawn into visitor contract territory again.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call
  2015-09-26 21:41     ` [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call Eric Blake
  2015-09-27  2:26       ` Eric Blake
@ 2015-09-28  9:24       ` Markus Armbruster
  1 sibling, 0 replies; 108+ messages in thread
From: Markus Armbruster @ 2015-09-28  9:24 UTC (permalink / raw
  To: Eric Blake
  Cc: Michael Roth, marcandre.lureau, qemu-devel, ehabkost,
	DirtY.iCE.hu

Eric Blake <eblake@redhat.com> writes:

> On 09/24/2015 08:58 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> Due to the existing semantics of the error_set() family,
>>> generated sequences in the qapi visitors such as:
>>>
>>>     visit_start_implicit_struct(m, (void **)obj, sizeof(FOO), &err);
>>>         if (!err) {
>>>             visit_type_FOO_fields(m, obj, errp);
>>>             visit_end_implicit_struct(m, &err);
>>>         }
>>>     error_propagate(errp, err);
>> 
>> Indentation seems off.  Intentional?
>> 
>>>
>>> are risky: if visit_type_FOO_fields() sets errp, and then
>>> visit_end_implicit_struct() also encounters an error, the
>>> attempt to overwrite the first error will cause an abort().
>
> I didn't even read error_propagate()'s contract correctly. It
> specifically specifies that if errp is already set, then err is ignored.

Yes.  Differs from error_set() & friends, where the destination must not
contain an error.  The inconsistency is a bit ugly.  Perhaps it adds
enough convenience to make it worthwhile.  Anyway, changing it now would
be a huge bother.

Note that GLib's g_propagate_error() requires the destination not to
contain an error.

> So the above sequence is actually just fine, because only the following
> paths exist:
>
> visit_start_implicit_struct() fails into &err,
> error_propagate() passes err into caller's errp
>
> visit_start_implicit_struct() succeeds,
> visit_type_FOO_fields() fails into caller's errp,
> visit_end_implicit_struct() succeeds,
> error_propagate() does nothing
>
> visit_start_implicit_struct() succeeds,
> visit_type_FOO_fields() fails into caller's errp,
> visit_end_implicit_struct() fails int &err,
> error_propagate() does nothing (errp trumps err)

Yes, but visit_end_implicit_struct() gets called with an errp argument
that may already contain an error, and that's unusual.  Prominent notice
in the contract required.

> visit_start_implicit_struct() succeeds,
> visit_type_FOO_fields() succeeds,
> visit_end_implicit_struct() fails int &err,
> error_propagate() passes err into caller's errp
>
> visit_start_implicit_struct() succeeds,
> visit_type_FOO_fields() succeeds,
> visit_end_implicit_struct() succeeds,
> error_propagate() does nothing
>
>
> As such, I'm revisiting if anything is needed at all, other than making
> the various visit_start/visit_end patterns consistent with one another
> using existing idioms, and it may turn out we don't need the ternary
> after all.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 05/46] qapi: Test use of 'number' within alternates
  2015-09-25 22:32         ` Eric Blake
@ 2015-09-28  9:26           ` Markus Armbruster
  0 siblings, 0 replies; 108+ messages in thread
From: Markus Armbruster @ 2015-09-28  9:26 UTC (permalink / raw
  To: Eric Blake
  Cc: marcandre.lureau, DirtY.iCE.hu, Michael Roth, ehabkost,
	qemu-devel

Eric Blake <eblake@redhat.com> writes:

> On 09/24/2015 10:29 AM, Markus Armbruster wrote:
>
>>>>> +
>>>>> +    /* FIXME: Order of alternate should not affect semantics */
>>>>
>>>> Inhowfar does it affect semantics?  Or asked differently: what exactly
>>>> is wrong with this test now?
>>>>
>>>>> +    v = visitor_input_test_init(data, "42");
>>>>> +    visit_type_AltThree(v, &three, NULL, &error_abort);
>>>>> +    g_assert_cmpint(three->kind, ==, ALT_THREE_KIND_N);
>>>>> +    g_assert_cmpfloat(three->n, ==, 42);
>>>>> +    qapi_free_AltThree(three);
>>>>> +    one = NULL;
>>>
>>>
>>> AltTwo and AltThree are ostensibly the same struct (two branches, one
>>> for 'str' and one for 'number', just in a different order), but they
>>> parsed differently (AltTwo failed, AltThree succeeded).  The bug is
>>> fixed later when the order of the branch declaration no longer impacts
>>> the result of the parse.
>> 
>> Then nothing is wrong with this test case, and the FIXME doesn't belong
>> here.
>
> Actually, the test for AltThree succeeds only by accident. There are two
> bugs at play; when I fix the first bug (order shouldn't matter: AltTwo
> and AltThree should parse identically), then the second bug is finally
> exposed (integers aren't being parsed as numbers, in either AltTwo or
> AltThree). But I can certainly rework the FIXMEs both here and on the
> first fix (19/46) to make it more obvious what the second fix (20/46) is
> good for.

Yes, please.  I find the FIXME quoted above confusing, because it makes
me look for problems exposed by this test when there are none.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 45/46] net: Complete qapi-fication of netdev_add
  2015-09-25 16:48       ` Paolo Bonzini
@ 2015-09-28  9:31         ` Markus Armbruster
  2015-09-28 11:29           ` Paolo Bonzini
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-28  9:31 UTC (permalink / raw
  To: Paolo Bonzini
  Cc: ehabkost, Jason Wang, qemu-devel, DirtY.iCE.hu, marcandre.lureau

Paolo Bonzini <pbonzini@redhat.com> writes:

> On 23/09/2015 18:37, Eric Blake wrote:
>>>> 
>>>> Is this still type-unsafe like the old netdev_add (e.g. accepts
>>>> a string for an integer)?
>> I did not address that yet; it still needs further patches to
>> accept an integer as a port number. I can investigate what
>> additional patches are needed along those lines, while still
>> preserving back-compat to previously-accepted QMP command lines (it
>> may require the use of an 'alternate' type that accepts both int
>> and string).  The main goal here was that the command line is
>> unchanged, and that now the QMP command is introspectable, even if
>> what introspection shows is ugly types.
>
> This in fact is a laudable goal.  However, while changing the command to
>
> { 'command': 'netdev_add', 'data': 'Netdev', 'box': true, 'gen': false }
>
> for better introspection, you should keep 'gen':'false' and the manual
> implementation based on qemu_opts_from_qdict, otherwise you break
> backwards-compatibility.

Non sequitur :)

We need to stay sufficiently backwards compatible somehow.  'gen' false
is one possible solution.  Liberal use of alternate types could be
another.  A special input visitor mode could be a third.

Since my review cursor is >30 patches behind this one, I don't have an
opinion, yet.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 45/46] net: Complete qapi-fication of netdev_add
  2015-09-28  9:31         ` Markus Armbruster
@ 2015-09-28 11:29           ` Paolo Bonzini
  0 siblings, 0 replies; 108+ messages in thread
From: Paolo Bonzini @ 2015-09-28 11:29 UTC (permalink / raw
  To: Markus Armbruster
  Cc: ehabkost, Jason Wang, qemu-devel, DirtY.iCE.hu, marcandre.lureau



On 28/09/2015 11:31, Markus Armbruster wrote:
>> However, while changing the command to
>> >
>> > { 'command': 'netdev_add', 'data': 'Netdev', 'box': true, 'gen': false }
>> >
>> > for better introspection, you should keep 'gen':'false' and the manual
>> > implementation based on qemu_opts_from_qdict, otherwise you break
>> > backwards-compatibility.
> Non sequitur :)
> 
> We need to stay sufficiently backwards compatible somehow.  'gen' false
> is one possible solution.  Liberal use of alternate types could be
> another.  A special input visitor mode could be a third.

Agreed; my suggestion is the minimal change on top of this patch,
actually undoing part of it.  Any other approach should be done separately.

Paolo

> Since my review cursor is >30 patches behind this one, I don't have an
> opinion, yet.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 11/46] qapi: Don't use info as witness of implicit object type
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 11/46] qapi: Don't use info as witness of implicit object type Eric Blake
@ 2015-09-28 12:43   ` Markus Armbruster
  2015-09-29  3:58     ` Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-28 12:43 UTC (permalink / raw
  To: Eric Blake
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

Eric Blake <eblake@redhat.com> writes:

> A future patch will enable error reporting from the various
> check() methods.  But to report an error on an implicit type,
> we'll need to associate a location with the type (the same
> location as the top-level entity that is causing the creation
> of the implicit type), and once we do that, keying off of
> whether foo.info exists is no longer a viable way to determine
> if foo is an implicit type.

Ensuring error messages are good even for implicit types could be hard.
But pretty much anything's better than error messages without location
information.

> Rename the info member to _info (so that sub-classes can still
> use it, but external code should not), add an is_implicit()
> method to QAPISchemaObjectType, and adjust the visitor to pass
> another parameter about whether the type is implicit.

I have doubts on the rename.

When you create an stable interface for use in other programs,
religiously hiding instance variables behind accessor methods can pay.
But in a purely internal interface like this one, I don't see the point.

If we run into a case where we want to use a QAPISchemaEntity's info, I
want to write .info and be done with it.  If we rename it to _info now,
we'll rename it back then.

So far, we've used the '_' prefix only for instance variables that are
clearly internal.  Mostly for stuff flowing from __init__() to check().

> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>  scripts/qapi-types.py          |  4 ++--
>  scripts/qapi-visit.py          |  4 ++--
>  scripts/qapi.py                | 33 +++++++++++++++++++--------------
>  tests/qapi-schema/test-qapi.py |  2 +-
>  4 files changed, 24 insertions(+), 19 deletions(-)
>
> diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
> index b292682..aa25e03 100644
> --- a/scripts/qapi-types.py
> +++ b/scripts/qapi-types.py
> @@ -253,8 +253,8 @@ class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
>              self.decl += gen_array(name, element_type)
>              self._gen_type_cleanup(name)
>
> -    def visit_object_type(self, name, info, base, members, variants):
> -        if info:
> +    def visit_object_type(self, name, info, base, members, variants, implicit):

This is now right at the PEP8 line length limit, and the number of
parameters is getting unwieldy, too.  Hmm.

> +        if not implicit:
>              self._fwdecl += gen_fwd_object_or_array(name)
>              if variants:
>                  assert not members      # not implemented
> diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
> index 1f287ba..62a47fa 100644
> --- a/scripts/qapi-visit.py
> +++ b/scripts/qapi-visit.py
> @@ -348,8 +348,8 @@ class QAPISchemaGenVisitVisitor(QAPISchemaVisitor):
>              self.decl += decl
>              self.defn += defn
>
> -    def visit_object_type(self, name, info, base, members, variants):
> -        if info:
> +    def visit_object_type(self, name, info, base, members, variants, implicit):
> +        if not implicit:
>              self.decl += gen_visit_decl(name)
>              if variants:
>                  assert not members      # not implemented
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 7275daa..1dc7641 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -786,7 +786,7 @@ class QAPISchemaEntity(object):
>      def __init__(self, name, info):
>          assert isinstance(name, str)
>          self.name = name
> -        self.info = info
> +        self._info = info
>
>      def c_name(self):
>          return c_name(self.name)
> @@ -814,7 +814,7 @@ class QAPISchemaVisitor(object):
>      def visit_array_type(self, name, info, element_type):
>          pass
>
> -    def visit_object_type(self, name, info, base, members, variants):
> +    def visit_object_type(self, name, info, base, members, variants, implicit):
>          pass
>
>      def visit_object_type_flat(self, name, info, members, variants):
> @@ -877,7 +877,7 @@ class QAPISchemaBuiltinType(QAPISchemaType):
>          return self._json_type_name
>
>      def visit(self, visitor):
> -        visitor.visit_builtin_type(self.name, self.info, self.json_type())
> +        visitor.visit_builtin_type(self.name, self._info, self.json_type())
>
>
>  class QAPISchemaEnumType(QAPISchemaType):
> @@ -903,7 +903,7 @@ class QAPISchemaEnumType(QAPISchemaType):
>          return 'string'
>
>      def visit(self, visitor):
> -        visitor.visit_enum_type(self.name, self.info,
> +        visitor.visit_enum_type(self.name, self._info,
>                                  self.values, self.prefix)
>
>
> @@ -922,7 +922,7 @@ class QAPISchemaArrayType(QAPISchemaType):
>          return 'array'
>
>      def visit(self, visitor):
> -        visitor.visit_array_type(self.name, self.info, self.element_type)
> +        visitor.visit_array_type(self.name, self._info, self.element_type)
>
>
>  class QAPISchemaObjectType(QAPISchemaType):
> @@ -961,21 +961,25 @@ class QAPISchemaObjectType(QAPISchemaType):
>              self.variants.check(schema, members, seen)
>          self.members = members
>
> +    def is_implicit(self):
> +        return self.name[0] == ':'
> +

The predicate could be defined on any QAPISchemaType, or even any
QAPISchemaEntity, but right now we only ever want to test it for
objects.  Okay.

>      def c_name(self):
> -        assert self.info
> +        assert not self.is_implicit()
>          return QAPISchemaType.c_name(self)
>
>      def c_type(self, is_param=False):
> -        assert self.info
> +        assert not self.is_implicit()
>          return QAPISchemaType.c_type(self)
>
>      def json_type(self):
>          return 'object'
>
>      def visit(self, visitor):
> -        visitor.visit_object_type(self.name, self.info,
> -                                  self.base, self.local_members, self.variants)
> -        visitor.visit_object_type_flat(self.name, self.info,
> +        visitor.visit_object_type(self.name, self._info,
> +                                  self.base, self.local_members, self.variants,
> +                                  self.is_implicit())
> +        visitor.visit_object_type_flat(self.name, self._info,
>                                         self.members, self.variants)
>
>
> @@ -1034,7 +1038,8 @@ class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
>      # This function exists to support ugly simple union special cases
>      # TODO get rid of them, and drop the function
>      def simple_union_type(self):
> -        if isinstance(self.type, QAPISchemaObjectType) and not self.type.info:
> +        if isinstance(self.type, QAPISchemaObjectType) and \
> +           self.type.is_implicit():
>              assert len(self.type.members) == 1
>              assert not self.type.variants
>              return self.type.members[0].type
> @@ -1055,7 +1060,7 @@ class QAPISchemaAlternateType(QAPISchemaType):
>          return 'value'
>
>      def visit(self, visitor):
> -        visitor.visit_alternate_type(self.name, self.info, self.variants)
> +        visitor.visit_alternate_type(self.name, self._info, self.variants)
>
>
>  class QAPISchemaCommand(QAPISchemaEntity):
> @@ -1080,7 +1085,7 @@ class QAPISchemaCommand(QAPISchemaEntity):
>              assert isinstance(self.ret_type, QAPISchemaType)
>
>      def visit(self, visitor):
> -        visitor.visit_command(self.name, self.info,
> +        visitor.visit_command(self.name, self._info,
>                                self.arg_type, self.ret_type,
>                                self.gen, self.success_response)
>
> @@ -1099,7 +1104,7 @@ class QAPISchemaEvent(QAPISchemaEntity):
>              assert not self.arg_type.variants   # not implemented
>
>      def visit(self, visitor):
> -        visitor.visit_event(self.name, self.info, self.arg_type)
> +        visitor.visit_event(self.name, self._info, self.arg_type)
>
>
>  class QAPISchema(object):
> diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
> index 649677e..f2cce64 100644
> --- a/tests/qapi-schema/test-qapi.py
> +++ b/tests/qapi-schema/test-qapi.py
> @@ -22,7 +22,7 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
>          if prefix:
>              print '    prefix %s' % prefix
>
> -    def visit_object_type(self, name, info, base, members, variants):
> +    def visit_object_type(self, name, info, base, members, variants, implicit):
>          print 'object %s' % name
>          if base:
>              print '    base %s' % base.name

Three of our visitors implement visit_object_type():

* test-qapi.py doesn't care about implicit (implicitness is obvious
  enough from the name here).

* qapi-types.py and qapi-visit.py ignore implicit object types.  Hmm.

  qapi-introspect.py has a similar need: it wants to ignore *all* types.
  Two ways to ignore entities seem one too many.  Preexisting, but your
  patch makes it stand out a bit more.

  Could we reuse the existing mechanism somehow (and keep method
  visit_object_type() simple)?

  To reuse it without changes, we'd have to make implicit object types a
  separate class, so that QAPISchema.visit()'s isinstance() test can be
  put to work.

  Another option is generalizing QAPISchema's filter.  How?

  A third option is to abandon QAPISchema's filter, and make
  qapi-introspect.py filter in the visitor methods, just like we filter
  implicit objects.

Patch could be split into

A. Encapsulate the "is implicit" predicate in a method, i.e. replace
   not o.info by o.is_implicit().

B. Clean up how we filter out implicit objects.  May better go before A,
   not sure.

C. Rename .info to ._info.  Not sure we even want this part.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 12/46] qapi: Track location that created an implicit type
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 12/46] qapi: Track location that created an implicit type Eric Blake
@ 2015-09-28 12:56   ` Markus Armbruster
  2015-09-29  4:03     ` Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-28 12:56 UTC (permalink / raw
  To: Eric Blake
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

Eric Blake <eblake@redhat.com> writes:

> A future patch will enable error detection in the various
> QapiSchema check() methods.  But since all errors have to
> have an associated 'info' location, we need a location to
> be associated with all implicit types.  Easiest is to reuse
> the location of the enclosing entity that includes the
> dictionary defining the implicit type.
>
> While at it, we were always passing None as the location of
> array types, making that parameter useless; sharing the
> location (if any) of the underlying element type makes sense.

The parameter is useless only because all array types are implicit.
Once we change that, it won't be anymore.

>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>  scripts/qapi.py | 22 ++++++++++++----------
>  1 file changed, 12 insertions(+), 10 deletions(-)
>
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 1dc7641..e982970 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -908,8 +908,8 @@ class QAPISchemaEnumType(QAPISchemaType):
>
>
>  class QAPISchemaArrayType(QAPISchemaType):
> -    def __init__(self, name, info, element_type):
> -        QAPISchemaType.__init__(self, name, info)
> +    def __init__(self, name, element_type):
> +        QAPISchemaType.__init__(self, name, None)
>          assert isinstance(element_type, str)
>          self._element_type_name = element_type
>          self.element_type = None
> @@ -917,6 +917,7 @@ class QAPISchemaArrayType(QAPISchemaType):
>      def check(self, schema):
>          self.element_type = schema.lookup_type(self._element_type_name)
>          assert self.element_type
> +        self._info = self.element_type._info
>
>      def json_type(self):
>          return 'array'

Implicit array type's info is the element type's info.  Okay.

> @@ -928,6 +929,7 @@ class QAPISchemaArrayType(QAPISchemaType):
>  class QAPISchemaObjectType(QAPISchemaType):
>      def __init__(self, name, info, base, local_members, variants):
>          QAPISchemaType.__init__(self, name, info)
> +        assert info or name == ':empty'

I think what we really want to assert is "we got info unless this is a
built-in entity", in QAPISchemaEntity.__init__().

Built-in entities are exactly the types defined by
QAPISchema._def_predefineds(), currently the built-in types and
':empty'.

>          assert base is None or isinstance(base, str)
>          for m in local_members:
>              assert isinstance(m, QAPISchemaObjectTypeMember)
> @@ -1165,15 +1167,15 @@ class QAPISchema(object):
>      def _make_array_type(self, element_type):
>          name = element_type + 'List'
>          if not self.lookup_type(name):
> -            self._def_entity(QAPISchemaArrayType(name, None, element_type))
> +            self._def_entity(QAPISchemaArrayType(name, element_type))
>          return name
>
> -    def _make_implicit_object_type(self, name, role, members):
> +    def _make_implicit_object_type(self, name, info, role, members):
>          if not members:
>              return None
>          name = ':obj-%s-%s' % (name, role)
>          if not self.lookup_entity(name, QAPISchemaObjectType):
> -            self._def_entity(QAPISchemaObjectType(name, None, None,
> +            self._def_entity(QAPISchemaObjectType(name, info, None,
>                                                    members, None))
>          return name
>
> @@ -1210,11 +1212,11 @@ class QAPISchema(object):
>      def _make_variant(self, case, typ):
>          return QAPISchemaObjectTypeVariant(case, typ)
>
> -    def _make_simple_variant(self, case, typ):
> +    def _make_simple_variant(self, info, case, typ):
>          if isinstance(typ, list):
>              assert len(typ) == 1
>              typ = self._make_array_type(typ[0])
> -        typ = self._make_implicit_object_type(typ, 'wrapper',
> +        typ = self._make_implicit_object_type(typ, info, 'wrapper',
>                                                [self._make_member('data', typ)])
>          return QAPISchemaObjectTypeVariant(case, typ)
>
> @@ -1232,7 +1234,7 @@ class QAPISchema(object):
>              variants = [self._make_variant(key, value)
>                          for (key, value) in data.iteritems()]
>          else:
> -            variants = [self._make_simple_variant(key, value)
> +            variants = [self._make_simple_variant(info, key, value)
>                          for (key, value) in data.iteritems()]
>              tag_enum = self._make_tag_enum(name, variants)
>          self._def_entity(

A simple union type's implicit wrapper types' info is the simple union
type's info.  Okay.

> @@ -1263,7 +1265,7 @@ class QAPISchema(object):
>          gen = expr.get('gen', True)
>          success_response = expr.get('success-response', True)
>          if isinstance(data, OrderedDict):
> -            data = self._make_implicit_object_type(name, 'arg',
> +            data = self._make_implicit_object_type(name, info, 'arg',
>                                                     self._make_members(data))
>          if isinstance(rets, list):
>              assert len(rets) == 1

A command's implicit argument type's info is the command's info.  Okay.

> @@ -1275,7 +1277,7 @@ class QAPISchema(object):
>          name = expr['event']
>          data = expr.get('data')
>          if isinstance(data, OrderedDict):
> -            data = self._make_implicit_object_type(name, 'arg',
> +            data = self._make_implicit_object_type(name, info, 'arg',
>                                                     self._make_members(data))
>          self._def_entity(QAPISchemaEvent(name, info, data))

An event's implicit argument type's info is the event's info.  Okay.

Missing: implicit enum types' info.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add
  2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
                   ` (45 preceding siblings ...)
  2015-09-21 21:58 ` [Qemu-devel] [PATCH v5 46/46] qapi: Allow anonymous base for flat union Eric Blake
@ 2015-09-28 13:07 ` Markus Armbruster
  2015-09-29  3:43   ` Eric Blake
  46 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-28 13:07 UTC (permalink / raw
  To: Eric Blake; +Cc: marcandre.lureau, qemu-devel, ehabkost, DirtY.iCE.hu

I think the first ten patches are a about as large a bite as we can chew
and commit quickly.  If you split them off, I can try to get them in
quickly, and in parallel continue reviewing the rest of your series.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 10/46] qapi: Merge generation of per-member visits
  2015-09-28  6:17   ` Markus Armbruster
@ 2015-09-28 15:40     ` Eric Blake
  2015-09-29  7:37       ` Markus Armbruster
  0 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-28 15:40 UTC (permalink / raw
  To: Markus Armbruster
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

[-- Attachment #1: Type: text/plain, Size: 3132 bytes --]

On 09/28/2015 12:17 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Consolidate the code between visit, command marshalling, and
>> event generation that iterates over the members of a struct.
>> It reduces code duplication in the generator, with no change to
>> generated marshal code, slightly more verbose visit code:
>>
>> |     visit_optional(v, &(*obj)->has_device, "device", &err);
>> |-    if (!err && (*obj)->has_device) {
>> |-        visit_type_str(v, &(*obj)->device, "device", &err);
>> |-    }
>> |     if (err) {
>> |         goto out;
>> |     }
>> |+    if ((*obj)->has_device) {
>> |+        visit_type_str(v, &(*obj)->device, "device", &err);
>> |+        if (err) {
>> |+            goto out;
>> |+        }
>> |+    }
> 
> I think the more verbose code is easier to understand, because it checks
> for errors exactly the same way as we do all the time, mimimizing
> cognitive load.

And then I shorten it later in 27/46, but the shorter form is consistent
to all three due to this refactor into a common helper.

> 
>> and slightly more verbose event code (recall that the qmp
>> output visitor has a no-op visit_optional()):
>>
>> |+    visit_optional(v, &has_offset, "offset", &err);
>> |+    if (err) {
>> |+        goto out;
>> |+    }
> 
> If we had a written contract, I suspect not calling visit_optional()
> would be a bug.

Indeed - we got lucky that the output visitor's visit_optional() was a
no-op.  I'll make that fact more obvious in the commit message.


>>
>> -def gen_err_check(err):
>> -    if not err:
>> -        return ''
>> -    return mcgen('''
>> -if (%(err)s) {
>> -    goto out;
>> -}
>> -''',
>> -                 err=err)
>> -
>> -
> 
> Only code motion.

I'm actually debating about splitting the move of this helper function
into its own patch, and using it in more places. Part of my debate is
that I'd rather go with:

def gen_err_check(err='err', label='out'):
    if not err:
        return ''
    return mcgen('''
    if (%(err)s) {
        goto %(label)s;
    }
''',
                 err=err, label=label)

so that it is applicable in more places, and so that callers don't have
to worry about push_indent()/pop_indent() if it is at the default usage
of 4 spaces (right now, all callers have to push, and not just callers
at 8 spaces where it is embedded inside an 'if' block).

Hmm, and just writing that, I'm wondering if we should fix mcgen() to
eat leading whitespace on any final blank line [as a separate patch],
since at least for me, emacs wants me to indent as:

    return mcgen('''
    code
                 ''', args)

rather than with the closing ''' flush left.


>> -''')
>> +    ret += gen_visit_fields(arg_type.members, '', False, errarg)
> 
> Perhaps a bit neater: make parameters prefix='', need_cast=False, and
> say prefix=... and need_cast=True in the one call where you need it.

Good idea, will work it into my v6.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add
  2015-09-28 13:07 ` [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Markus Armbruster
@ 2015-09-29  3:43   ` Eric Blake
  0 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-29  3:43 UTC (permalink / raw
  To: Markus Armbruster; +Cc: marcandre.lureau, qemu-devel, ehabkost, DirtY.iCE.hu

[-- Attachment #1: Type: text/plain, Size: 1269 bytes --]

On 09/28/2015 07:07 AM, Markus Armbruster wrote:
> I think the first ten patches are a about as large a bite as we can chew
> and commit quickly.  If you split them off, I can try to get them in
> quickly, and in parallel continue reviewing the rest of your series.

Done. Well, the first 10 patches turned into 16 on the respin, but
hopefully easier to chew.
http://repo.or.cz/qemu/ericb.git/shortlog/refs/tags/qapi-cleanupv6a
[I'd also post a mail archives link, but it looks like the list server
is having some ugly delays in sending mail from lots of people today, so
I don't have a link yet]

I guess you can continue reviewing v5 textually, even though it will be
hard to actually apply many of those patches atop v6 subset A, while
waiting for me to rebase patches based on comments so far.  I've got
enough churn locally rebasing things already that it may be the better
part of a day before I post the rest of my v6 series subset B and update
my public git repo (I know Marc-Andre at least is basing his patches on
my public repo, so I'm trying to avoid rewinding that branch to anything
that has less functionality than v5 had)

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 11/46] qapi: Don't use info as witness of implicit object type
  2015-09-28 12:43   ` Markus Armbruster
@ 2015-09-29  3:58     ` Eric Blake
  2015-09-29  7:51       ` Markus Armbruster
  0 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-29  3:58 UTC (permalink / raw
  To: Markus Armbruster
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

[-- Attachment #1: Type: text/plain, Size: 5012 bytes --]

On 09/28/2015 06:43 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> A future patch will enable error reporting from the various
>> check() methods.  But to report an error on an implicit type,
>> we'll need to associate a location with the type (the same
>> location as the top-level entity that is causing the creation
>> of the implicit type), and once we do that, keying off of
>> whether foo.info exists is no longer a viable way to determine
>> if foo is an implicit type.
> 
> Ensuring error messages are good even for implicit types could be hard.
> But pretty much anything's better than error messages without location
> information.

Especially since the current implementation crashes hard when trying to
report an error with info=None.

> 
>> Rename the info member to _info (so that sub-classes can still
>> use it, but external code should not), add an is_implicit()
>> method to QAPISchemaObjectType, and adjust the visitor to pass
>> another parameter about whether the type is implicit.
> 
> I have doubts on the rename.

Fair enough; the proposal to separate it into its own patch, so we can
then discard or easily revert it, sounds like the right approach.


>>  class QAPISchemaObjectType(QAPISchemaType):
>> @@ -961,21 +961,25 @@ class QAPISchemaObjectType(QAPISchemaType):
>>              self.variants.check(schema, members, seen)
>>          self.members = members
>>
>> +    def is_implicit(self):
>> +        return self.name[0] == ':'
>> +
> 
> The predicate could be defined on any QAPISchemaType, or even any
> QAPISchemaEntity, but right now we only ever want to test it for
> objects.  Okay.

Yeah, I thought about that.  All builtin types are implicit, all array
types are implicit, no commands or events are implicit, and we didn't
make any different generated output based on whether enums were explicit
or implicit, so that leaves just objects.

>> +++ b/tests/qapi-schema/test-qapi.py
>> @@ -22,7 +22,7 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
>>          if prefix:
>>              print '    prefix %s' % prefix
>>
>> -    def visit_object_type(self, name, info, base, members, variants):
>> +    def visit_object_type(self, name, info, base, members, variants, implicit):
>>          print 'object %s' % name
>>          if base:
>>              print '    base %s' % base.name
> 
> Three of our visitors implement visit_object_type():

Another idea would be change the signature from:

def visit_object_type(self (QAPISchemaVisitor), name (str),
                      info (dict), base (QEMUSchemaObjectType),
                      members (list of QEMUSchemaObjectTypeMember),
                      variants (QAPISchemaObjectTypeVariants),
                      implicit (bool))

to:

def visit_object_type(self, object (QEMUSchemaObjectType))

and let callers dereference object.name, object.info, object.base,
object.members (or object.local_members), object.variants, and
object.is_implicit() as they see fit. (In fact, in one of my later
patches, I already wished I had access to the actual
QEMUSchemaObjectType object rather than its breakdown of parts to begin
with, and ended up doing a schema.lookupType(name) just to get back to
that piece of information).


> 
> * test-qapi.py doesn't care about implicit (implicitness is obvious
>   enough from the name here).
> 
> * qapi-types.py and qapi-visit.py ignore implicit object types.  Hmm.
> 
>   qapi-introspect.py has a similar need: it wants to ignore *all* types.
>   Two ways to ignore entities seem one too many.  Preexisting, but your
>   patch makes it stand out a bit more.
> 
>   Could we reuse the existing mechanism somehow (and keep method
>   visit_object_type() simple)?
> 
>   To reuse it without changes, we'd have to make implicit object types a
>   separate class, so that QAPISchema.visit()'s isinstance() test can be
>   put to work.

Maybe. Would also make implementing is_implicit() easy (which type did I
instantiate) rather than hacky (does name start with ':').

> 
>   Another option is generalizing QAPISchema's filter.  How?
> 
>   A third option is to abandon QAPISchema's filter, and make
>   qapi-introspect.py filter in the visitor methods, just like we filter
>   implicit objects.

I'm still thinking about this one.

> 
> Patch could be split into
> 
> A. Encapsulate the "is implicit" predicate in a method, i.e. replace
>    not o.info by o.is_implicit().
> 
> B. Clean up how we filter out implicit objects.  May better go before A,
>    not sure.
> 
> C. Rename .info to ._info.  Not sure we even want this part.

Yes, I'll go along with a split somewhere along these lines before
reposting this patch for v6, although I'm going to have to sleep on it
before deciding how to clean up the filtering.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 12/46] qapi: Track location that created an implicit type
  2015-09-28 12:56   ` Markus Armbruster
@ 2015-09-29  4:03     ` Eric Blake
  2015-09-29  8:02       ` Markus Armbruster
  0 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-29  4:03 UTC (permalink / raw
  To: Markus Armbruster
  Cc: marcandre.lureau, DirtY.iCE.hu, qemu-devel, ehabkost,
	Michael Roth

[-- Attachment #1: Type: text/plain, Size: 2753 bytes --]

On 09/28/2015 06:56 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> A future patch will enable error detection in the various
>> QapiSchema check() methods.  But since all errors have to
>> have an associated 'info' location, we need a location to
>> be associated with all implicit types.  Easiest is to reuse
>> the location of the enclosing entity that includes the
>> dictionary defining the implicit type.
>>
>> While at it, we were always passing None as the location of
>> array types, making that parameter useless; sharing the
>> location (if any) of the underlying element type makes sense.
> 
> The parameter is useless only because all array types are implicit.
> Once we change that, it won't be anymore.

I guess it depends whether I pass in the existing info when creating the
array or determine the info when resolving the string name of the array
element during check() (it will ultimately be the same info either way,
it's just a question of which approach looks cleaner for getting the
information set).


>> @@ -917,6 +917,7 @@ class QAPISchemaArrayType(QAPISchemaType):
>>      def check(self, schema):
>>          self.element_type = schema.lookup_type(self._element_type_name)
>>          assert self.element_type
>> +        self._info = self.element_type._info
>>
>>      def json_type(self):
>>          return 'array'
> 
> Implicit array type's info is the element type's info.  Okay.
> 
>> @@ -928,6 +929,7 @@ class QAPISchemaArrayType(QAPISchemaType):
>>  class QAPISchemaObjectType(QAPISchemaType):
>>      def __init__(self, name, info, base, local_members, variants):
>>          QAPISchemaType.__init__(self, name, info)
>> +        assert info or name == ':empty'
> 
> I think what we really want to assert is "we got info unless this is a
> built-in entity", in QAPISchemaEntity.__init__().

To do that, arrays would have to pass info in to __init__() rather than
deferring to check() as I did above.

> 
> Built-in entities are exactly the types defined by
> QAPISchema._def_predefineds(), currently the built-in types and
> ':empty'.

I'm still wondering how best to test that, but agree that hoisting the
assert into QAPISchemaEntity instead of just in QAPISchemaObjectType
would be nice.  Maybe some sort of boolean switch, initially off, then
turned on after _def_predefineds() is called, and assuming that no types
other than predefineds are initialized prior to that point.

> 
> Missing: implicit enum types' info.

I'll add it; should be the info of the containing union type that caused
the implicit enum.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 10/46] qapi: Merge generation of per-member visits
  2015-09-28 15:40     ` Eric Blake
@ 2015-09-29  7:37       ` Markus Armbruster
  0 siblings, 0 replies; 108+ messages in thread
From: Markus Armbruster @ 2015-09-29  7:37 UTC (permalink / raw
  To: Eric Blake
  Cc: Michael Roth, marcandre.lureau, qemu-devel, ehabkost,
	DirtY.iCE.hu

Eric Blake <eblake@redhat.com> writes:

> On 09/28/2015 12:17 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> Consolidate the code between visit, command marshalling, and
>>> event generation that iterates over the members of a struct.
>>> It reduces code duplication in the generator, with no change to
>>> generated marshal code, slightly more verbose visit code:
>>>
>>> |     visit_optional(v, &(*obj)->has_device, "device", &err);
>>> |-    if (!err && (*obj)->has_device) {
>>> |-        visit_type_str(v, &(*obj)->device, "device", &err);
>>> |-    }
>>> |     if (err) {
>>> |         goto out;
>>> |     }
>>> |+    if ((*obj)->has_device) {
>>> |+        visit_type_str(v, &(*obj)->device, "device", &err);
>>> |+        if (err) {
>>> |+            goto out;
>>> |+        }
>>> |+    }
>> 
>> I think the more verbose code is easier to understand, because it checks
>> for errors exactly the same way as we do all the time, mimimizing
>> cognitive load.
>
> And then I shorten it later in 27/46, but the shorter form is consistent
> to all three due to this refactor into a common helper.
>
>> 
>>> and slightly more verbose event code (recall that the qmp
>>> output visitor has a no-op visit_optional()):
>>>
>>> |+    visit_optional(v, &has_offset, "offset", &err);
>>> |+    if (err) {
>>> |+        goto out;
>>> |+    }
>> 
>> If we had a written contract, I suspect not calling visit_optional()
>> would be a bug.
>
> Indeed - we got lucky that the output visitor's visit_optional() was a
> no-op.  I'll make that fact more obvious in the commit message.
>
>
>>>
>>> -def gen_err_check(err):
>>> -    if not err:
>>> -        return ''
>>> -    return mcgen('''
>>> -if (%(err)s) {
>>> -    goto out;
>>> -}
>>> -''',
>>> -                 err=err)
>>> -
>>> -
>> 
>> Only code motion.
>
> I'm actually debating about splitting the move of this helper function
> into its own patch, and using it in more places. Part of my debate is
> that I'd rather go with:
>
> def gen_err_check(err='err', label='out'):
>     if not err:
>         return ''
>     return mcgen('''
>     if (%(err)s) {
>         goto %(label)s;
>     }
> ''',
>                  err=err, label=label)
>
> so that it is applicable in more places, and so that callers don't have
> to worry about push_indent()/pop_indent() if it is at the default usage
> of 4 spaces (right now, all callers have to push, and not just callers
> at 8 spaces where it is embedded inside an 'if' block).

Could have a parameter indent_amount with a suitable default, passed on
to push_indent() / pop_indent().

Our helper function's indentation level is rather haphazard.

> Hmm, and just writing that, I'm wondering if we should fix mcgen() to
> eat leading whitespace on any final blank line [as a separate patch],
> since at least for me, emacs wants me to indent as:
>
>     return mcgen('''
>     code
>                  ''', args)
>
> rather than with the closing ''' flush left.

I think Emacs is mistaken here.  I find the indented ''' mildly
distracting, because it immediately begs the question whether the
trailing whitespace ends up in the generated code.

If we want to add further processing, such as stripping trailing
whitespace, the correct place is cgen().  If we decide to strip it, I
guess we better strip it from every line, not just the last one.

However, I think the appropriate place to strip whitespace is the source
code.

See also commit 77e703b.

>>> -''')
>>> +    ret += gen_visit_fields(arg_type.members, '', False, errarg)
>> 
>> Perhaps a bit neater: make parameters prefix='', need_cast=False, and
>> say prefix=... and need_cast=True in the one call where you need it.
>
> Good idea, will work it into my v6.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 11/46] qapi: Don't use info as witness of implicit object type
  2015-09-29  3:58     ` Eric Blake
@ 2015-09-29  7:51       ` Markus Armbruster
  2015-09-30  4:13         ` [Qemu-devel] [RFC PATCH] qapi: Use callback to determine visit filtering Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-29  7:51 UTC (permalink / raw
  To: Eric Blake
  Cc: Michael Roth, marcandre.lureau, qemu-devel, ehabkost,
	DirtY.iCE.hu

Eric Blake <eblake@redhat.com> writes:

> On 09/28/2015 06:43 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> A future patch will enable error reporting from the various
>>> check() methods.  But to report an error on an implicit type,
>>> we'll need to associate a location with the type (the same
>>> location as the top-level entity that is causing the creation
>>> of the implicit type), and once we do that, keying off of
>>> whether foo.info exists is no longer a viable way to determine
>>> if foo is an implicit type.
>> 
>> Ensuring error messages are good even for implicit types could be hard.
>> But pretty much anything's better than error messages without location
>> information.
>
> Especially since the current implementation crashes hard when trying to
> report an error with info=None.
>
>> 
>>> Rename the info member to _info (so that sub-classes can still
>>> use it, but external code should not), add an is_implicit()
>>> method to QAPISchemaObjectType, and adjust the visitor to pass
>>> another parameter about whether the type is implicit.
>> 
>> I have doubts on the rename.
>
> Fair enough; the proposal to separate it into its own patch, so we can
> then discard or easily revert it, sounds like the right approach.
[...]
> So far, we've used the '_' prefix only for instance variables that are
> clearly internal.  Mostly for stuff flowing from __init__() to check().

I'd rather not rename it at all now.  Stick to our present use of the
'_' prefix.

>>>  class QAPISchemaObjectType(QAPISchemaType):
>>> @@ -961,21 +961,25 @@ class QAPISchemaObjectType(QAPISchemaType):
>>>              self.variants.check(schema, members, seen)
>>>          self.members = members
>>>
>>> +    def is_implicit(self):
>>> +        return self.name[0] == ':'
>>> +
>> 
>> The predicate could be defined on any QAPISchemaType, or even any
>> QAPISchemaEntity, but right now we only ever want to test it for
>> objects.  Okay.
>
> Yeah, I thought about that.  All builtin types are implicit, all array
> types are implicit, no commands or events are implicit, and we didn't
> make any different generated output based on whether enums were explicit
> or implicit, so that leaves just objects.
>
>>> +++ b/tests/qapi-schema/test-qapi.py
>>> @@ -22,7 +22,7 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
>>>          if prefix:
>>>              print '    prefix %s' % prefix
>>>
>>> -    def visit_object_type(self, name, info, base, members, variants):
>>> +    def visit_object_type(self, name, info, base, members, variants, implicit):
>>>          print 'object %s' % name
>>>          if base:
>>>              print '    base %s' % base.name
>> 
>> Three of our visitors implement visit_object_type():
>
> Another idea would be change the signature from:
>
> def visit_object_type(self (QAPISchemaVisitor), name (str),
>                       info (dict), base (QEMUSchemaObjectType),
>                       members (list of QEMUSchemaObjectTypeMember),
>                       variants (QAPISchemaObjectTypeVariants),
>                       implicit (bool))
>
> to:
>
> def visit_object_type(self, object (QEMUSchemaObjectType))
>
> and let callers dereference object.name, object.info, object.base,
> object.members (or object.local_members), object.variants, and
> object.is_implicit() as they see fit. (In fact, in one of my later
> patches, I already wished I had access to the actual
> QEMUSchemaObjectType object rather than its breakdown of parts to begin
> with, and ended up doing a schema.lookupType(name) just to get back to
> that piece of information).

If you apply this idea across the board, all the visit_FOO() take
exactly two parameters, self and a FOO.  Feels a bit like declaring
bankruptcy on the visitor pattern...  You start to wonder why we need a
separate visit_FOO().

Nevertheless, I've felt the temptation myself.

>> * test-qapi.py doesn't care about implicit (implicitness is obvious
>>   enough from the name here).
>> 
>> * qapi-types.py and qapi-visit.py ignore implicit object types.  Hmm.
>> 
>>   qapi-introspect.py has a similar need: it wants to ignore *all* types.
>>   Two ways to ignore entities seem one too many.  Preexisting, but your
>>   patch makes it stand out a bit more.
>> 
>>   Could we reuse the existing mechanism somehow (and keep method
>>   visit_object_type() simple)?
>> 
>>   To reuse it without changes, we'd have to make implicit object types a
>>   separate class, so that QAPISchema.visit()'s isinstance() test can be
>>   put to work.
>
> Maybe. Would also make implementing is_implicit() easy (which type did I
> instantiate) rather than hacky (does name start with ':').

I don't mind the hacky bit, since you encapsulate it.

>>   Another option is generalizing QAPISchema's filter.  How?

We can always add an indirection: instead of parameterizing a fixed
predicate (ignore and isinstance(entity, ignore)) with a type ignore, we
could pass a predicate, i.e. a function mapping entity to bool.

>>   A third option is to abandon QAPISchema's filter, and make
>>   qapi-introspect.py filter in the visitor methods, just like we filter
>>   implicit objects.
>
> I'm still thinking about this one.
>
>> 
>> Patch could be split into
>> 
>> A. Encapsulate the "is implicit" predicate in a method, i.e. replace
>>    not o.info by o.is_implicit().
>> 
>> B. Clean up how we filter out implicit objects.  May better go before A,
>>    not sure.
>> 
>> C. Rename .info to ._info.  Not sure we even want this part.
>
> Yes, I'll go along with a split somewhere along these lines before
> reposting this patch for v6, although I'm going to have to sleep on it
> before deciding how to clean up the filtering.

Sounds good.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 12/46] qapi: Track location that created an implicit type
  2015-09-29  4:03     ` Eric Blake
@ 2015-09-29  8:02       ` Markus Armbruster
  2015-09-30 16:02         ` Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-09-29  8:02 UTC (permalink / raw
  To: Eric Blake
  Cc: Michael Roth, marcandre.lureau, qemu-devel, ehabkost,
	DirtY.iCE.hu

Eric Blake <eblake@redhat.com> writes:

> On 09/28/2015 06:56 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> A future patch will enable error detection in the various
>>> QapiSchema check() methods.  But since all errors have to
>>> have an associated 'info' location, we need a location to
>>> be associated with all implicit types.  Easiest is to reuse
>>> the location of the enclosing entity that includes the
>>> dictionary defining the implicit type.
>>>
>>> While at it, we were always passing None as the location of
>>> array types, making that parameter useless; sharing the
>>> location (if any) of the underlying element type makes sense.
>> 
>> The parameter is useless only because all array types are implicit.
>> Once we change that, it won't be anymore.
>
> I guess it depends whether I pass in the existing info when creating the
> array or determine the info when resolving the string name of the array
> element during check() (it will ultimately be the same info either way,
> it's just a question of which approach looks cleaner for getting the
> information set).

The latter leaves info None until check().  Unhealthy state.

I suspect the appropriate info is readily available in all the places
where we create array types, so let's just pass it to
_make_array_type().

>>> @@ -917,6 +917,7 @@ class QAPISchemaArrayType(QAPISchemaType):
>>>      def check(self, schema):
>>>          self.element_type = schema.lookup_type(self._element_type_name)
>>>          assert self.element_type
>>> +        self._info = self.element_type._info
>>>
>>>      def json_type(self):
>>>          return 'array'
>> 
>> Implicit array type's info is the element type's info.  Okay.
>> 
>>> @@ -928,6 +929,7 @@ class QAPISchemaArrayType(QAPISchemaType):
>>>  class QAPISchemaObjectType(QAPISchemaType):
>>>      def __init__(self, name, info, base, local_members, variants):
>>>          QAPISchemaType.__init__(self, name, info)
>>> +        assert info or name == ':empty'
>> 
>> I think what we really want to assert is "we got info unless this is a
>> built-in entity", in QAPISchemaEntity.__init__().
>
> To do that, arrays would have to pass info in to __init__() rather than
> deferring to check() as I did above.
>
>> 
>> Built-in entities are exactly the types defined by
>> QAPISchema._def_predefineds(), currently the built-in types and
>> ':empty'.
>
> I'm still wondering how best to test that, but agree that hoisting the
> assert into QAPISchemaEntity instead of just in QAPISchemaObjectType
> would be nice.  Maybe some sort of boolean switch, initially off, then
> turned on after _def_predefineds() is called, and assuming that no types
> other than predefineds are initialized prior to that point.

Either that, or have a special info value for built-in types.  Oh wait,
we got one already: None :)

Back to serious.  If we have to work for the assertion, we should
consider the assertion's value: how likely are the actual mistakes it
can catch?

Can legitimate errors be reported for built-in types?

>> 
>> Missing: implicit enum types' info.
>
> I'll add it; should be the info of the containing union type that caused
> the implicit enum.

Yup, just like for the union's wrapper objects.

^ permalink raw reply	[flat|nested] 108+ messages in thread

* [Qemu-devel] [RFC PATCH] qapi: Use callback to determine visit filtering
  2015-09-29  7:51       ` Markus Armbruster
@ 2015-09-30  4:13         ` Eric Blake
  2015-10-01  6:12           ` Markus Armbruster
  0 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-09-30  4:13 UTC (permalink / raw
  To: qemu-devel; +Cc: armbru, Michael Roth

Previously, qapi-types and qapi-visit filtered an object visit
to bypass implicit types by inspecting whether 'info' was passed
in (since implicit objects do not [yet] have associated info);
meanwhile qapi-introspect filtered by returning a python type
from visit_begin() in order to exclude all type entities on the
first pass.  Rather than keeping these ad hoc methods, rework
the contract of visit_begin() to state that it may return a
predicate function that returns True to ignore a given entity,
and use that mechanism to simplify all three visitors.

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

>>   Another option is generalizing QAPISchema's filter.  How?

> We can always add an indirection: instead of parameterizing a fixed
> predicate (ignore and isinstance(entity, ignore)) with a type ignore, we
> could pass a predicate, i.e. a function mapping entity to bool.

Like this?  Generates the same code before and after the
patch.  I'm open to suggestions on any way to make it more
idiomatic python, althouth it at least passed pep8.  In
particular, I'm wondering if the predicate should have its
sense reversed (pass False to skip, True to visit).

I'll probably drop the 'assert info' lines in the two
visit_object_type() calls (I put it there to make sure the
predicate was working).

 scripts/qapi-introspect.py |  5 ++++-
 scripts/qapi-types.py      | 20 ++++++++++++--------
 scripts/qapi-visit.py      | 18 +++++++++++-------
 scripts/qapi.py            |  4 +++-
 4 files changed, 30 insertions(+), 17 deletions(-)

diff --git a/scripts/qapi-introspect.py b/scripts/qapi-introspect.py
index 7d39320..37b4306 100644
--- a/scripts/qapi-introspect.py
+++ b/scripts/qapi-introspect.py
@@ -54,7 +54,7 @@ class QAPISchemaGenIntrospectVisitor(QAPISchemaVisitor):
         self._jsons = []
         self._used_types = []
         self._name_map = {}
-        return QAPISchemaType   # don't visit types for now
+        return self._visit_filter   # don't visit types for now

     def visit_end(self):
         # visit the types that are actually used
@@ -82,6 +82,9 @@ const char %(c_name)s[] = %(c_string)s;
         self._used_types = None
         self._name_map = None

+    def _visit_filter(self, entity):
+        return isinstance(entity, QAPISchemaType)
+
     def _name(self, name):
         if self._unmask:
             return name
diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
index d405f8d..fbe74e1 100644
--- a/scripts/qapi-types.py
+++ b/scripts/qapi-types.py
@@ -213,12 +213,16 @@ class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
         self._fwdefn = None
         self._btin = None

+    def _visit_filter(self, entity):
+        return isinstance(entity, QAPISchemaObjectType) and not entity.info
+
     def visit_begin(self, schema):
         self.decl = ''
         self.defn = ''
         self._fwdecl = ''
         self._fwdefn = ''
         self._btin = guardstart('QAPI_TYPES_BUILTIN')
+        return self._visit_filter    # ignore builtin types

     def visit_end(self):
         self.decl = self._fwdecl + self.decl
@@ -254,14 +258,14 @@ class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
             self._gen_type_cleanup(name)

     def visit_object_type(self, name, info, base, members, variants):
-        if info:
-            self._fwdecl += gen_fwd_object_or_array(name)
-            if variants:
-                assert not members      # not implemented
-                self.decl += gen_union(name, base, variants)
-            else:
-                self.decl += gen_struct(name, base, members)
-            self._gen_type_cleanup(name)
+        assert info
+        self._fwdecl += gen_fwd_object_or_array(name)
+        if variants:
+            assert not members      # not implemented
+            self.decl += gen_union(name, base, variants)
+        else:
+            self.decl += gen_struct(name, base, members)
+        self._gen_type_cleanup(name)

     def visit_alternate_type(self, name, info, variants):
         self._fwdecl += gen_fwd_object_or_array(name)
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 4f97781..be1bba7 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -319,10 +319,14 @@ class QAPISchemaGenVisitVisitor(QAPISchemaVisitor):
         self.defn = None
         self._btin = None

+    def _visit_filter(self, entity):
+        return isinstance(entity, QAPISchemaObjectType) and not entity.info
+
     def visit_begin(self, schema):
         self.decl = ''
         self.defn = ''
         self._btin = guardstart('QAPI_VISIT_BUILTIN')
+        return self._visit_filter    # ignore builtin types

     def visit_end(self):
         # To avoid header dependency hell, we always generate
@@ -349,13 +353,13 @@ class QAPISchemaGenVisitVisitor(QAPISchemaVisitor):
             self.defn += defn

     def visit_object_type(self, name, info, base, members, variants):
-        if info:
-            self.decl += gen_visit_decl(name)
-            if variants:
-                assert not members      # not implemented
-                self.defn += gen_visit_union(name, base, variants)
-            else:
-                self.defn += gen_visit_struct(name, base, members)
+        assert info
+        self.decl += gen_visit_decl(name)
+        if variants:
+            assert not members      # not implemented
+            self.defn += gen_visit_union(name, base, variants)
+        else:
+            self.defn += gen_visit_struct(name, base, members)

     def visit_alternate_type(self, name, info, variants):
         self.decl += gen_visit_decl(name)
diff --git a/scripts/qapi.py b/scripts/qapi.py
index 7761b4b..0fadc05 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -805,6 +805,8 @@ class QAPISchemaEntity(object):


 class QAPISchemaVisitor(object):
+    # To ignore certain entities, return a predicate function such
+    # that predicate(entity) returns True to ignore that entity
     def visit_begin(self, schema):
         pass

@@ -1306,7 +1308,7 @@ class QAPISchema(object):
     def visit(self, visitor):
         ignore = visitor.visit_begin(self)
         for name in sorted(self._entity_dict.keys()):
-            if not ignore or not isinstance(self._entity_dict[name], ignore):
+            if not ignore or not ignore(self._entity_dict[name]):
                 self._entity_dict[name].visit(visitor)
         visitor.visit_end()

-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 12/46] qapi: Track location that created an implicit type
  2015-09-29  8:02       ` Markus Armbruster
@ 2015-09-30 16:02         ` Eric Blake
  0 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-30 16:02 UTC (permalink / raw
  To: Markus Armbruster
  Cc: Michael Roth, marcandre.lureau, qemu-devel, ehabkost,
	DirtY.iCE.hu

[-- Attachment #1: Type: text/plain, Size: 2766 bytes --]

On 09/29/2015 02:02 AM, Markus Armbruster wrote:

>>>> While at it, we were always passing None as the location of
>>>> array types, making that parameter useless; sharing the
>>>> location (if any) of the underlying element type makes sense.
>>>
>>> The parameter is useless only because all array types are implicit.
>>> Once we change that, it won't be anymore.
>>
>> I guess it depends whether I pass in the existing info when creating the
>> array or determine the info when resolving the string name of the array
>> element during check() (it will ultimately be the same info either way,
>> it's just a question of which approach looks cleaner for getting the
>> information set).
> 
> The latter leaves info None until check().  Unhealthy state.
> 
> I suspect the appropriate info is readily available in all the places
> where we create array types, so let's just pass it to
> _make_array_type().

I just realized why I left it None until check(). We can have forward
references to arrays, as in:

{ 'struct':'A', 'data':{ 'list': ['B'] } }
{ 'struct':'B', 'data':{ 'value': 'int' } }

Normally, BList gets created at the same time as B (and so logically
shares the location of B), but with forward references, we have the
problem that _make_member() has no access to info (unless I plumb it
in), and even if it did, it would be the info for A, whereas my check()
methodology used the info for B.  That is, _make_array_type() does not
yet have ready access to info in all call sites.

On the other hand, we have the big TODO about whether pre-creating array
types is even necessary, and want to consider lazy array creation where
they are only instantiated if actually used by a member type or command
return value. I guess it's time for me to play with doing that cleanup
first, and then I'd be happy to have an array's info be the info of its
first instantiation, rather than the info of its element type. (And that
means that ['int'] would have an info, if an array of ints is used
anywhere, even though 'int' does not.)

> Back to serious.  If we have to work for the assertion, we should
> consider the assertion's value: how likely are the actual mistakes it
> can catch?
> 
> Can legitimate errors be reported for built-in types?

I don't think so (and in fact have assertions along those lines) - we
can only report a semantic error about something in the user's QAPI
file, but their file didn't define the builtin types, so I don't see how
we would ever want to report a semantic error at the location tracked by
a builtin type.  So we should be safe if builtin types use info=None.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [PATCH v5 13/46] qapi: Track owner of each object member
  2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 13/46] qapi: Track owner of each object member Eric Blake
@ 2015-09-30 16:06   ` Eric Blake
  0 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-09-30 16:06 UTC (permalink / raw
  To: qemu-devel; +Cc: marcandre.lureau, ehabkost, Michael Roth, armbru, DirtY.iCE.hu

[-- Attachment #1: Type: text/plain, Size: 874 bytes --]

On 09/21/2015 03:57 PM, Eric Blake wrote:

> 
> Where implicit types are involved, the code intentionally tries
> to pick the name of the owner of that implicit type, rather than
> the type name itself (a user reading the error message should be
> able to grep for the problem in their original file, but will not
> be able to locate a generated implicit name).  For simple unions,
> I chose not to pass the union name to the variants constructor;
> but this should be okay because in practice nothing should ever
> conflict with the implicit 'type' of a simple union.

This last sentence is false; I later discovered that we can indeed have
collisions with a branch named 'type' (see v6 subset A 6/16). So I'll
probably have to adjust this a bit.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [RFC PATCH] qapi: Use callback to determine visit filtering
  2015-09-30  4:13         ` [Qemu-devel] [RFC PATCH] qapi: Use callback to determine visit filtering Eric Blake
@ 2015-10-01  6:12           ` Markus Armbruster
  2015-10-01 14:09             ` Eric Blake
  0 siblings, 1 reply; 108+ messages in thread
From: Markus Armbruster @ 2015-10-01  6:12 UTC (permalink / raw
  To: Eric Blake; +Cc: qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> Previously, qapi-types and qapi-visit filtered an object visit
> to bypass implicit types by inspecting whether 'info' was passed
> in (since implicit objects do not [yet] have associated info);
> meanwhile qapi-introspect filtered by returning a python type
> from visit_begin() in order to exclude all type entities on the
> first pass.  Rather than keeping these ad hoc methods, rework
> the contract of visit_begin() to state that it may return a
> predicate function that returns True to ignore a given entity,
> and use that mechanism to simplify all three visitors.
>
> Suggested-by: Markus Armbruster <armbru@redhat.com>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>
>>>   Another option is generalizing QAPISchema's filter.  How?
>
>> We can always add an indirection: instead of parameterizing a fixed
>> predicate (ignore and isinstance(entity, ignore)) with a type ignore, we
>> could pass a predicate, i.e. a function mapping entity to bool.
>
> Like this?  Generates the same code before and after the
> patch.  I'm open to suggestions on any way to make it more
> idiomatic python, althouth it at least passed pep8.  In
> particular, I'm wondering if the predicate should have its
> sense reversed (pass False to skip, True to visit).
>
> I'll probably drop the 'assert info' lines in the two
> visit_object_type() calls (I put it there to make sure the
> predicate was working).

Yes, please.

>  scripts/qapi-introspect.py |  5 ++++-
>  scripts/qapi-types.py      | 20 ++++++++++++--------
>  scripts/qapi-visit.py      | 18 +++++++++++-------
>  scripts/qapi.py            |  4 +++-
>  4 files changed, 30 insertions(+), 17 deletions(-)
>
> diff --git a/scripts/qapi-introspect.py b/scripts/qapi-introspect.py
> index 7d39320..37b4306 100644
> --- a/scripts/qapi-introspect.py
> +++ b/scripts/qapi-introspect.py
> @@ -54,7 +54,7 @@ class QAPISchemaGenIntrospectVisitor(QAPISchemaVisitor):
>          self._jsons = []
>          self._used_types = []
>          self._name_map = {}
> -        return QAPISchemaType   # don't visit types for now
> +        return self._visit_filter   # don't visit types for now
>
>      def visit_end(self):
>          # visit the types that are actually used
> @@ -82,6 +82,9 @@ const char %(c_name)s[] = %(c_string)s;
>          self._used_types = None
>          self._name_map = None
>
> +    def _visit_filter(self, entity):
> +        return isinstance(entity, QAPISchemaType)
> +
>      def _name(self, name):
>          if self._unmask:
>              return name
> diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
> index d405f8d..fbe74e1 100644
> --- a/scripts/qapi-types.py
> +++ b/scripts/qapi-types.py
> @@ -213,12 +213,16 @@ class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
>          self._fwdefn = None
>          self._btin = None
>
> +    def _visit_filter(self, entity):
> +        return isinstance(entity, QAPISchemaObjectType) and not entity.info
> +
>      def visit_begin(self, schema):
>          self.decl = ''
>          self.defn = ''
>          self._fwdecl = ''
>          self._fwdefn = ''
>          self._btin = guardstart('QAPI_TYPES_BUILTIN')
> +        return self._visit_filter    # ignore builtin types
>
>      def visit_end(self):
>          self.decl = self._fwdecl + self.decl
> @@ -254,14 +258,14 @@ class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
>              self._gen_type_cleanup(name)
>
>      def visit_object_type(self, name, info, base, members, variants):
> -        if info:
> -            self._fwdecl += gen_fwd_object_or_array(name)
> -            if variants:
> -                assert not members      # not implemented
> -                self.decl += gen_union(name, base, variants)
> -            else:
> -                self.decl += gen_struct(name, base, members)
> -            self._gen_type_cleanup(name)
> +        assert info
> +        self._fwdecl += gen_fwd_object_or_array(name)
> +        if variants:
> +            assert not members      # not implemented
> +            self.decl += gen_union(name, base, variants)
> +        else:
> +            self.decl += gen_struct(name, base, members)
> +        self._gen_type_cleanup(name)
>
>      def visit_alternate_type(self, name, info, variants):
>          self._fwdecl += gen_fwd_object_or_array(name)
> diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
> index 4f97781..be1bba7 100644
> --- a/scripts/qapi-visit.py
> +++ b/scripts/qapi-visit.py
> @@ -319,10 +319,14 @@ class QAPISchemaGenVisitVisitor(QAPISchemaVisitor):
>          self.defn = None
>          self._btin = None
>
> +    def _visit_filter(self, entity):
> +        return isinstance(entity, QAPISchemaObjectType) and not entity.info
> +
>      def visit_begin(self, schema):
>          self.decl = ''
>          self.defn = ''
>          self._btin = guardstart('QAPI_VISIT_BUILTIN')
> +        return self._visit_filter    # ignore builtin types
>
>      def visit_end(self):
>          # To avoid header dependency hell, we always generate
> @@ -349,13 +353,13 @@ class QAPISchemaGenVisitVisitor(QAPISchemaVisitor):
>              self.defn += defn
>
>      def visit_object_type(self, name, info, base, members, variants):
> -        if info:
> -            self.decl += gen_visit_decl(name)
> -            if variants:
> -                assert not members      # not implemented
> -                self.defn += gen_visit_union(name, base, variants)
> -            else:
> -                self.defn += gen_visit_struct(name, base, members)
> +        assert info
> +        self.decl += gen_visit_decl(name)
> +        if variants:
> +            assert not members      # not implemented
> +            self.defn += gen_visit_union(name, base, variants)
> +        else:
> +            self.defn += gen_visit_struct(name, base, members)
>
>      def visit_alternate_type(self, name, info, variants):
>          self.decl += gen_visit_decl(name)
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 7761b4b..0fadc05 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -805,6 +805,8 @@ class QAPISchemaEntity(object):
>
>
>  class QAPISchemaVisitor(object):
> +    # To ignore certain entities, return a predicate function such
> +    # that predicate(entity) returns True to ignore that entity
>      def visit_begin(self, schema):
>          pass
>
> @@ -1306,7 +1308,7 @@ class QAPISchema(object):
>      def visit(self, visitor):
>          ignore = visitor.visit_begin(self)
>          for name in sorted(self._entity_dict.keys()):
> -            if not ignore or not isinstance(self._entity_dict[name], ignore):
> +            if not ignore or not ignore(self._entity_dict[name]):
>                  self._entity_dict[name].visit(visitor)
>          visitor.visit_end()

I think this turned out rather nicely.

Can we go one step further?  Unconditionally call visitor.visit_filter()
here, define the pass-everything filter in QAPISchemaVisitor, override
it as needed.

Name it visit_filter_out() to make the sense of the return value
obvious?

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [RFC PATCH] qapi: Use callback to determine visit filtering
  2015-10-01  6:12           ` Markus Armbruster
@ 2015-10-01 14:09             ` Eric Blake
  0 siblings, 0 replies; 108+ messages in thread
From: Eric Blake @ 2015-10-01 14:09 UTC (permalink / raw
  To: Markus Armbruster; +Cc: qemu-devel, Michael Roth

[-- Attachment #1: Type: text/plain, Size: 1493 bytes --]

On 10/01/2015 12:12 AM, Markus Armbruster wrote:

>>
>>> We can always add an indirection: instead of parameterizing a fixed
>>> predicate (ignore and isinstance(entity, ignore)) with a type ignore, we
>>> could pass a predicate, i.e. a function mapping entity to bool.
>>
>> Like this?  Generates the same code before and after the
>> patch.  I'm open to suggestions on any way to make it more
>> idiomatic python, althouth it at least passed pep8.  In
>> particular, I'm wondering if the predicate should have its
>> sense reversed (pass False to skip, True to visit).
>>
>> I'll probably drop the 'assert info' lines in the two
>> visit_object_type() calls (I put it there to make sure the
>> predicate was working).
> 
> Yes, please.

Returning True to visit and False to skip was easier to rationalize
about, so I've made that change in my local tree.


> 
> I think this turned out rather nicely.
> 
> Can we go one step further?  Unconditionally call visitor.visit_filter()
> here, define the pass-everything filter in QAPISchemaVisitor, override
> it as needed.
> 
> Name it visit_filter_out() to make the sense of the return value
> obvious?

Oh, nice idea. Then we don't even have to return it during visit_begin()
- we just blindly call it.  Will work that into my local tree, and it
will be ready when I post (the next subset) of v6.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* [Qemu-devel] [RFC PATCH] qapi: split visit_end_struct() into pieces
  2015-09-28  9:14         ` Markus Armbruster
@ 2015-10-06 21:10           ` Eric Blake
  2015-10-07 12:00             ` Markus Armbruster
  0 siblings, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-10-06 21:10 UTC (permalink / raw
  To: qemu-devel
  Cc: Michael S. Tsirkin, Michael Roth, armbru, Paolo Bonzini,
	Luiz Capitulino, Andreas Färber

We want to call the various visit_end_*() functions unconditionally,
so that visitors can release resources tied up since the matching
visit_start_*().  But we also have a requirement for detecting when
an input visitor did not consume everything, so the code allowed
visit_end_*() to set an error.  This led to some awkward coding,
since safely setting the error, even if an earlier one had
occurred, while still favoring the earliest error, requires either
multiple error_propagate() calls (and an understanding that unlike
error_setg(), it is safe to propagate onto an already-set error),
or else ternaries such as foo(..., err ? NULL : &err) to ignore
secondary errors if an earlier error is already known.

Splitting the functionality allows slightly cleaner idioms. Now,
clients must call visit_check_struct() if all earlier actions
have been successful for one last chance at flagging excess
input, while visit_end_struct() is unconditional and does not
set any further errors.

As it is an API change, everything must be changed in one commit
(there is no way to break this into smaller pieces): both the
generated code and other existing clients, as well as the various
visitor backends.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
TODO: There is some redundancy in the testsuite, with multiple
files hand-writing their own visit_type_TestStruct() instead
of letting the qapi generators create it.  Should this be
consolidated as a separate patch?

Probably won't apply directly to any published commit, so much
as me posting a part of my worktree early to get feedback on
whether the idea even makes sense, given how large the results
end up being.

v6: new patch, based on discussion of v5 7/46:

On 09/28/2015 03:14 AM, Markus Armbruster wrote:

>> One other potential alternative:  What if we split visit_end_struct()
>> into two visitor functions, one that checks for success, and the other
>> that is called unconditionally to clean up resources.

> I think this split could help with writing safe code: in
> visit_check_struct() you can rely on "no error so far", as usual.  In
> visit_end_struct(), you can't, but it should be a pure cleanup function,
> where that's quite normal.
>
> Looks like we're getting drawn into visitor contract territory again.
>

---
 hmp.c                              |  3 ++-
 hw/virtio/virtio-balloon.c         | 14 ++++++++------
 include/qapi/visitor-impl.h        |  8 +++++---
 include/qapi/visitor.h             | 24 ++++++++++++++++++------
 qapi/opts-visitor.c                | 17 +++++++++++++++--
 qapi/qapi-dealloc-visitor.c        |  6 +++---
 qapi/qapi-visit-core.c             | 19 +++++++++++++------
 qapi/qmp-input-visitor.c           | 36 +++++++++++++++++++++++++-----------
 qapi/qmp-output-visitor.c          |  4 ++--
 qapi/string-input-visitor.c        |  2 +-
 qapi/string-output-visitor.c       |  2 +-
 qom/object.c                       |  5 ++---
 scripts/qapi-event.py              |  5 +++--
 scripts/qapi-visit.py              | 24 ++++++++++++------------
 tests/test-qmp-input-strict.c      |  9 +++++----
 tests/test-qmp-input-visitor.c     |  9 +++++----
 tests/test-qmp-output-visitor.c    | 23 ++++++++++++++++-------
 tests/test-visitor-serialization.c |  9 +++++----
 vl.c                               | 12 +++++++-----
 19 files changed, 148 insertions(+), 83 deletions(-)

diff --git a/hmp.c b/hmp.c
index 5048eee..b7a42dc 100644
--- a/hmp.c
+++ b/hmp.c
@@ -1658,8 +1658,9 @@ void hmp_object_add(Monitor *mon, const QDict *qdict)

     object_add(type, id, pdict, opts_get_visitor(ov), &err);

+    visit_check_struct(opts_get_visitor(ov), &err_end);
 out_end:
-    visit_end_struct(opts_get_visitor(ov), &err_end);
+    visit_end_struct(opts_get_visitor(ov));
     if (!err && err_end) {
         qmp_object_del(id, NULL);
     }
diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c
index c419b17..48867c4 100644
--- a/hw/virtio/virtio-balloon.c
+++ b/hw/virtio/virtio-balloon.c
@@ -132,14 +132,16 @@ static void balloon_stats_get_all(Object *obj, struct Visitor *v,
         visit_type_int64(v, (int64_t *) &s->stats[i], balloon_stat_names[i],
                          &err);
     }
-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_struct(v, &err);
+    if (!err) {
+        visit_check_struct(v, &err);
+    }
+    visit_end_struct(v);

 out_end:
-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_struct(v, &err);
+    if (!err) {
+        visit_check_struct(v, &err);
+    }
+    visit_end_struct(v);
 out:
     error_propagate(errp, err);
 }
diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
index 090b19b..c53d4d3 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -28,21 +28,23 @@ struct Visitor
      * currently visit structs). */
     void (*start_struct)(Visitor *v, void **obj, const char *kind,
                          const char *name, size_t size, Error **errp);
+    /* May be NULL; most useful for input visitors. */
+    void (*check_struct)(Visitor *v, Error **errp);
     /* Must be provided if start_struct is present. */
-    void (*end_struct)(Visitor *v, Error **errp);
+    void (*end_struct)(Visitor *v);

     /* May be NULL; most useful for input visitors. */
     void (*start_implicit_struct)(Visitor *v, void **obj, size_t size,
                                   Error **errp);
     /* May be NULL */
-    void (*end_implicit_struct)(Visitor *v, Error **errp);
+    void (*end_implicit_struct)(Visitor *v);

     /* Must be set */
     void (*start_list)(Visitor *v, const char *name, Error **errp);
     /* Must be set */
     GenericList *(*next_list)(Visitor *v, GenericList **list, Error **errp);
     /* Must be set */
-    void (*end_list)(Visitor *v, Error **errp);
+    void (*end_list)(Visitor *v);

     /* Must be set, although the helpers input_type_enum() and
      * output_type_enum() can be used.  */
diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index 6534cca..708f55c 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -56,11 +56,19 @@ typedef struct GenericList
 void visit_start_struct(Visitor *v, void **obj, const char *kind,
                         const char *name, size_t size, Error **errp);
 /**
+ * Check whether completing a struct is safe.
+ * Should be called prior to visit_end_struct() if all other intermediate
+ * visit steps were successful, to allow the caller one last chance to
+ * report errors such as remaining data that was not consumed by the visit.
+ */
+void visit_check_struct(Visitor *v, Error **errp);
+/**
  * Complete a struct visit started earlier.
  * Must be called after any successful use of visit_start_struct(),
- * even if intermediate processing was skipped due to errors.
+ * even if intermediate processing was skipped due to errors, to allow
+ * the backend to release any resources.
  */
-void visit_end_struct(Visitor *v, Error **errp);
+void visit_end_struct(Visitor *v);

 /**
  * Prepare to visit an implicit struct.
@@ -76,9 +84,12 @@ void visit_start_implicit_struct(Visitor *v, void **obj, size_t size,
 /**
  * Complete an implicit struct visit started earlier.
  * Must be called after any successful use of visit_start_implicit_struct(),
- * even if intermediate processing was skipped due to errors.
+ * even if intermediate processing was skipped due to errors.  Unlike
+ * visit_end_struct(), there is no need for a prior check call (since
+ * an implicit object is a subset of larger object, checking before ending
+ * the overall struct is sufficient).
  */
-void visit_end_implicit_struct(Visitor *v, Error **errp);
+void visit_end_implicit_struct(Visitor *v);

 /**
  * Prepare to visit an array tied to an object key @name.
@@ -101,9 +112,10 @@ GenericList *visit_next_list(Visitor *v, GenericList **list, Error **errp);
 /**
  * Complete the list started earlier.
  * Must be called after any successful use of visit_start_list(),
- * even if intermediate processing was skipped due to errors.
+ * even if intermediate processing was skipped due to errors, to allow
+ * the backend to release any resources.
  */
-void visit_end_list(Visitor *v, Error **errp);
+void visit_end_list(Visitor *v);

 /**
  * Check if an optional member @name of an object needs visiting.
diff --git a/qapi/opts-visitor.c b/qapi/opts-visitor.c
index cd10392..39dc431 100644
--- a/qapi/opts-visitor.c
+++ b/qapi/opts-visitor.c
@@ -158,7 +158,7 @@ ghr_true(gpointer ign_key, gpointer ign_value, gpointer ign_user_data)


 static void
-opts_end_struct(Visitor *v, Error **errp)
+opts_check_struct(Visitor *v, Error **errp)
 {
     OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
     GQueue *any;
@@ -175,6 +175,18 @@ opts_end_struct(Visitor *v, Error **errp)
         first = g_queue_peek_head(any);
         error_setg(errp, QERR_INVALID_PARAMETER, first->name);
     }
+}
+
+
+static void
+opts_end_struct(Visitor *v)
+{
+    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
+
+    if (--ov->depth > 0) {
+        return;
+    }
+
     g_hash_table_destroy(ov->unprocessed_opts);
     ov->unprocessed_opts = NULL;
     if (ov->fake_id_opt) {
@@ -263,7 +275,7 @@ opts_next_list(Visitor *v, GenericList **list, Error **errp)


 static void
-opts_end_list(Visitor *v, Error **errp)
+opts_end_list(Visitor *v)
 {
     OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);

@@ -506,6 +518,7 @@ opts_visitor_new(const QemuOpts *opts)
     ov = g_malloc0(sizeof *ov);

     ov->visitor.start_struct = &opts_start_struct;
+    ov->visitor.check_struct = &opts_check_struct;
     ov->visitor.end_struct   = &opts_end_struct;

     ov->visitor.start_list = &opts_start_list;
diff --git a/qapi/qapi-dealloc-visitor.c b/qapi/qapi-dealloc-visitor.c
index 737deab..a2139dd 100644
--- a/qapi/qapi-dealloc-visitor.c
+++ b/qapi/qapi-dealloc-visitor.c
@@ -67,7 +67,7 @@ static void qapi_dealloc_start_struct(Visitor *v, void **obj, const char *kind,
     qapi_dealloc_push(qov, obj);
 }

-static void qapi_dealloc_end_struct(Visitor *v, Error **errp)
+static void qapi_dealloc_end_struct(Visitor *v)
 {
     QapiDeallocVisitor *qov = to_qov(v);
     void **obj = qapi_dealloc_pop(qov);
@@ -85,7 +85,7 @@ static void qapi_dealloc_start_implicit_struct(Visitor *v,
     qapi_dealloc_push(qov, obj);
 }

-static void qapi_dealloc_end_implicit_struct(Visitor *v, Error **errp)
+static void qapi_dealloc_end_implicit_struct(Visitor *v)
 {
     QapiDeallocVisitor *qov = to_qov(v);
     void **obj = qapi_dealloc_pop(qov);
@@ -121,7 +121,7 @@ static GenericList *qapi_dealloc_next_list(Visitor *v, GenericList **listp,
     return NULL;
 }

-static void qapi_dealloc_end_list(Visitor *v, Error **errp)
+static void qapi_dealloc_end_list(Visitor *v)
 {
     QapiDeallocVisitor *qov = to_qov(v);
     void *obj = qapi_dealloc_pop(qov);
diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index cbf7780..82d54af 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -23,9 +23,16 @@ void visit_start_struct(Visitor *v, void **obj, const char *kind,
     v->start_struct(v, obj, kind, name, size, errp);
 }

-void visit_end_struct(Visitor *v, Error **errp)
+void visit_check_struct(Visitor *v, Error **errp)
 {
-    v->end_struct(v, errp);
+    if (v->check_struct) {
+        v->check_struct(v, errp);
+    }
+}
+
+void visit_end_struct(Visitor *v)
+{
+    v->end_struct(v);
 }

 void visit_start_implicit_struct(Visitor *v, void **obj, size_t size,
@@ -36,10 +43,10 @@ void visit_start_implicit_struct(Visitor *v, void **obj, size_t size,
     }
 }

-void visit_end_implicit_struct(Visitor *v, Error **errp)
+void visit_end_implicit_struct(Visitor *v)
 {
     if (v->end_implicit_struct) {
-        v->end_implicit_struct(v, errp);
+        v->end_implicit_struct(v);
     }
 }

@@ -53,9 +60,9 @@ GenericList *visit_next_list(Visitor *v, GenericList **list, Error **errp)
     return v->next_list(v, list, errp);
 }

-void visit_end_list(Visitor *v, Error **errp)
+void visit_end_list(Visitor *v)
 {
-    v->end_list(v, errp);
+    v->end_list(v);
 }

 bool visit_start_union(Visitor *v, bool data_present, Error **errp)
diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
index 5310db5..f10f74f 100644
--- a/qapi/qmp-input-visitor.c
+++ b/qapi/qmp-input-visitor.c
@@ -88,14 +88,14 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
     qiv->nb_stack++;
 }

-/** Only for qmp_input_pop. */
+/** Only for qmp_input_check. */
 static gboolean always_true(gpointer key, gpointer val, gpointer user_pkey)
 {
     *(const char **)user_pkey = (const char *)key;
     return TRUE;
 }

-static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
+static void qmp_input_check(QmpInputVisitor *qiv, Error **errp)
 {
     assert(qiv->nb_stack > 0);

@@ -107,6 +107,17 @@ static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
                 g_hash_table_find(top_ht, always_true, &key);
                 error_setg(errp, QERR_QMP_EXTRA_MEMBER, key);
             }
+        }
+    }
+}
+
+static void qmp_input_pop(QmpInputVisitor *qiv)
+{
+    assert(qiv->nb_stack > 0);
+
+    if (qiv->strict) {
+        GHashTable * const top_ht = qiv->stack[qiv->nb_stack - 1].h;
+        if (top_ht) {
             g_hash_table_unref(top_ht);
         }
     }
@@ -138,11 +149,18 @@ static void qmp_input_start_struct(Visitor *v, void **obj, const char *kind,
     }
 }

-static void qmp_input_end_struct(Visitor *v, Error **errp)
+static void qmp_input_check_struct(Visitor *v, Error **errp)
 {
     QmpInputVisitor *qiv = to_qiv(v);

-    qmp_input_pop(qiv, errp);
+    qmp_input_check(qiv, errp);
+}
+
+static void qmp_input_end_struct(Visitor *v)
+{
+    QmpInputVisitor *qiv = to_qiv(v);
+
+    qmp_input_pop(qiv);
 }

 static void qmp_input_start_implicit_struct(Visitor *v, void **obj,
@@ -153,10 +171,6 @@ static void qmp_input_start_implicit_struct(Visitor *v, void **obj,
     }
 }

-static void qmp_input_end_implicit_struct(Visitor *v, Error **errp)
-{
-}
-
 static void qmp_input_start_list(Visitor *v, const char *name, Error **errp)
 {
     QmpInputVisitor *qiv = to_qiv(v);
@@ -201,11 +215,11 @@ static GenericList *qmp_input_next_list(Visitor *v, GenericList **list,
     return entry;
 }

-static void qmp_input_end_list(Visitor *v, Error **errp)
+static void qmp_input_end_list(Visitor *v)
 {
     QmpInputVisitor *qiv = to_qiv(v);

-    qmp_input_pop(qiv, errp);
+    qmp_input_pop(qiv);
 }

 static void qmp_input_get_next_type(Visitor *v, qtype_code *type,
@@ -332,9 +346,9 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
     v = g_malloc0(sizeof(*v));

     v->visitor.start_struct = qmp_input_start_struct;
+    v->visitor.check_struct = qmp_input_check_struct;
     v->visitor.end_struct = qmp_input_end_struct;
     v->visitor.start_implicit_struct = qmp_input_start_implicit_struct;
-    v->visitor.end_implicit_struct = qmp_input_end_implicit_struct;
     v->visitor.start_list = qmp_input_start_list;
     v->visitor.next_list = qmp_input_next_list;
     v->visitor.end_list = qmp_input_end_list;
diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c
index f891e72..b9fba7b 100644
--- a/qapi/qmp-output-visitor.c
+++ b/qapi/qmp-output-visitor.c
@@ -122,7 +122,7 @@ static void qmp_output_start_struct(Visitor *v, void **obj, const char *kind,
     qmp_output_push(qov, dict);
 }

-static void qmp_output_end_struct(Visitor *v, Error **errp)
+static void qmp_output_end_struct(Visitor *v)
 {
     QmpOutputVisitor *qov = to_qov(v);
     qmp_output_pop(qov);
@@ -153,7 +153,7 @@ static GenericList *qmp_output_next_list(Visitor *v, GenericList **listp,
     return list ? list->next : NULL;
 }

-static void qmp_output_end_list(Visitor *v, Error **errp)
+static void qmp_output_end_list(Visitor *v)
 {
     QmpOutputVisitor *qov = to_qov(v);
     qmp_output_pop(qov);
diff --git a/qapi/string-input-visitor.c b/qapi/string-input-visitor.c
index bbd6a54..97e00c8 100644
--- a/qapi/string-input-visitor.c
+++ b/qapi/string-input-visitor.c
@@ -173,7 +173,7 @@ next_list(Visitor *v, GenericList **list, Error **errp)
 }

 static void
-end_list(Visitor *v, Error **errp)
+end_list(Visitor *v)
 {
     StringInputVisitor *siv = DO_UPCAST(StringInputVisitor, visitor, v);
     siv->head = true;
diff --git a/qapi/string-output-visitor.c b/qapi/string-output-visitor.c
index b86ce2c..b269f39 100644
--- a/qapi/string-output-visitor.c
+++ b/qapi/string-output-visitor.c
@@ -290,7 +290,7 @@ next_list(Visitor *v, GenericList **list, Error **errp)
 }

 static void
-end_list(Visitor *v, Error **errp)
+end_list(Visitor *v)
 {
     StringOutputVisitor *sov = DO_UPCAST(StringOutputVisitor, visitor, v);

diff --git a/qom/object.c b/qom/object.c
index 11cd86b..6d92e01 100644
--- a/qom/object.c
+++ b/qom/object.c
@@ -1844,10 +1844,9 @@ static void property_get_tm(Object *obj, Visitor *v, void *opaque,
     if (err) {
         goto out_end;
     }
+    visit_check_struct(v, &err);
 out_end:
-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_struct(v, errp);
+    visit_end_struct(v);
 out:
     error_propagate(errp, err);

diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py
index 720486f..250e753 100644
--- a/scripts/qapi-event.py
+++ b/scripts/qapi-event.py
@@ -73,13 +73,14 @@ def gen_event_send(name, arg_type):
         ret += gen_err_check()
         ret += gen_visit_fields(arg_type.members, need_cast=True)
         ret += mcgen('''
-    visit_end_struct(v, &err);
+    visit_check_struct(v, &err);
     if (err) {
         goto out;
     }
+    visit_end_struct(v);

     obj = qmp_output_get_qobject(qov);
-    g_assert(obj != NULL);
+    g_assert(obj);

     qdict_put_obj(qmp, "data", obj);
 ''')
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 1ac5350..c5a32cf 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -52,7 +52,7 @@ static void visit_type_implicit_%(c_type)s(Visitor *v, %(c_type)s **obj, Error *
     visit_start_implicit_struct(v, (void **)obj, sizeof(%(c_type)s), &err);
     if (!err) {
         visit_type_%(c_type)s_fields(v, obj, errp);
-        visit_end_implicit_struct(v, &err);
+        visit_end_implicit_struct(v);
     }
     error_propagate(errp, err);
 }
@@ -115,9 +115,12 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
     visit_start_struct(v, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err);
     if (!err) {
         if (*obj) {
-            visit_type_%(c_name)s_fields(v, obj, errp);
+            visit_type_%(c_name)s_fields(v, obj, &err);
         }
-        visit_end_struct(v, &err);
+        if (!err) {
+            visit_check_struct(v, &err);
+        }
+        visit_end_struct(v);
     }
     error_propagate(errp, err);
 }
@@ -147,9 +150,7 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
         visit_type_%(c_elt_type)s(v, &native_i->value, NULL, &err);
     }

-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_list(v, &err);
+    visit_end_list(v);
 out:
     error_propagate(errp, err);
 }
@@ -208,9 +209,7 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
                    "%(name)s");
     }
 out_obj:
-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_implicit_struct(v, &err);
+    visit_end_implicit_struct(v);
 out:
     error_propagate(errp, err);
 }
@@ -297,13 +296,14 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
     default:
         abort();
     }
+    if (!err) {
+        visit_check_struct(v, &err);
+    }
 out_obj:
     error_propagate(errp, err);
     err = NULL;
     visit_end_union(v, !!(*obj)->data, &err);
-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_struct(v, &err);
+    visit_end_struct(v);
 out:
     error_propagate(errp, err);
 }
diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c
index 1c13c8f..da9dafd 100644
--- a/tests/test-qmp-input-strict.c
+++ b/tests/test-qmp-input-strict.c
@@ -118,11 +118,12 @@ static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
         goto out_end;
     }
     visit_type_str(v, &(*obj)->string, "string", &err);
-
+    if (err) {
+        goto out_end;
+    }
+    visit_check_struct(v, &err);
 out_end:
-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_struct(v, &err);
+    visit_end_struct(v);
 out:
     error_propagate(errp, err);
 }
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index ef5871b..38b1436 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -200,11 +200,12 @@ static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
         goto out_end;
     }
     visit_type_str(v, &(*obj)->string, "string", &err);
-
+    if (err) {
+        goto out_end;
+    }
+    visit_check_struct(v, &err);
 out_end:
-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_struct(v, &err);
+    visit_end_struct(v);
 out:
     error_propagate(errp, err);
 }
diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c
index 11996d0..f836588 100644
--- a/tests/test-qmp-output-visitor.c
+++ b/tests/test-qmp-output-visitor.c
@@ -194,10 +194,11 @@ static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
     }
     visit_type_str(v, &(*obj)->string, "string", &err);

+    if (!err) {
+        visit_check_struct(v, &err);
+    }
 out_end:
-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_struct(v, &err);
+    visit_end_struct(v);
 out:
     error_propagate(errp, err);
 }
@@ -329,15 +330,23 @@ static void visit_type_TestStructList(Visitor *v, TestStructList **obj,
                                       const char *name, Error **errp)
 {
     GenericList *i, **head = (GenericList **)obj;
+    Error *err = NULL;

-    visit_start_list(v, name, errp);
+    visit_start_list(v, name, &err);
+    if (err) {
+        goto out;
+    }

-    for (*head = i = visit_next_list(v, head, errp); i; i = visit_next_list(v, &i, errp)) {
+    for (*head = i = visit_next_list(v, head, &err);
+         !err && i;
+         i = visit_next_list(v, &i, &err)) {
         TestStructList *native_i = (TestStructList *)i;
-        visit_type_TestStruct(v, &native_i->value, NULL, errp);
+        visit_type_TestStruct(v, &native_i->value, NULL, &err);
     }

-    visit_end_list(v, errp);
+    visit_end_list(v);
+out:
+    error_propagate(errp, err);
 }

 static void test_visitor_out_list(TestOutputVisitorData *data,
diff --git a/tests/test-visitor-serialization.c b/tests/test-visitor-serialization.c
index fa86cae..dd50d69 100644
--- a/tests/test-visitor-serialization.c
+++ b/tests/test-visitor-serialization.c
@@ -212,11 +212,12 @@ static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
         goto out_end;
     }
     visit_type_str(v, &(*obj)->string, "string", &err);
-
+    if (err) {
+        goto out_end;
+    }
+    visit_check_struct(v, &err);
 out_end:
-    error_propagate(errp, err);
-    err = NULL;
-    visit_end_struct(v, &err);
+    visit_end_struct(v);
 out:
     error_propagate(errp, err);
 }
diff --git a/vl.c b/vl.c
index 8d1846c..8b944aa 100644
--- a/vl.c
+++ b/vl.c
@@ -2796,23 +2796,25 @@ static int object_create(void *opaque, QemuOpts *opts, Error **errp)
     qdict_del(pdict, "qom-type");
     visit_type_str(opts_get_visitor(ov), &type, "qom-type", &err);
     if (err) {
-        goto out;
+        goto obj_out;
     }
     if (!type_predicate(type)) {
-        goto out;
+        goto obj_out;
     }

     qdict_del(pdict, "id");
     visit_type_str(opts_get_visitor(ov), &id, "id", &err);
     if (err) {
-        goto out;
+        goto obj_out;
     }

     object_add(type, id, pdict, opts_get_visitor(ov), &err);
     if (err) {
-        goto out;
+        goto obj_out;
     }
-    visit_end_struct(opts_get_visitor(ov), &err);
+    visit_check_struct(opts_get_visitor(ov), &err);
+obj_out:
+    visit_end_struct(opts_get_visitor(ov));
     if (err) {
         qmp_object_del(id, NULL);
     }
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [RFC PATCH] qapi: split visit_end_struct() into pieces
  2015-10-06 21:10           ` [Qemu-devel] [RFC PATCH] qapi: split visit_end_struct() into pieces Eric Blake
@ 2015-10-07 12:00             ` Markus Armbruster
  2015-10-07 13:08               ` Markus Armbruster
  2015-10-07 14:57               ` Eric Blake
  0 siblings, 2 replies; 108+ messages in thread
From: Markus Armbruster @ 2015-10-07 12:00 UTC (permalink / raw
  To: Eric Blake
  Cc: Michael S. Tsirkin, qemu-devel, Michael Roth, Luiz Capitulino,
	Paolo Bonzini, Andreas Färber

Eric Blake <eblake@redhat.com> writes:

> We want to call the various visit_end_*() functions unconditionally,
> so that visitors can release resources tied up since the matching
> visit_start_*().  But we also have a requirement for detecting when
> an input visitor did not consume everything, so the code allowed
> visit_end_*() to set an error.  This led to some awkward coding,
> since safely setting the error, even if an earlier one had
> occurred, while still favoring the earliest error, requires either
> multiple error_propagate() calls (and an understanding that unlike
> error_setg(), it is safe to propagate onto an already-set error),
> or else ternaries such as foo(..., err ? NULL : &err) to ignore
> secondary errors if an earlier error is already known.
>
> Splitting the functionality allows slightly cleaner idioms. Now,
> clients must call visit_check_struct() if all earlier actions
> have been successful for one last chance at flagging excess
> input, while visit_end_struct() is unconditional and does not
> set any further errors.
>
> As it is an API change, everything must be changed in one commit
> (there is no way to break this into smaller pieces): both the
> generated code and other existing clients, as well as the various
> visitor backends.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
> TODO: There is some redundancy in the testsuite, with multiple
> files hand-writing their own visit_type_TestStruct() instead
> of letting the qapi generators create it.  Should this be
> consolidated as a separate patch?

If generating makes sense, it should be a separate patch.  Whether it
makes sense I can't say until I see the patch.

> Probably won't apply directly to any published commit, so much
> as me posting a part of my worktree early to get feedback on
> whether the idea even makes sense, given how large the results
> end up being.
>
> v6: new patch, based on discussion of v5 7/46:
>
> On 09/28/2015 03:14 AM, Markus Armbruster wrote:
>
>>> One other potential alternative:  What if we split visit_end_struct()
>>> into two visitor functions, one that checks for success, and the other
>>> that is called unconditionally to clean up resources.
>
>> I think this split could help with writing safe code: in
>> visit_check_struct() you can rely on "no error so far", as usual.  In
>> visit_end_struct(), you can't, but it should be a pure cleanup function,
>> where that's quite normal.
>>
>> Looks like we're getting drawn into visitor contract territory again.
>>
>
> ---
>  hmp.c                              |  3 ++-
>  hw/virtio/virtio-balloon.c         | 14 ++++++++------
>  include/qapi/visitor-impl.h        |  8 +++++---
>  include/qapi/visitor.h             | 24 ++++++++++++++++++------
>  qapi/opts-visitor.c                | 17 +++++++++++++++--
>  qapi/qapi-dealloc-visitor.c        |  6 +++---
>  qapi/qapi-visit-core.c             | 19 +++++++++++++------
>  qapi/qmp-input-visitor.c           | 36 +++++++++++++++++++++++++-----------
>  qapi/qmp-output-visitor.c          |  4 ++--
>  qapi/string-input-visitor.c        |  2 +-
>  qapi/string-output-visitor.c       |  2 +-
>  qom/object.c                       |  5 ++---
>  scripts/qapi-event.py              |  5 +++--
>  scripts/qapi-visit.py              | 24 ++++++++++++------------
>  tests/test-qmp-input-strict.c      |  9 +++++----
>  tests/test-qmp-input-visitor.c     |  9 +++++----
>  tests/test-qmp-output-visitor.c    | 23 ++++++++++++++++-------
>  tests/test-visitor-serialization.c |  9 +++++----
>  vl.c                               | 12 +++++++-----
>  19 files changed, 148 insertions(+), 83 deletions(-)
>
> diff --git a/hmp.c b/hmp.c
> index 5048eee..b7a42dc 100644
> --- a/hmp.c
> +++ b/hmp.c
> @@ -1658,8 +1658,9 @@ void hmp_object_add(Monitor *mon, const QDict *qdict)
>
>      object_add(type, id, pdict, opts_get_visitor(ov), &err);
>
> +    visit_check_struct(opts_get_visitor(ov), &err_end);
>  out_end:
> -    visit_end_struct(opts_get_visitor(ov), &err_end);
> +    visit_end_struct(opts_get_visitor(ov));
>      if (!err && err_end) {
>          qmp_object_del(id, NULL);
>      }

Preexisting: calling object_add() before visit_end_struct() is awkward.
Can we simplify things now we have separate visit_check_struct() and
visit_end_struct()?  Call visit_check_struct() before object_add(),
bypass object_add() on error, avoiding the need to undo it with
qmp_object_del().

> diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c
> index c419b17..48867c4 100644
> --- a/hw/virtio/virtio-balloon.c
> +++ b/hw/virtio/virtio-balloon.c
> @@ -132,14 +132,16 @@ static void balloon_stats_get_all(Object *obj, struct Visitor *v,
       visit_start_struct(v, NULL, NULL, "stats", 0, &err);
       if (err) {
           goto out_end;
       }
       for (i = 0; !err && i < VIRTIO_BALLOON_S_NR; i++) {
>          visit_type_int64(v, (int64_t *) &s->stats[i], balloon_stat_names[i],
>                           &err);

Preexisting: if visit_type_int64() fails, we need to break the loop.
Separate fix.

I wonder whether this is a common mistake.  Perhaps I can search for it
with Coccinelle.

>      }
> -    error_propagate(errp, err);
> -    err = NULL;
> -    visit_end_struct(v, &err);
> +    if (!err) {
> +        visit_check_struct(v, &err);
> +    }
> +    visit_end_struct(v);
>
>  out_end:
> -    error_propagate(errp, err);
> -    err = NULL;
> -    visit_end_struct(v, &err);
> +    if (!err) {
> +        visit_check_struct(v, &err);
> +    }
> +    visit_end_struct(v);
>  out:
>      error_propagate(errp, err);
>  }

This is the obvious mechanical change.

Loop fix and visit_end_struct() split squashed together could look like
this (not even compile-tested):

    for (i = 0; !err && i < VIRTIO_BALLOON_S_NR; i++) {
        visit_type_int64(v, (int64_t *) &s->stats[i], balloon_stat_names[i],
                         &err);
        if (err) {
            goto out_inner_end;
        }
    }

    visit_check_struct(v, &err);
out_inner_end:
    visit_end_struct(v);

    if (!err) {
        visit_check_struct(v, &err);
    }
out_end:
    visit_end_struct(v);

out:
    error_propagate(errp, err);

> diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
> index 090b19b..c53d4d3 100644
> --- a/include/qapi/visitor-impl.h
> +++ b/include/qapi/visitor-impl.h
> @@ -28,21 +28,23 @@ struct Visitor
>       * currently visit structs). */
>      void (*start_struct)(Visitor *v, void **obj, const char *kind,
>                           const char *name, size_t size, Error **errp);
> +    /* May be NULL; most useful for input visitors. */
> +    void (*check_struct)(Visitor *v, Error **errp);
>      /* Must be provided if start_struct is present. */
> -    void (*end_struct)(Visitor *v, Error **errp);
> +    void (*end_struct)(Visitor *v);
>
>      /* May be NULL; most useful for input visitors. */
>      void (*start_implicit_struct)(Visitor *v, void **obj, size_t size,
>                                    Error **errp);
>      /* May be NULL */
> -    void (*end_implicit_struct)(Visitor *v, Error **errp);
> +    void (*end_implicit_struct)(Visitor *v);
>
>      /* Must be set */
>      void (*start_list)(Visitor *v, const char *name, Error **errp);
>      /* Must be set */
>      GenericList *(*next_list)(Visitor *v, GenericList **list, Error **errp);
>      /* Must be set */
> -    void (*end_list)(Visitor *v, Error **errp);
> +    void (*end_list)(Visitor *v);
>
>      /* Must be set, although the helpers input_type_enum() and
>       * output_type_enum() can be used.  */
> diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
> index 6534cca..708f55c 100644
> --- a/include/qapi/visitor.h
> +++ b/include/qapi/visitor.h
> @@ -56,11 +56,19 @@ typedef struct GenericList
>  void visit_start_struct(Visitor *v, void **obj, const char *kind,
>                          const char *name, size_t size, Error **errp);
>  /**
> + * Check whether completing a struct is safe.

"Safe"?  We need to complete the struct visit with visit_end_struct()
regardless of what this function returns...

> + * Should be called prior to visit_end_struct() if all other intermediate
> + * visit steps were successful, to allow the caller one last chance to
> + * report errors such as remaining data that was not consumed by the visit.
> + */
> +void visit_check_struct(Visitor *v, Error **errp);
> +/**
>   * Complete a struct visit started earlier.
>   * Must be called after any successful use of visit_start_struct(),
> - * even if intermediate processing was skipped due to errors.
> + * even if intermediate processing was skipped due to errors, to allow
> + * the backend to release any resources.
>   */
> -void visit_end_struct(Visitor *v, Error **errp);
> +void visit_end_struct(Visitor *v);
>
>  /**
>   * Prepare to visit an implicit struct.
> @@ -76,9 +84,12 @@ void visit_start_implicit_struct(Visitor *v, void **obj, size_t size,
>  /**
>   * Complete an implicit struct visit started earlier.
>   * Must be called after any successful use of visit_start_implicit_struct(),
> - * even if intermediate processing was skipped due to errors.
> + * even if intermediate processing was skipped due to errors.  Unlike
> + * visit_end_struct(), there is no need for a prior check call (since
> + * an implicit object is a subset of larger object, checking before ending
> + * the overall struct is sufficient).
>   */
> -void visit_end_implicit_struct(Visitor *v, Error **errp);
> +void visit_end_implicit_struct(Visitor *v);
>
>  /**
>   * Prepare to visit an array tied to an object key @name.
> @@ -101,9 +112,10 @@ GenericList *visit_next_list(Visitor *v, GenericList **list, Error **errp);
>  /**
>   * Complete the list started earlier.
>   * Must be called after any successful use of visit_start_list(),
> - * even if intermediate processing was skipped due to errors.
> + * even if intermediate processing was skipped due to errors, to allow
> + * the backend to release any resources.
>   */
> -void visit_end_list(Visitor *v, Error **errp);
> +void visit_end_list(Visitor *v);
>
>  /**
>   * Check if an optional member @name of an object needs visiting.
> diff --git a/qapi/opts-visitor.c b/qapi/opts-visitor.c
> index cd10392..39dc431 100644
> --- a/qapi/opts-visitor.c
> +++ b/qapi/opts-visitor.c
> @@ -158,7 +158,7 @@ ghr_true(gpointer ign_key, gpointer ign_value, gpointer ign_user_data)
>
>
>  static void
> -opts_end_struct(Visitor *v, Error **errp)
> +opts_check_struct(Visitor *v, Error **errp)
>  {
>      OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
>      GQueue *any;

       if (--ov->depth > 0) {

Do we want to decrement ov->depth here?  We'll decrement it again in
opts_end_struct()...

           return;
       }

       /* we should have processed all (distinct) QemuOpt instances */
       any = g_hash_table_find(ov->unprocessed_opts, &ghr_true, NULL);
       if (any) {
           const QemuOpt *first;

> @@ -175,6 +175,18 @@ opts_end_struct(Visitor *v, Error **errp)
>          first = g_queue_peek_head(any);
>          error_setg(errp, QERR_INVALID_PARAMETER, first->name);
>      }
> +}
> +
> +
> +static void
> +opts_end_struct(Visitor *v)
> +{
> +    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
> +
> +    if (--ov->depth > 0) {
> +        return;
> +    }
> +
>      g_hash_table_destroy(ov->unprocessed_opts);
>      ov->unprocessed_opts = NULL;
>      if (ov->fake_id_opt) {
> @@ -263,7 +275,7 @@ opts_next_list(Visitor *v, GenericList **list, Error **errp)
>
>
>  static void
> -opts_end_list(Visitor *v, Error **errp)
> +opts_end_list(Visitor *v)
>  {
>      OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
>
> @@ -506,6 +518,7 @@ opts_visitor_new(const QemuOpts *opts)
>      ov = g_malloc0(sizeof *ov);
>
>      ov->visitor.start_struct = &opts_start_struct;
> +    ov->visitor.check_struct = &opts_check_struct;
>      ov->visitor.end_struct   = &opts_end_struct;
>
>      ov->visitor.start_list = &opts_start_list;
> diff --git a/qapi/qapi-dealloc-visitor.c b/qapi/qapi-dealloc-visitor.c
> index 737deab..a2139dd 100644
> --- a/qapi/qapi-dealloc-visitor.c
> +++ b/qapi/qapi-dealloc-visitor.c
> @@ -67,7 +67,7 @@ static void qapi_dealloc_start_struct(Visitor *v, void **obj, const char *kind,
>      qapi_dealloc_push(qov, obj);
>  }
>
> -static void qapi_dealloc_end_struct(Visitor *v, Error **errp)
> +static void qapi_dealloc_end_struct(Visitor *v)
>  {
>      QapiDeallocVisitor *qov = to_qov(v);
>      void **obj = qapi_dealloc_pop(qov);
> @@ -85,7 +85,7 @@ static void qapi_dealloc_start_implicit_struct(Visitor *v,
>      qapi_dealloc_push(qov, obj);
>  }
>
> -static void qapi_dealloc_end_implicit_struct(Visitor *v, Error **errp)
> +static void qapi_dealloc_end_implicit_struct(Visitor *v)
>  {
>      QapiDeallocVisitor *qov = to_qov(v);
>      void **obj = qapi_dealloc_pop(qov);
> @@ -121,7 +121,7 @@ static GenericList *qapi_dealloc_next_list(Visitor *v, GenericList **listp,
>      return NULL;
>  }
>
> -static void qapi_dealloc_end_list(Visitor *v, Error **errp)
> +static void qapi_dealloc_end_list(Visitor *v)
>  {
>      QapiDeallocVisitor *qov = to_qov(v);
>      void *obj = qapi_dealloc_pop(qov);
> diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
> index cbf7780..82d54af 100644
> --- a/qapi/qapi-visit-core.c
> +++ b/qapi/qapi-visit-core.c
> @@ -23,9 +23,16 @@ void visit_start_struct(Visitor *v, void **obj, const char *kind,
>      v->start_struct(v, obj, kind, name, size, errp);
>  }
>
> -void visit_end_struct(Visitor *v, Error **errp)
> +void visit_check_struct(Visitor *v, Error **errp)
>  {
> -    v->end_struct(v, errp);
> +    if (v->check_struct) {
> +        v->check_struct(v, errp);
> +    }
> +}
> +
> +void visit_end_struct(Visitor *v)
> +{
> +    v->end_struct(v);
>  }
>
>  void visit_start_implicit_struct(Visitor *v, void **obj, size_t size,
> @@ -36,10 +43,10 @@ void visit_start_implicit_struct(Visitor *v, void **obj, size_t size,
>      }
>  }
>
> -void visit_end_implicit_struct(Visitor *v, Error **errp)
> +void visit_end_implicit_struct(Visitor *v)
>  {
>      if (v->end_implicit_struct) {
> -        v->end_implicit_struct(v, errp);
> +        v->end_implicit_struct(v);
>      }
>  }
>
> @@ -53,9 +60,9 @@ GenericList *visit_next_list(Visitor *v, GenericList **list, Error **errp)
>      return v->next_list(v, list, errp);
>  }
>
> -void visit_end_list(Visitor *v, Error **errp)
> +void visit_end_list(Visitor *v)
>  {
> -    v->end_list(v, errp);
> +    v->end_list(v);
>  }
>
>  bool visit_start_union(Visitor *v, bool data_present, Error **errp)
> diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c
> index 5310db5..f10f74f 100644
> --- a/qapi/qmp-input-visitor.c
> +++ b/qapi/qmp-input-visitor.c
> @@ -88,14 +88,14 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
>      qiv->nb_stack++;
>  }
>
> -/** Only for qmp_input_pop. */
> +/** Only for qmp_input_check. */

Drop the comment instead?

Aside: a loop would be more idiomatic C.  Leave higher order functions
to languages that are actually equipped for the job.

>  static gboolean always_true(gpointer key, gpointer val, gpointer user_pkey)
>  {
>      *(const char **)user_pkey = (const char *)key;
>      return TRUE;
>  }
>
> -static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
> +static void qmp_input_check(QmpInputVisitor *qiv, Error **errp)
>  {
>      assert(qiv->nb_stack > 0);
>
> @@ -107,6 +107,17 @@ static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
>                  g_hash_table_find(top_ht, always_true, &key);
>                  error_setg(errp, QERR_QMP_EXTRA_MEMBER, key);
>              }
> +        }
> +    }
> +}
> +
> +static void qmp_input_pop(QmpInputVisitor *qiv)
> +{
> +    assert(qiv->nb_stack > 0);
> +
> +    if (qiv->strict) {
> +        GHashTable * const top_ht = qiv->stack[qiv->nb_stack - 1].h;
> +        if (top_ht) {
>              g_hash_table_unref(top_ht);
>          }
>      }
> @@ -138,11 +149,18 @@ static void qmp_input_start_struct(Visitor *v, void **obj, const char *kind,
>      }
>  }
>
> -static void qmp_input_end_struct(Visitor *v, Error **errp)
> +static void qmp_input_check_struct(Visitor *v, Error **errp)
>  {
>      QmpInputVisitor *qiv = to_qiv(v);
>
> -    qmp_input_pop(qiv, errp);
> +    qmp_input_check(qiv, errp);

qmp_input_check(to_qiv(v));

Alternatively, inline, as this is the only caller.

> +}
> +
> +static void qmp_input_end_struct(Visitor *v)
> +{
> +    QmpInputVisitor *qiv = to_qiv(v);
> +
> +    qmp_input_pop(qiv);

qmp_input_pop(to_qiv(v));

Since all callers pass to_qiv(v), we could move that into the callee.
Same for qmp_input_check() then.

>  }
>
>  static void qmp_input_start_implicit_struct(Visitor *v, void **obj,
> @@ -153,10 +171,6 @@ static void qmp_input_start_implicit_struct(Visitor *v, void **obj,
>      }
>  }
>
> -static void qmp_input_end_implicit_struct(Visitor *v, Error **errp)
> -{
> -}
> -

Further evidence of confusion about the visitor contract.

>  static void qmp_input_start_list(Visitor *v, const char *name, Error **errp)
>  {
>      QmpInputVisitor *qiv = to_qiv(v);
> @@ -201,11 +215,11 @@ static GenericList *qmp_input_next_list(Visitor *v, GenericList **list,
>      return entry;
>  }
>
> -static void qmp_input_end_list(Visitor *v, Error **errp)
> +static void qmp_input_end_list(Visitor *v)
>  {
>      QmpInputVisitor *qiv = to_qiv(v);
>
> -    qmp_input_pop(qiv, errp);
> +    qmp_input_pop(qiv);
>  }

Identical to qmp_input_end_struct() now.  If we move to_qiv() into
qmp_input_pop(), we can use it as callback directly, and drop the two
wrappers.

>
>  static void qmp_input_get_next_type(Visitor *v, qtype_code *type,
> @@ -332,9 +346,9 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
>      v = g_malloc0(sizeof(*v));
>
>      v->visitor.start_struct = qmp_input_start_struct;
> +    v->visitor.check_struct = qmp_input_check_struct;
>      v->visitor.end_struct = qmp_input_end_struct;
>      v->visitor.start_implicit_struct = qmp_input_start_implicit_struct;
> -    v->visitor.end_implicit_struct = qmp_input_end_implicit_struct;
>      v->visitor.start_list = qmp_input_start_list;
>      v->visitor.next_list = qmp_input_next_list;
>      v->visitor.end_list = qmp_input_end_list;
> diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c
> index f891e72..b9fba7b 100644
> --- a/qapi/qmp-output-visitor.c
> +++ b/qapi/qmp-output-visitor.c
> @@ -122,7 +122,7 @@ static void qmp_output_start_struct(Visitor *v, void **obj, const char *kind,
>      qmp_output_push(qov, dict);
>  }
>
> -static void qmp_output_end_struct(Visitor *v, Error **errp)
> +static void qmp_output_end_struct(Visitor *v)
>  {
>      QmpOutputVisitor *qov = to_qov(v);
>      qmp_output_pop(qov);
> @@ -153,7 +153,7 @@ static GenericList *qmp_output_next_list(Visitor *v, GenericList **listp,
>      return list ? list->next : NULL;
>  }
>
> -static void qmp_output_end_list(Visitor *v, Error **errp)
> +static void qmp_output_end_list(Visitor *v)
>  {
>      QmpOutputVisitor *qov = to_qov(v);
>      qmp_output_pop(qov);
> diff --git a/qapi/string-input-visitor.c b/qapi/string-input-visitor.c
> index bbd6a54..97e00c8 100644
> --- a/qapi/string-input-visitor.c
> +++ b/qapi/string-input-visitor.c
> @@ -173,7 +173,7 @@ next_list(Visitor *v, GenericList **list, Error **errp)
>  }
>
>  static void
> -end_list(Visitor *v, Error **errp)
> +end_list(Visitor *v)
>  {
>      StringInputVisitor *siv = DO_UPCAST(StringInputVisitor, visitor, v);
>      siv->head = true;
> diff --git a/qapi/string-output-visitor.c b/qapi/string-output-visitor.c
> index b86ce2c..b269f39 100644
> --- a/qapi/string-output-visitor.c
> +++ b/qapi/string-output-visitor.c
> @@ -290,7 +290,7 @@ next_list(Visitor *v, GenericList **list, Error **errp)
>  }
>
>  static void
> -end_list(Visitor *v, Error **errp)
> +end_list(Visitor *v)
>  {
>      StringOutputVisitor *sov = DO_UPCAST(StringOutputVisitor, visitor, v);
>
> diff --git a/qom/object.c b/qom/object.c
> index 11cd86b..6d92e01 100644
> --- a/qom/object.c
> +++ b/qom/object.c
> @@ -1844,10 +1844,9 @@ static void property_get_tm(Object *obj, Visitor *v, void *opaque,
>      if (err) {
>          goto out_end;
>      }
> +    visit_check_struct(v, &err);
>  out_end:
> -    error_propagate(errp, err);
> -    err = NULL;
> -    visit_end_struct(v, errp);
> +    visit_end_struct(v);
>  out:
>      error_propagate(errp, err);
>

By now the improvement is obvious: the users of this interface get nicer
pretty consistently.

> diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py
> index 720486f..250e753 100644
> --- a/scripts/qapi-event.py
> +++ b/scripts/qapi-event.py
> @@ -73,13 +73,14 @@ def gen_event_send(name, arg_type):
>          ret += gen_err_check()
>          ret += gen_visit_fields(arg_type.members, need_cast=True)
>          ret += mcgen('''
> -    visit_end_struct(v, &err);
> +    visit_check_struct(v, &err);
>      if (err) {
>          goto out;
>      }
> +    visit_end_struct(v);
>
>      obj = qmp_output_get_qobject(qov);
> -    g_assert(obj != NULL);
> +    g_assert(obj);

I prefer the more laconic form myself, but it's an unrelated change.

>
>      qdict_put_obj(qmp, "data", obj);
>  ''')
> diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
> index 1ac5350..c5a32cf 100644
> --- a/scripts/qapi-visit.py
> +++ b/scripts/qapi-visit.py
> @@ -52,7 +52,7 @@ static void visit_type_implicit_%(c_type)s(Visitor *v, %(c_type)s **obj, Error *
>      visit_start_implicit_struct(v, (void **)obj, sizeof(%(c_type)s), &err);
>      if (!err) {
>          visit_type_%(c_type)s_fields(v, obj, errp);
> -        visit_end_implicit_struct(v, &err);
> +        visit_end_implicit_struct(v);
>      }
>      error_propagate(errp, err);
>  }
> @@ -115,9 +115,12 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
>      visit_start_struct(v, (void **)obj, "%(name)s", name, sizeof(%(c_name)s), &err);
>      if (!err) {
>          if (*obj) {
> -            visit_type_%(c_name)s_fields(v, obj, errp);
> +            visit_type_%(c_name)s_fields(v, obj, &err);
>          }
> -        visit_end_struct(v, &err);
> +        if (!err) {
> +            visit_check_struct(v, &err);
> +        }
> +        visit_end_struct(v);
>      }
>      error_propagate(errp, err);
>  }
> @@ -147,9 +150,7 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
>          visit_type_%(c_elt_type)s(v, &native_i->value, NULL, &err);
>      }
>
> -    error_propagate(errp, err);
> -    err = NULL;
> -    visit_end_list(v, &err);
> +    visit_end_list(v);
>  out:
>      error_propagate(errp, err);
>  }
> @@ -208,9 +209,7 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
>                     "%(name)s");
>      }
>  out_obj:
> -    error_propagate(errp, err);
> -    err = NULL;
> -    visit_end_implicit_struct(v, &err);
> +    visit_end_implicit_struct(v);
>  out:
>      error_propagate(errp, err);
>  }
> @@ -297,13 +296,14 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
>      default:
>          abort();
>      }
> +    if (!err) {
> +        visit_check_struct(v, &err);
> +    }
>  out_obj:
>      error_propagate(errp, err);
>      err = NULL;
>      visit_end_union(v, !!(*obj)->data, &err);

Should visit_end_union() be similarly split?  Or should its Error **
parameter be dropped?  As far as I can tell, no visitor implements this
method...

> -    error_propagate(errp, err);
> -    err = NULL;
> -    visit_end_struct(v, &err);
> +    visit_end_struct(v);
>  out:
>      error_propagate(errp, err);
>  }
> diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c
> index 1c13c8f..da9dafd 100644
> --- a/tests/test-qmp-input-strict.c
> +++ b/tests/test-qmp-input-strict.c
> @@ -118,11 +118,12 @@ static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
>          goto out_end;
>      }
>      visit_type_str(v, &(*obj)->string, "string", &err);
> -
> +    if (err) {
> +        goto out_end;
> +    }
> +    visit_check_struct(v, &err);
>  out_end:
> -    error_propagate(errp, err);
> -    err = NULL;
> -    visit_end_struct(v, &err);
> +    visit_end_struct(v);
>  out:
>      error_propagate(errp, err);
>  }
> diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
> index ef5871b..38b1436 100644
> --- a/tests/test-qmp-input-visitor.c
> +++ b/tests/test-qmp-input-visitor.c
> @@ -200,11 +200,12 @@ static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
>          goto out_end;
>      }
>      visit_type_str(v, &(*obj)->string, "string", &err);
> -
> +    if (err) {
> +        goto out_end;
> +    }
> +    visit_check_struct(v, &err);

Here's you goto over visit_check_struct()...

>  out_end:
> -    error_propagate(errp, err);
> -    err = NULL;
> -    visit_end_struct(v, &err);
> +    visit_end_struct(v);
>  out:
>      error_propagate(errp, err);
>  }
> diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c
> index 11996d0..f836588 100644
> --- a/tests/test-qmp-output-visitor.c
> +++ b/tests/test-qmp-output-visitor.c
> @@ -194,10 +194,11 @@ static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
>      }
>      visit_type_str(v, &(*obj)->string, "string", &err);
>
> +    if (!err) {
> +        visit_check_struct(v, &err);
> +    }

... but here, you guard it with an if.  Either way works, but I'd like
us to pick just one for the generators.

>  out_end:
> -    error_propagate(errp, err);
> -    err = NULL;
> -    visit_end_struct(v, &err);
> +    visit_end_struct(v);
>  out:
>      error_propagate(errp, err);
>  }
> @@ -329,15 +330,23 @@ static void visit_type_TestStructList(Visitor *v, TestStructList **obj,
>                                        const char *name, Error **errp)
>  {
>      GenericList *i, **head = (GenericList **)obj;
> +    Error *err = NULL;
>
> -    visit_start_list(v, name, errp);
> +    visit_start_list(v, name, &err);
> +    if (err) {
> +        goto out;
> +    }
>
> -    for (*head = i = visit_next_list(v, head, errp); i; i = visit_next_list(v, &i, errp)) {
> +    for (*head = i = visit_next_list(v, head, &err);
> +         !err && i;
> +         i = visit_next_list(v, &i, &err)) {
>          TestStructList *native_i = (TestStructList *)i;
> -        visit_type_TestStruct(v, &native_i->value, NULL, errp);
> +        visit_type_TestStruct(v, &native_i->value, NULL, &err);
>      }

Is this a silent bug fix?  Before your patch, the loop doesn't break on
error.

I'm not afraid of complex for expressions, but I think this one is
unnecessarily complex.

Why does visit_type_TestStruct() store to *head?  All that buys us is
less obviously correct list cleanup in test_visitor_out_list().

Testing err in the for expression works, but the admittedly more verbose

        visit_type_TestStruct(v, &native_i->value, NULL, &err);
        if (err) {
            break; // or goto wherever
        }

would be closer to common Error usage.  Matter of taste.

Aside: the caller creates identical list elements, which weakens the
test needlessly.

>
> -    visit_end_list(v, errp);
> +    visit_end_list(v);
> +out:
> +    error_propagate(errp, err);
>  }
>
>  static void test_visitor_out_list(TestOutputVisitorData *data,
> diff --git a/tests/test-visitor-serialization.c b/tests/test-visitor-serialization.c
> index fa86cae..dd50d69 100644
> --- a/tests/test-visitor-serialization.c
> +++ b/tests/test-visitor-serialization.c
> @@ -212,11 +212,12 @@ static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
>          goto out_end;
>      }
>      visit_type_str(v, &(*obj)->string, "string", &err);
> -
> +    if (err) {
> +        goto out_end;
> +    }
> +    visit_check_struct(v, &err);
>  out_end:
> -    error_propagate(errp, err);
> -    err = NULL;
> -    visit_end_struct(v, &err);
> +    visit_end_struct(v);
>  out:
>      error_propagate(errp, err);
>  }
> diff --git a/vl.c b/vl.c
> index 8d1846c..8b944aa 100644
> --- a/vl.c
> +++ b/vl.c
> @@ -2796,23 +2796,25 @@ static int object_create(void *opaque, QemuOpts *opts, Error **errp)
>      qdict_del(pdict, "qom-type");
>      visit_type_str(opts_get_visitor(ov), &type, "qom-type", &err);
>      if (err) {
> -        goto out;
> +        goto obj_out;
>      }
>      if (!type_predicate(type)) {
> -        goto out;
> +        goto obj_out;
>      }
>
>      qdict_del(pdict, "id");
>      visit_type_str(opts_get_visitor(ov), &id, "id", &err);
>      if (err) {
> -        goto out;
> +        goto obj_out;
>      }
>
>      object_add(type, id, pdict, opts_get_visitor(ov), &err);
>      if (err) {
> -        goto out;
> +        goto obj_out;
>      }
> -    visit_end_struct(opts_get_visitor(ov), &err);
> +    visit_check_struct(opts_get_visitor(ov), &err);
> +obj_out:
> +    visit_end_struct(opts_get_visitor(ov));
>      if (err) {
>          qmp_object_del(id, NULL);
>      }

Silent bug fix: we now call visit_end_struct() even on error.  Impact?
Separate patch?

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [RFC PATCH] qapi: split visit_end_struct() into pieces
  2015-10-07 12:00             ` Markus Armbruster
@ 2015-10-07 13:08               ` Markus Armbruster
  2015-10-07 14:57               ` Eric Blake
  1 sibling, 0 replies; 108+ messages in thread
From: Markus Armbruster @ 2015-10-07 13:08 UTC (permalink / raw
  To: Eric Blake
  Cc: Michael S. Tsirkin, qemu-devel, Michael Roth, Luiz Capitulino,
	Paolo Bonzini, Andreas Färber

Markus Armbruster <armbru@redhat.com> writes:

> Eric Blake <eblake@redhat.com> writes:
[...]
>> diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c
>> index c419b17..48867c4 100644
>> --- a/hw/virtio/virtio-balloon.c
>> +++ b/hw/virtio/virtio-balloon.c
>> @@ -132,14 +132,16 @@ static void balloon_stats_get_all(Object *obj, struct Visitor *v,
>        visit_start_struct(v, NULL, NULL, "stats", 0, &err);
>        if (err) {
>            goto out_end;
>        }
>        for (i = 0; !err && i < VIRTIO_BALLOON_S_NR; i++) {
>>          visit_type_int64(v, (int64_t *) &s->stats[i], balloon_stat_names[i],
>>                           &err);
>
> Preexisting: if visit_type_int64() fails, we need to break the loop.
> Separate fix.

Nonsense.

[...]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [RFC PATCH] qapi: split visit_end_struct() into pieces
  2015-10-07 12:00             ` Markus Armbruster
  2015-10-07 13:08               ` Markus Armbruster
@ 2015-10-07 14:57               ` Eric Blake
  2015-10-07 15:23                 ` Markus Armbruster
  1 sibling, 1 reply; 108+ messages in thread
From: Eric Blake @ 2015-10-07 14:57 UTC (permalink / raw
  To: Markus Armbruster
  Cc: Michael S. Tsirkin, qemu-devel, Michael Roth, Luiz Capitulino,
	Paolo Bonzini, Andreas Färber

[-- Attachment #1: Type: text/plain, Size: 6479 bytes --]

On 10/07/2015 06:00 AM, Markus Armbruster wrote:

>>> Looks like we're getting drawn into visitor contract territory again.
>>>

>> +++ b/hmp.c
>> @@ -1658,8 +1658,9 @@ void hmp_object_add(Monitor *mon, const QDict *qdict)
>>
>>      object_add(type, id, pdict, opts_get_visitor(ov), &err);
>>
>> +    visit_check_struct(opts_get_visitor(ov), &err_end);
>>  out_end:
>> -    visit_end_struct(opts_get_visitor(ov), &err_end);
>> +    visit_end_struct(opts_get_visitor(ov));
>>      if (!err && err_end) {
>>          qmp_object_del(id, NULL);
>>      }
> 
> Preexisting: calling object_add() before visit_end_struct() is awkward.
> Can we simplify things now we have separate visit_check_struct() and
> visit_end_struct()?  Call visit_check_struct() before object_add(),
> bypass object_add() on error, avoiding the need to undo it with
> qmp_object_del().

Okay, it sounds like I'm sitting on a pile of pre-patch cleanups, and
that I'm on the right track for having done the split.


>> +++ b/include/qapi/visitor.h
>> @@ -56,11 +56,19 @@ typedef struct GenericList
>>  void visit_start_struct(Visitor *v, void **obj, const char *kind,
>>                          const char *name, size_t size, Error **errp);
>>  /**
>> + * Check whether completing a struct is safe.
> 
> "Safe"?  We need to complete the struct visit with visit_end_struct()
> regardless of what this function returns...
> 
>> + * Should be called prior to visit_end_struct() if all other intermediate
>> + * visit steps were successful, to allow the caller one last chance to
>> + * report errors such as remaining data that was not consumed by the visit.
>> + */
>> +void visit_check_struct(Visitor *v, Error **errp);

Maybe:

Declare the current struct complete, and check for unvisited contents.

>>  static void
>> -opts_end_struct(Visitor *v, Error **errp)
>> +opts_check_struct(Visitor *v, Error **errp)
>>  {
>>      OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
>>      GQueue *any;
> 
>        if (--ov->depth > 0) {
> 
> Do we want to decrement ov->depth here?  We'll decrement it again in
> opts_end_struct()...

Oh, good catch.  This was an awkward split, and I got it off-by-one.

>> +++ b/qapi/qmp-input-visitor.c
>> @@ -88,14 +88,14 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
>>      qiv->nb_stack++;
>>  }
>>
>> -/** Only for qmp_input_pop. */
>> +/** Only for qmp_input_check. */
> 
> Drop the comment instead?
> 
> Aside: a loop would be more idiomatic C.  Leave higher order functions
> to languages that are actually equipped for the job.
> 
>>  static gboolean always_true(gpointer key, gpointer val, gpointer user_pkey)
>>  {
>>      *(const char **)user_pkey = (const char *)key;
>>      return TRUE;
>>  }
>>
>> -static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
>> +static void qmp_input_check(QmpInputVisitor *qiv, Error **errp)
>>  {
>>      assert(qiv->nb_stack > 0);
>>
>> @@ -107,6 +107,17 @@ static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
>>                  g_hash_table_find(top_ht, always_true, &key);

always_true() exists for g_hash_table_find() - unless you know of some
other way to grab any arbitrary element of the hash table that doesn't
require a higher-order function.


>> +++ b/scripts/qapi-event.py
>> @@ -73,13 +73,14 @@ def gen_event_send(name, arg_type):
>>          ret += gen_err_check()
>>          ret += gen_visit_fields(arg_type.members, need_cast=True)
>>          ret += mcgen('''
>> -    visit_end_struct(v, &err);
>> +    visit_check_struct(v, &err);
>>      if (err) {
>>          goto out;
>>      }
>> +    visit_end_struct(v);
>>
>>      obj = qmp_output_get_qobject(qov);
>> -    g_assert(obj != NULL);
>> +    g_assert(obj);
> 
> I prefer the more laconic form myself, but it's an unrelated change.

I can split that one-line change into a more appropriate patch.

>>  out_obj:
>>      error_propagate(errp, err);
>>      err = NULL;
>>      visit_end_union(v, !!(*obj)->data, &err);
> 
> Should visit_end_union() be similarly split?  Or should its Error **
> parameter be dropped?  As far as I can tell, no visitor implements this
> method...
> 

visit_end_union() gets completely dropped in a different patch.  See v5
28/46.


>> +++ b/tests/test-qmp-output-visitor.c
>> @@ -194,10 +194,11 @@ static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
>>      }
>>      visit_type_str(v, &(*obj)->string, "string", &err);
>>
>> +    if (!err) {
>> +        visit_check_struct(v, &err);
>> +    }
> 
> ... but here, you guard it with an if.  Either way works, but I'd like
> us to pick just one for the generators.

Sure.


>> -    for (*head = i = visit_next_list(v, head, errp); i; i = visit_next_list(v, &i, errp)) {
>> +    for (*head = i = visit_next_list(v, head, &err);
>> +         !err && i;
>> +         i = visit_next_list(v, &i, &err)) {
>>          TestStructList *native_i = (TestStructList *)i;
>> -        visit_type_TestStruct(v, &native_i->value, NULL, errp);
>> +        visit_type_TestStruct(v, &native_i->value, NULL, &err);
>>      }
> 
> Is this a silent bug fix?  Before your patch, the loop doesn't break on
> error.
> 

Yes, looks like it. And all the more reason for our test code to NOT
hand-write this, but to rely on the generator (so that we are testing a
single version of visit_* calls, rather than subtle differences in
generated vs. hand-rolled).


>> +++ b/vl.c
>> @@ -2796,23 +2796,25 @@ static int object_create(void *opaque, QemuOpts *opts, Error **errp)
>>      qdict_del(pdict, "qom-type");
>>      visit_type_str(opts_get_visitor(ov), &type, "qom-type", &err);
>>      if (err) {
>> -        goto out;
>> +        goto obj_out;

>> +obj_out:
>> +    visit_end_struct(opts_get_visitor(ov));
>>      if (err) {
>>          qmp_object_del(id, NULL);
>>      }
> 
> Silent bug fix: we now call visit_end_struct() even on error.  Impact?
> Separate patch?

Yes, separate patch, and I'll evaluate impact there.

So sounds like I should proceed with this RFC (which means more respin
of my other patches, before I can post subset C - but that's okay, since
we aren't even through reviewing subset B, nor is subset A in a pull
request).

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 108+ messages in thread

* Re: [Qemu-devel] [RFC PATCH] qapi: split visit_end_struct() into pieces
  2015-10-07 14:57               ` Eric Blake
@ 2015-10-07 15:23                 ` Markus Armbruster
  0 siblings, 0 replies; 108+ messages in thread
From: Markus Armbruster @ 2015-10-07 15:23 UTC (permalink / raw
  To: Eric Blake
  Cc: Michael S. Tsirkin, qemu-devel, Michael Roth, Luiz Capitulino,
	Paolo Bonzini, Andreas Färber

Eric Blake <eblake@redhat.com> writes:

> On 10/07/2015 06:00 AM, Markus Armbruster wrote:
>
>>>> Looks like we're getting drawn into visitor contract territory again.
>>>>
>
>>> +++ b/hmp.c
>>> @@ -1658,8 +1658,9 @@ void hmp_object_add(Monitor *mon, const QDict *qdict)
>>>
>>>      object_add(type, id, pdict, opts_get_visitor(ov), &err);
>>>
>>> +    visit_check_struct(opts_get_visitor(ov), &err_end);
>>>  out_end:
>>> -    visit_end_struct(opts_get_visitor(ov), &err_end);
>>> +    visit_end_struct(opts_get_visitor(ov));
>>>      if (!err && err_end) {
>>>          qmp_object_del(id, NULL);
>>>      }
>> 
>> Preexisting: calling object_add() before visit_end_struct() is awkward.
>> Can we simplify things now we have separate visit_check_struct() and
>> visit_end_struct()?  Call visit_check_struct() before object_add(),
>> bypass object_add() on error, avoiding the need to undo it with
>> qmp_object_del().
>
> Okay, it sounds like I'm sitting on a pile of pre-patch cleanups, and
> that I'm on the right track for having done the split.

I think so, too.

>>> +++ b/include/qapi/visitor.h
>>> @@ -56,11 +56,19 @@ typedef struct GenericList
>>>  void visit_start_struct(Visitor *v, void **obj, const char *kind,
>>>                          const char *name, size_t size, Error **errp);
>>>  /**
>>> + * Check whether completing a struct is safe.
>> 
>> "Safe"?  We need to complete the struct visit with visit_end_struct()
>> regardless of what this function returns...
>> 
>>> + * Should be called prior to visit_end_struct() if all other intermediate
>>> + * visit steps were successful, to allow the caller one last chance to
>>> + * report errors such as remaining data that was not consumed by the visit.
>>> + */
>>> +void visit_check_struct(Visitor *v, Error **errp);
>
> Maybe:
>
> Declare the current struct complete, and check for unvisited contents.

That's one purpose of implementing the method wrapped by this function.
Perhaps simply: Prepare completing a struct visit.

>>>  static void
>>> -opts_end_struct(Visitor *v, Error **errp)
>>> +opts_check_struct(Visitor *v, Error **errp)
>>>  {
>>>      OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
>>>      GQueue *any;
>> 
>>        if (--ov->depth > 0) {
>> 
>> Do we want to decrement ov->depth here?  We'll decrement it again in
>> opts_end_struct()...
>
> Oh, good catch.  This was an awkward split, and I got it off-by-one.
>
>>> +++ b/qapi/qmp-input-visitor.c
>>> @@ -88,14 +88,14 @@ static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
>>>      qiv->nb_stack++;
>>>  }
>>>
>>> -/** Only for qmp_input_pop. */
>>> +/** Only for qmp_input_check. */
>> 
>> Drop the comment instead?
>> 
>> Aside: a loop would be more idiomatic C.  Leave higher order functions
>> to languages that are actually equipped for the job.
>> 
>>>  static gboolean always_true(gpointer key, gpointer val, gpointer user_pkey)
>>>  {
>>>      *(const char **)user_pkey = (const char *)key;
>>>      return TRUE;
>>>  }
>>>
>>> -static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
>>> +static void qmp_input_check(QmpInputVisitor *qiv, Error **errp)
>>>  {
>>>      assert(qiv->nb_stack > 0);
>>>
>>> @@ -107,6 +107,17 @@ static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
>>>                  g_hash_table_find(top_ht, always_true, &key);
>
> always_true() exists for g_hash_table_find() - unless you know of some
> other way to grab any arbitrary element of the hash table that doesn't
> require a higher-order function.

g_hash_table_iter_init() and g_hash_table_iter_next().

>>> +++ b/scripts/qapi-event.py
>>> @@ -73,13 +73,14 @@ def gen_event_send(name, arg_type):
>>>          ret += gen_err_check()
>>>          ret += gen_visit_fields(arg_type.members, need_cast=True)
>>>          ret += mcgen('''
>>> -    visit_end_struct(v, &err);
>>> +    visit_check_struct(v, &err);
>>>      if (err) {
>>>          goto out;
>>>      }
>>> +    visit_end_struct(v);
>>>
>>>      obj = qmp_output_get_qobject(qov);
>>> -    g_assert(obj != NULL);
>>> +    g_assert(obj);
>> 
>> I prefer the more laconic form myself, but it's an unrelated change.
>
> I can split that one-line change into a more appropriate patch.
>
>>>  out_obj:
>>>      error_propagate(errp, err);
>>>      err = NULL;
>>>      visit_end_union(v, !!(*obj)->data, &err);
>> 
>> Should visit_end_union() be similarly split?  Or should its Error **
>> parameter be dropped?  As far as I can tell, no visitor implements this
>> method...
>> 
>
> visit_end_union() gets completely dropped in a different patch.  See v5
> 28/46.
>
>
>>> +++ b/tests/test-qmp-output-visitor.c
>>> @@ -194,10 +194,11 @@ static void visit_type_TestStruct(Visitor *v,
>>> TestStruct **obj,
>>>      }
>>>      visit_type_str(v, &(*obj)->string, "string", &err);
>>>
>>> +    if (!err) {
>>> +        visit_check_struct(v, &err);
>>> +    }
>> 
>> ... but here, you guard it with an if.  Either way works, but I'd like
>> us to pick just one for the generators.
>
> Sure.
>
>
>>> -    for (*head = i = visit_next_list(v, head, errp); i; i = visit_next_list(v, &i, errp)) {
>>> +    for (*head = i = visit_next_list(v, head, &err);
>>> +         !err && i;
>>> +         i = visit_next_list(v, &i, &err)) {
>>>          TestStructList *native_i = (TestStructList *)i;
>>> -        visit_type_TestStruct(v, &native_i->value, NULL, errp);
>>> +        visit_type_TestStruct(v, &native_i->value, NULL, &err);
>>>      }
>> 
>> Is this a silent bug fix?  Before your patch, the loop doesn't break on
>> error.
>> 
>
> Yes, looks like it. And all the more reason for our test code to NOT
> hand-write this, but to rely on the generator (so that we are testing a
> single version of visit_* calls, rather than subtle differences in
> generated vs. hand-rolled).
>
>
>>> +++ b/vl.c
>>> @@ -2796,23 +2796,25 @@ static int object_create(void *opaque, QemuOpts *opts, Error **errp)
>>>      qdict_del(pdict, "qom-type");
>>>      visit_type_str(opts_get_visitor(ov), &type, "qom-type", &err);
>>>      if (err) {
>>> -        goto out;
>>> +        goto obj_out;
>
>>> +obj_out:
>>> +    visit_end_struct(opts_get_visitor(ov));
>>>      if (err) {
>>>          qmp_object_del(id, NULL);
>>>      }
>> 
>> Silent bug fix: we now call visit_end_struct() even on error.  Impact?
>> Separate patch?
>
> Yes, separate patch, and I'll evaluate impact there.
>
> So sounds like I should proceed with this RFC (which means more respin
> of my other patches, before I can post subset C - but that's okay, since
> we aren't even through reviewing subset B, nor is subset A in a pull
> request).

Yes, please.

Subset A is ready, and will be in my next QAPI pull request.

^ permalink raw reply	[flat|nested] 108+ messages in thread

end of thread, other threads:[~2015-10-07 15:23 UTC | newest]

Thread overview: 108+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-09-21 21:57 [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 01/46] qapi: Sort qapi-schema tests Eric Blake
2015-09-23 14:26   ` Eric Blake
2015-09-23 15:09     ` Markus Armbruster
2015-09-23 15:19       ` Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 02/46] qapi: Clean up qapi.py per pep8 Eric Blake
2015-09-22 14:00   ` Markus Armbruster
2015-09-22 14:58     ` Eric Blake
2015-09-23  9:20       ` Markus Armbruster
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 03/46] qapi: Test for C member name collisions Eric Blake
2015-09-22 15:23   ` Markus Armbruster
2015-09-22 17:52     ` Eric Blake
2015-09-23  9:43       ` Markus Armbruster
2015-09-23 12:45         ` Eric Blake
2015-09-23 14:02           ` Markus Armbruster
2015-09-23 14:19             ` Eric Blake
2015-09-23 15:12               ` Markus Armbruster
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 04/46] qapi: Add tests for empty unions Eric Blake
2015-09-24 14:16   ` Markus Armbruster
2015-09-24 15:52     ` Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 05/46] qapi: Test use of 'number' within alternates Eric Blake
2015-09-24 14:36   ` Markus Armbruster
2015-09-24 16:00     ` Eric Blake
2015-09-24 16:29       ` Markus Armbruster
2015-09-25 22:32         ` Eric Blake
2015-09-28  9:26           ` Markus Armbruster
2015-09-25 22:50         ` Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 06/46] qapi: Improve 'include' error message Eric Blake
2015-09-24 14:39   ` Markus Armbruster
2015-09-24 16:04     ` Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call Eric Blake
2015-09-24 14:58   ` Markus Armbruster
2015-09-24 16:14     ` Eric Blake
2015-09-26 21:05       ` Eric Blake
2015-09-28  9:14         ` Markus Armbruster
2015-10-06 21:10           ` [Qemu-devel] [RFC PATCH] qapi: split visit_end_struct() into pieces Eric Blake
2015-10-07 12:00             ` Markus Armbruster
2015-10-07 13:08               ` Markus Armbruster
2015-10-07 14:57               ` Eric Blake
2015-10-07 15:23                 ` Markus Armbruster
2015-09-26 21:41     ` [Qemu-devel] [PATCH v5 07/46] qapi: Don't pass pre-existing error to later call Eric Blake
2015-09-27  2:26       ` Eric Blake
2015-09-28  9:24       ` Markus Armbruster
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 08/46] qapi: Reuse code for flat union base validation Eric Blake
2015-09-25 16:30   ` Markus Armbruster
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 09/46] qapi: Use consistent generated code patterns Eric Blake
2015-09-25 16:54   ` Markus Armbruster
2015-09-25 19:06     ` Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 10/46] qapi: Merge generation of per-member visits Eric Blake
2015-09-28  6:17   ` Markus Armbruster
2015-09-28 15:40     ` Eric Blake
2015-09-29  7:37       ` Markus Armbruster
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 11/46] qapi: Don't use info as witness of implicit object type Eric Blake
2015-09-28 12:43   ` Markus Armbruster
2015-09-29  3:58     ` Eric Blake
2015-09-29  7:51       ` Markus Armbruster
2015-09-30  4:13         ` [Qemu-devel] [RFC PATCH] qapi: Use callback to determine visit filtering Eric Blake
2015-10-01  6:12           ` Markus Armbruster
2015-10-01 14:09             ` Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 12/46] qapi: Track location that created an implicit type Eric Blake
2015-09-28 12:56   ` Markus Armbruster
2015-09-29  4:03     ` Eric Blake
2015-09-29  8:02       ` Markus Armbruster
2015-09-30 16:02         ` Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 13/46] qapi: Track owner of each object member Eric Blake
2015-09-30 16:06   ` Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 14/46] qapi: Detect collisions in C member names Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 15/46] qapi: Defer duplicate member checks to schema check() Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 16/46] qapi: Detect base class loops Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 17/46] qapi: Provide nicer array names in introspection Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 18/46] qapi-introspect: Guarantee particular sorting Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 19/46] qapi: Simplify visiting of alternate types Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 20/46] qapi: Fix alternates that accept 'number' but not 'int' Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 21/46] qmp: Fix reference-counting of qnull on empty output visit Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 22/46] qapi: Don't abuse stack to track qmp-output root Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 23/46] qapi: Remove dead visitor code Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 24/46] qapi: Document visitor interfaces Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 25/46] qapi: Plug leaks in test-qmp-input-visitor Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 26/46] qapi: Test failure in middle of array parse Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 27/46] qapi: Simplify visits of optional fields Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 28/46] qapi: Rework deallocation of partial struct Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 29/46] qapi: Change visit_type_FOO() to no longer return partial objects Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 30/46] net: use Netdev instead of NetClientOptions in client init Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 31/46] qapi: use 'type' in generated C code to match QMP union wire form Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 32/46] qapi: Hide tag_name data member of variants Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 33/46] vnc: hoist allocation of VncBasicInfo to callers Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 34/46] qapi: Unbox base members Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 35/46] qapi-visit: Remove redundant functions for flat union base Eric Blake
2015-09-23 20:55   ` Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 36/46] qapi: Avoid use of 'data' member of qapi unions Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 37/46] qapi: Forbid empty unions and useless alternates Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 38/46] qapi: Drop useless 'data' member of unions Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 39/46] qapi: Plumb in 'box' to qapi generator lower levels Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 40/46] qapi: Implement boxed structs for commands/events Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 41/46] qapi: Support boxed unions Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 42/46] qapi: support implicit structs in OptsVisitor Eric Blake
2015-09-21 21:57 ` [Qemu-devel] [PATCH v5 43/46] qapi: Change Netdev into a flat union Eric Blake
2015-09-21 21:58 ` [Qemu-devel] [PATCH v5 44/46] net: Use correct type for bool flag Eric Blake
2015-09-21 21:58 ` [Qemu-devel] [PATCH v5 45/46] net: Complete qapi-fication of netdev_add Eric Blake
2015-09-23 15:40   ` Paolo Bonzini
2015-09-23 16:37     ` Eric Blake
2015-09-25 16:48       ` Paolo Bonzini
2015-09-28  9:31         ` Markus Armbruster
2015-09-28 11:29           ` Paolo Bonzini
2015-09-21 21:58 ` [Qemu-devel] [PATCH v5 46/46] qapi: Allow anonymous base for flat union Eric Blake
2015-09-23 20:59   ` Eric Blake
2015-09-28 13:07 ` [Qemu-devel] [PATCH v5 00/46] post-introspection cleanups, and qapi-ify netdev_add Markus Armbruster
2015-09-29  3:43   ` Eric Blake

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.