-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
UB due to incompatible function pointer types #2
Comments
Yes, it is undefined behaviour: https://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type. How can then we get around this except for accepting |
Very valid point. Regarding the warning about incompatible types, that is indeed a warning I'd expect - to make sure the user is using the "correct" function type. However the types in the interface and the type asked from the user are certainly formally incompatible. Though I believe you were looking for this excerpt-
I think Regardless, it's correct that the function types are incompatible since
In general, the casting itself is perfectly allowed, calling it is UB (which this design does), as you've already noted-
This feels rather muddy, in the context of what the standard mandates and what may actually happen in most (if not all) implementations. On one hand, I think the design needs some way to have user facing type safety - on the other hand, I can't think of a way to perform the cast back due to the generic nature of the function call. This needs to be noted in the design doc itself - I'll add that. |
Sure, most implementations work in the same way here. However, in the above SO link, people suggested some platforms on which this trick doesn't work (OpenWatcom, Emscripten LLVM to Javascript, etc.), and moreover, it implies that even if it would be fixed, it can emerge on other platforms in future as well, and it would be cumbersome to figure out what's wrong with the code. Sometimes it is really unfortunate that the standard paper and the real world are two different things. |
I noticed there's a naïve solution to this. The user provided function could be wrapped in another function that accepts Something like this is what the #define impl_show(T, Name, show_f) \
static inline char* CONCAT(show_f, __)(void* self) \
{ \
char* (*const show_)(T* self) = (show_f); \
(void)show_; \
return show_f(self); \
} \
Show Name(T* x) \
{ \
static ShowTC const tc = { .show = (CONCAT(show_f, __)) }; \
return (Show){ .tc = &tc, .self = x }; \
} One wrapper function would be needed for every typeclass function, the type checking is now moved inside the wrapper functions. A whole lot of boilerplate for the definer. But hopefully a good experience for the implementer. I implemented these changes in the ub-fix branch. I'd appreciate it if you could check it out as I may have overlooked something. Going back to the disadvantage - even more boilerplate for the definer. I think if information about the typeclass functions are persisted in an arg list-esque structure - meta macros could potentially be used to automate the writing of those wrapper functions. One concern that came across to me immediately, though, is that |
The approach you're suggesting works but, as you've mentioned, it has a drawback concerning boilerplate on the definer's side (it already has a lot of boilerplate). It could be eliminated with meta-macros but really shouldn't because an interface function definition would look like this (macros need parentheses and commas to distinguish syntactical elements): iFn(void, set, (void *, self), (int, x)); instead of this: iFn(void, set, void *self, int x); Moreover, the case with iFnVoid(set, (void *, self), (int, x)); All this machinery complicates everything, from maintaining to the learning curve, and furthermore, looks less natural to C. My work on Poica has shown that this is a stillborn approach. Your branch |
Interface function implementations accept
T *
as a first parameter, whereas a corresponding function in a virtual table acceptsvoid *
, thereby making these two function types incompatible (godbolt).Moreover (
6.2.7 Compatible type and composite type
):And I am curious about this code:
Here,
.func_name
andT_func_name
are two declarations to the same function having incompatible types. Then we castT_func_name
to the type acceptingvoid *
as a first parameter; we cast incompatible types. Does it even follow the standard, or it is UB?The C99 draft standard: http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
The text was updated successfully, but these errors were encountered: