I don't think this needs to be or should be defined in a public header. It was [added here](https://github.com/geany/geany/commit/721009e262e9bc279122566a60e14af0048d90...), I suspect in order to provide documentation for it. Unless I'm mistaken, this function needn't be forward declared as it's just pulled out of the plugin DSOs using `g_module_get_symbol()`.
The reason it's an issue is two-fold. One is because it appears as though it's a public function which plugins should be able to call. More problematically, it prevents plugins from declaring it in their own way which is still compatible with the calling convention. For example, in C++ plugins, it prevents add a `noexcept` or similar specifiers because the (unused) forward declaration in the header doesn't have the same signature as the actual implementation.
I propose to move it to one of the *.dox files in the documentation directory so that it can still be documented but prevent interfering with plugins.
I guess for plain C plugins, it might be useful to have the forward declaration to get warnings about mismatch signature, but it seems a little harsh to force it if not required.
Well, the reason we have this like we do for old-style `plugin_*()` is specifically to check for compatibility.
It's not very nice if *really* compatible things are deemed not, but if the compiler thinks it's incompatible, is it really not? Also, could you propose another way to make sure the signature is correct, or at least try and warn if it isn't?
As for C, are there really any pragmas or attributes, is there really some that would lead to incompatibility of the prototype here, while actually not changing how the function need be called?
Well, the intention of adding `noexcept` is to prevent exceptions escaping from C++ into C.
The REAL problem with exceptions is is when one passes through some C code and back into C++ which catches it and continues to execute. Who knows what state the data being manipulated by the C code will be in since it will never complete its execution.
If an exception tries to exit a noexcept function its an automatic terminate which tells the programmer they screwed up.
The REAL problem with exceptions is is when one passes through some C code and back into C++ which catches it and continues to execute.
That's not the case here, as this is only directly called by Geany, right?
If an exception tries to exit a noexcept function its an automatic terminate which tells the programmer they screwed up.
Not perfect, but indeed better than corrupting the call stack.
BTW, can really C-linkage stuff throw exceptions? Sure it's somewhat unrelated, but if it's meant to be called by C code it better not throw indeed.
That's not the case here, as this is only directly called by Geany, right?
I was thinking a proxy written in C++ could cause Geany to call it on a subplugin? (but I don't know the details of proxy and geany interaction).
BTW, can really C-linkage stuff throw exceptions? Sure it's somewhat unrelated, but if it's meant to be called by C code it better not throw indeed.
As best I can tell its UB. The `extern "C"` is defined to control calling convention and name mangling, nothing else. I would doubt the C++ compiler will unilaterally apply `noexcept` to `extern "c"` functions since its still ok to call them from C++ as well.
In fact a disaster may occur without even the need to go through C and back to C++. If the stack unwinding moves from C++ to C stack frames, will it see frames without handlers, or will it see some random part of the C frame as specifying an exception handler and do "interesting" things? UB.
Its just very important to prevent all exceptions from exiting C++ into C, one of the rare places where swallowing exceptions is probably acceptable.
I was thinking a proxy written in C++ could cause Geany to call it on a subplugin? (but I don't know the details of proxy and geany interaction).
Probably not, because `geany_load_module()` is the builtin Geany API, a proxy plugin is likely to have it own, and even then, only one place should call that function inside plugins.
As best I can tell its UB. […]
Yeah I'd imagine. Though, I'm wondering what's worse: not checking signature at all, or requiring authors of C++ plugins to carefully not throw any exception (from inside a function that is highly unlikely to contain much C++ code)? If really the author meant to add `noexcept`, she must then be aware of the problem and properly handle exceptions anyway, as IIUC all this is merely a runtime check that leads to program abortion.
An alternative solution could be something like this: ```C++ #ifdef __cplusplus /* >= 2011 */ gboolean geany_load_module(GeanyPlugin *plugin) noexcept; #else gboolean geany_load_module(GeanyPlugin *plugin); #endif ``` So authors of C++ plugins are forced to consider the issue. But that breaks C++ API.
If really the author meant to add noexcept, she must then be aware of the problem and properly handle exceptions anyway, as IIUC all this is merely a runtime check that leads to program abortion.
Its sadly easy to allow exceptions, they are slippery little things:
```c++ int f() try { the code; } catch(...){ now I must not allow anything to throw here; careful of IO, constructing objects, operators; sheesh, not much you CAN do; better just terminate, or return -1; } ```
The nice thing about the `noexcept` is it can catch exceptions from the `catch` clause too, so it would be good if it could be used.
So authors of C++ plugins are forced to consider the issue. But that breaks C++ API.
Though I wouldn't expect too many C++ plugins for the new API, yet, now is the best chance.
Though, I still wonder how this can change the ABI, especially in C linkage where there can't be no name mangling anyway.
I wouldn't think it would change the ABI, I would have expected the stack frame to have a flag saying this function is `noexcept` so the unwinder can see it and terminate. So its internal to the function. But it is implementation dependent, so who knows.
The nice thing about the `noexcept` is it can catch exceptions from the `catch` clause too, so it would be good if it could be used.
Yeah, but my point is that it's not really a solution, as it'll abort Geany altogether, not just the plugin. So a well-behaved plugin should manage it anyway. Maybe do: ```C++ extern "C" G_MODULE_EXPORT void geany_load_module (GeanyPlugin *plugin) { try { plugin->info->name = "great plugin"; plugin->info->description = "saves the world"; plugin->info->version = "42"; plugin->info->author = "Me";
plugin->funcs->init = great_plugin_init; plugin->funcs->cleanup = great_plugin_cleanup;
GEANY_PLUGIN_REGISTER (plugin, 225); } catch(...) { // not much to be done, and mustn't throw exceptions g_critical("My great plugin failed to register itself. Maybe it's not so great after all..."); } } ```
I wouldn't think it would change the ABI […]
It *has* not to change the ABI of the C decl, as the C caller won't know about it so won't do anything in either case. It still has to be able to call the function just the same.
Unless `noexcept` is the only thing in C/C++ (and compiler extensions/attributes) which must be declared on the definition rather than/in addition to the declaration, discussing it (and C++ exceptions) is kind of off-topic. I was pretty sure there were others, particularly GCC `__attribute__`s where the definition and declaration must match (ex. whatever G_GNUC_INTERNAL expands to seems to be like this in C++ at least, last I checked), but I could be wrong.
In any case, maybe we could simply offer a macro to guard out the forward declaration if the plugin opts to do so by defining the macro before including the header. Maybe something like `GEANY_DISABLE_PLUGIN_FORWARD_DECLS` or such.
@codebrainz all declarations and the definition need to have the same exception specification in C++.
To quote the surprisingly lucid standard clause "If any declaration of a function has an exception-specification that is not a noexcept-specification allowing all exceptions, all declarations, including the definition and any explicit specialization, of that function shall have a compatible exception-specification."
That was the OP wasn't it?
@b4n the standard requires an `extern "c"` function to have a C compatible ABI, and it does not disallow `noexcept` so if the compiler allows `noexcept` then it must not change the ABI. (or the compiler has a bug)
That was the OP wasn't it?
Yes, it was the specification I was trying to use when ran into this issue.
Probably the best solution is @b4n's one:
``` #if a good language like C++ extern "C" declarations with noexcept #else if only C simple declarations #end ```
That allows both languages to check the definition in the user plugin has the right type.
Probably the best solution is @b4n's one
No, it misses the point of the issue. It's not specific to `noexcept` and solving for that one specification only, among all current and future language specifications or compiler attributes and pragmas which could cause a similar problem doesn't really help in general.
IMO, it would be better to go with the preprocessor guard and let plugins remove the forward declaration completely if they know what they're doing. This is more general and allows plugins to workaround not just `noexcept` but also any other specifications/attributes/pragmas which could cause similar problems.
Ok, when given a choice, take both :)
``` #idndef yes_I_really_really_really_really_know_what_I_am_doing #if a good language like C++ extern "C" declarations with noexcept #else if only C simple declarations #endif #endif ```
github-comments@lists.geany.org