[PATCH v3] libctf: ctf_member_next needs to return (ssize_t)-1 on error

Nick Alcock nick.alcock@oracle.com
Tue Oct 3 20:53:44 GMT 2023


This is a really fiddly horror show, isn't it...

On 3 Oct 2023, Torbjorn SVENSSON outgrape:

> On 2023-10-02 12:57, Nick Alcock wrote:
>> On 29 Sep 2023, Torbjorn SVENSSON verbalised:
>> 
>>> On 2023-09-28 18:41, Nick Alcock wrote:
>> Ack. This is the problem area, because the interface contract for libctf
>> specifies (or would if it were written down anywhere) that you can test
>> all signed returns for error via < 0 and all unsigned ones (like
>> ctf_id_t) via == CTF_ERR, and CTF_ERR... has a cast in it.
>
> I don't think this is true with the current logic in place.
> On a signed integral function, it could be either -1 (negative one) or, if the data type of the called function is wide enough,
> CTF_ERR.

Oh. That needs fixing then -- there's no other way for the caller to
tell what to test, and honestly even '-1 for signed, CTF_ERR for
unsigned and ctf_id_t, == NULL for pointers' is complex enough that it's
a source of errors all on its own :(

>>> In my mind, I see it as we need 2 different implementations. One that
>>> returns a simple -1 and another one that casts it for all unsigned
>>> calls, but maybe I'm oversimplifying this.
>> I suppose if it was always done *at the return site* so the compiler
>> could always see the return type properly, it would always get the cast
>> right: my worry is the CTF_ERR at the test site, in the user code we
>> cannot affect (except by changing CTF_ERR).
>
> I suppose so, but does it really matter when we cannot do this part?

I was assuming that a *macro*

#define ctf_set_errno(fp, err) do {
    (fp)->ctf_errno = (err);
    return -1;
} while (0);

would return the right thing automatically -- but oh ugh that's a
behaviour change, ctf_set_errno does not do a return currently. That
won't work. Fixing that to get a ctf_set_errno macro to return a
suitable value requires the statement-expression extension which of
course we cannot rely on being present in binutils :( so a
ctf_set_errno macro is untenable. Dammit.

> Or are you thinking about just having the ctf_set_errno function
> always return -1 (negative one) and let the caller do the automagic
> conversion?

I was assuming that the callee would do it.

>             If so, I think the idiom with return_value == CTF_ERR is
> going to fail.

Oh wait, I see -- if some return types are wider than unsigned long,
CTF_ERR is not going to equal -1 for comparisons against that type. Ew.
And of course what that type is is platform-dependent.

Maybe we *can* get away with using uintmax_t in the definition of
CTF_ERR?

>>> To make things easier, I would actually consider just having the
>>> assign statement and the return statement inlined (without a macro or
>>> inline function) directly where they are supposed to be.
>>>
>>> Would you be open to removing the ctf_set_errno() function completely
>>> and just expand it to where it's called (almost like my v2 patch, but
>>> everywhere instead)?
>> Well, you could use a macro to make it less gross. All the code
>> duplication makes me wince a bit.
>
> How would a macro work for this?

Not sure. I'm casting about and not even writing enough test cases I'm
afraid. :(

I disproved the macro in this reply anyway (see above). Sorry, I'm
thinking very slowly right now.

> Consider that a function returns a char*, in this case, neither CTF_ERR, nor -1 should be returned, but instead NULL. Using a macro
> would make this problematic to differentiate.

Ah yes, pointers are the third case. Obviously NULL == error for those.

> A possibility could be to have a macro like:
>
> #define CTF_SET_ERRNO(fp,err) do { fp->ctf_errno = err; } while(0)
>
> but what's the point?
> Is it better to write:
>
> if (some_failure_condition)
>   {
>     CTF_SET_ERRNO(fp, ENOMEM);
>     return -1; // or CTF_ERR, NULL, ..., depending on the function
>   }
>
> instead of:
>
> if (some_failure_condition)
>   {
>     fp->ctf_errno = ENOMEM;
>     return -1; // or CTF_ERR, NULL, ..., depending on the function
>   }

I'd say stick with the fp->ctf_errno assignment, it's far clearer than a
SHOUTY MACRO that just does an assignment. The whole point of
ctf_set_errno() in the first place was to avoid the need for multiple
statements and thus a new block: if we need multiple statements anyway
because of return-type pain like this, we might as well dump the macro.

(assuming this works, because that's a lot of changes to stick you
with.)

>> But more problematic from my perspective is the implications for the
>> callers. We're trying to get either -1 or (some type)-1 to the callers
>> so they can error-check it in a consistent and not-too-horrifying
>> fashion, after all. (We could add a ctf_is_error() function or macro
>> that does whatever magic is needed if it's too baroque, but that would
>> be my least favourite option because it looks nothing like a
>> conditional.)
>
> Adding a ctf_is_error()-function would be one way to go, but it would be an API change while all the others I've been talking about
> is internal into the library without actually touching the interface.

That's exactly my objection too (also, it's clunky as hell). I really do
want to avoid that. My big fear here is that you've found some
fundamental problem with a -1 signed, CTF_ERR unsigned return (we can't
even change the definition of CTF_ERR, because it's baked into callers,
while libctf can change under them.)

>> I think we're still narrowing down the problem, really. -1 is such a
>> common error return, it's amazing how hard it is to make it work right :)
>
> So, based on all we have talk about so for, I'm leaning towards just
> having the 2 statements in all places where an error should be
> returned and have the change contained inside the library rather than
> making external changes required.

I think you're right.

> Then the question is, would the CTF_SET_ERRNO macro have anything to
> add? I.e. should we have it even if it's just setting the field or
> should we set the field directly? Regardless, the return statement
> needs to be kept outside that block.

I'd go for dumping CTF_SET_ERRNO: just switch to a scheme where we set
fp->ctf_errno and do a return. It adds a few more {} blocks but that's
unavoidable in the absence of statement expressions. (And half the
places we care about don't need new blocks anyway.)

> Please let me know what you think so that we can progress on this topic.
> I'm currently blocked by this point for the tool chain that I'm supposed to deliver.

Oh I'm sorry!

-- 
NULL && (void)


More information about the Binutils mailing list