agapply misuse
The compiler has some opinions about calls to agapply
:
CC attr.lo
attr.c: In function ‘init_all_attrs’:
attr.c:534:37: warning: cast between incompatible function types from ‘void (*)(Agraph_t *)’ {aka ‘void (*)(struct Agraph_s *)’} to ‘void (*)(Agraph_t *, Agobj_t *, void *)’ {aka ‘void (*)(struct Agraph_s *, struct Agobj_s *, void *)’} [-Wcast-function-type]
534 | agapply(root, (Agobj_t *) root, (agobjfn_t) agraphattr_init,
| ^
...
CC node.lo
node.c: In function ‘agrelabel_node’:
node.c:245:39: warning: cast between incompatible function types from ‘void (*)(Agnode_t *, void *)’ {aka ‘void (*)(struct Agnode_s *, void *)’} to ‘void (*)(Agraph_t *, Agobj_t *, void *)’ {aka ‘void (*)(struct Agraph_s *, struct Agobj_s *, void *)’} [-Wcast-function-type]
245 | agapply(g, (Agobj_t *) n, (agobjfn_t) dict_relabel,
| ^
Pulling the thread on this, neither of these are false positives. The problem is that agobjfn_t
takes 3 parameters:
typedef void (*agobjfn_t) (Agraph_t * g, Agobj_t * obj, void *arg);
In the agraphattr_init
case, it only takes a single parameter:
void agraphattr_init(Agraph_t * g)
So using it in combination with agapply
is relying on the calling convention for a 1-parameter function and a 3-parameter function being compatible. While this is true on x86-64 and ARM, it is not true on all platforms.
The dict_relabel
case is even more curious. dict_relabel
takes 2 parameters, but where agraphattr_init
is assuming it can elide the second and third parameter, dict_relabel
is assuming it can elide the first:
static void dict_relabel(Agnode_t * n, void *arg)
I do not understand how this would work on any of the major platforms we currently support (x86-64/ARM × BSD/Linux/macOS/Windows). It looks to me like it would only ever work with a purely stack-based calling convention that indexed and pushed/popped the arguments last-to-first. Maybe this was written/tested on 32-bit x86? There, I believe the calling convention would make things work. But in contrast, the agraphattr_init
case would be busted on 32-bit x86.
Does anyone understand how/why any of this was intended to work? It is a bit mysterious to me.