Skip to content
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

Compiler should issue a warning when passing function pointers not marked noexcept to functions where throwing an exception in a callback function is undefined behavior #121427

Open
ryao opened this issue Jan 1, 2025 · 14 comments
Labels
clang:diagnostics New/improved warning or error message in Clang, but not in clang-tidy or static analyzer

Comments

@ryao
Copy link

ryao commented Jan 1, 2025

The unity blog describes an interesting bug that occurred on Windows:

https://unity.com/blog/engine-platform/debugging-memory-debugging-memory-corruption-who-wrote-2-into-my-stack-who-the-hell

Thread A blocked in select(), which is a wrapper around WaitForSingleObjectEx(). Thread B called QueueUserAPC(), which interrupted Thread A to run a callback function. The callback function then threw an exception, causing the stack to unwind, while the kernel had yet to respond to the select() call. When it finally did respond to the select call, the stack frame was gone due to C++ stack unwinding, and WAIT_TIMEOUT (0x00000102L) was written to the stack, causing stack corruption.

QueueUserAPC() is a C ABI function and thus invoking a C++ exception in it triggers undefined behavior. The compiler should issue a warning when passing a function pointer not marked noexcept to a C ABI function, but does not:

https://godbolt.org/z/7hKnrW1xe

Similarly, the compiler should warn about passing a function pointer not marked noexcept to a function marked noexcept, but does not:

https://godbolt.org/z/bMab4a4YG

There is also a bug filed with GCC, which similarly does not issue a warning:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118263

@safocl
Copy link

safocl commented Jan 1, 2025

Similarly, the compiler should warn about passing a function pointer not marked noexcept to a function marked noexcept, but does not:

https://godbolt.org/z/sP7YM1xYT -- you are not marked a functiont pointer with noexcept specifier -- with it:

<source>:14:5: error: no matching function for call to 'noexceptFunction'
   14 |     noexceptFunction(nonNoexceptFunction); // Warning should happen here, but does not.
      |     ^~~~~~~~~~~~~~~~
<source>:4:6: note: candidate function not viable: no known conversion from 'void ()' to 'void (*)() noexcept' for 1st argument
    4 | void noexceptFunction(void (*func)() noexcept) noexcept {
      |      ^                ~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
Compiler returned: 1

@safocl
Copy link

safocl commented Jan 1, 2025

Similarly, the compiler should warn about passing a function pointer not marked noexcept to a function marked noexcept, but does not:

expected behavior is:

c++11 (N3337), c++14 (N4140) (except.spec p9):

Whenever an exception is thrown and the search for a handler ([except.handle]) encounters the outermost block of a function with an exception-specification that does not allow the exception, then,
— if the[exception-specification is a dynamic-exception-specification, the function std::unexpected() is called ([except.unexpected]),
— otherwise, the function std::terminate() is called ([except.terminate]).

c++17 (N4659), c++20 (N4868), c++23 (N4950) ("called" replace with "invoked") (except.spec p5) :

Whenever an exception is thrown and the search for a handler ([except.handle]) encounters the outermost block of a function with a non-throwing exception specification, the function std​::​terminate is invoked ([except.terminate])

@safocl
Copy link

safocl commented Jan 1, 2025

QueueUserAPC() is a C ABI function and thus invoking a C++ exception in it triggers undefined behavior. The compiler should issue a warning when passing a function pointer not marked noexcept to a C ABI function, but does not:

https://godbolt.org/z/7hKnrW1xe

The compiler should generate an error for the rule:

dcl.link p1

Two function types with different language linkages are distinct types even if they are otherwise identical

@frederick-vs-ja
Copy link
Contributor

frederick-vs-ja commented Jan 1, 2025

The compiler should generate an error for the rule:
Two function types with different language linkages are distinct types even if they are otherwise identical
here

This is CWG1555 which is unfortunately closed as NAD now. Most compilers don't make C and C++ language linkages differentiate function types, which is currently non-conforming.

I don't think compilers should change for this, as doing so will break too much code. Perhaps someone should write a paper targeting WG21/EWG to properly resolve CWG1555.

@frederick-vs-ja
Copy link
Contributor

QueueUserAPC() is a C ABI function and thus invoking a C++ exception in it triggers undefined behavior.

Actually, there's no such rule in C++ (or in C which can't specify anything for C++ exceptions). In a conforming C++ implementation (note that the -fno-exception option makes the implementation non-conforming), you can mark an extern "C" function noexcept(false) and exit the function via an exception.

@frederick-vs-ja frederick-vs-ja added clang:diagnostics New/improved warning or error message in Clang, but not in clang-tidy or static analyzer and removed new issue labels Jan 1, 2025
@safocl
Copy link

safocl commented Jan 1, 2025

I don't think compilers should change for this, as doing so will break too much code.

This is the same as Linus Torvald's "bug report" about the supposedly broken memcpy function in GCC. The linux kernel code had undefined behavior, but he thought that the compiler was the one with the bug. But I think that we shouldn't rely on "dirty" code bases, or at least make it possible for programmers who don't make bad code to have the compiler behave normally in accordance with the standard. Code that relies on non-standard behavior cannot claim to work normally.

upd. The lack of a compilation error in this case can lead to very hard to catch bugs when code relies on such overloads. I think compiler developers could consider this as well.

@ryao
Copy link
Author

ryao commented Jan 1, 2025

QueueUserAPC() is a C ABI function and thus invoking a C++ exception in it triggers undefined behavior.

Actually, there's no such rule in C++ (or in C which can't specify anything for C++ exceptions). In a conforming C++ implementation (note that the -fno-exception option makes the implementation non-conforming), you can mark an extern "C" function noexcept(false) and exit the function via an exception.

Is it reasonable to expect C headers to mark their functions noexcept(false) when that is not valid C?

@frederick-vs-ja
Copy link
Contributor

frederick-vs-ja commented Jan 1, 2025

I don't think compilers should change for this, as doing so will break too much code.

This is the same as Linus Torvald's "bug report" about the supposedly broken memcpy function in GCC. The linux kernel code had undefined behavior, but he thought that the compiler was the one with the bug. But I think that we shouldn't rely on "dirty" code bases, or at least make it possible for programmers who don't make bad code to have the compiler behave normally in accordance with the standard. Code that relies on non-standard behavior cannot claim to work normally.

upd. The lack of a compilation error in this case can lead to very hard to catch bugs when code relies on such overloads. I think compiler developers could consider this as well.

Ah, but when the code attempts to rely on standard behavior, it often doesn't compile (already reported in #15935).

When all of extern "C", function pointers, and overloads are involved, it's almost unavoidable to rely on whether C and C++ language linkages differentiate function types or not.

QueueUserAPC() is a C ABI function and thus invoking a C++ exception in it triggers undefined behavior.

Actually, there's no such rule in C++ (or in C which can't specify anything for C++ exceptions). In a conforming C++ implementation (note that the -fno-exception option makes the implementation non-conforming), you can mark an extern "C" function noexcept(false) and exit the function via an exception.

Is it reasonable to expect C headers to mark their functions noexcept(false) when that is not valid C?

extern "C" is merely about linkage and function types (the latter is rarely implemented, though). It's highly unconventional to make an extern "C" function throwing, but MS UCRT's __RTDynamicCast is already doing so.

@safocl
Copy link

safocl commented Jan 2, 2025

Ah, but when the code attempts to rely on standard behavior, it often doesn't compile (already reported in #15935).

does not compile precisely because it does not comply with the standard.
(#15935) such code should compile without errors.

@frederick-vs-ja
Copy link
Contributor

See also

Despite closing CWG1555 as NAD, EWG didn't seem to request implementations to change...

@shafik
Copy link
Collaborator

shafik commented Jan 2, 2025

@safocl please use latest draft when quoting and use > to quote not code block.

@safocl
Copy link

safocl commented Jan 2, 2025

please use latest draft when quoting

how latest draft to use? i used before from the latest released standard c++23

@frederick-vs-ja
Copy link
Contributor

frederick-vs-ja commented Jan 2, 2025

please use latest draft when quoting

how latest draft to use? i used before from the latest released standard c++23

You can use https://eel.is/c++draft.

But I'd say link to the latest draft is somehow unstable, and a released NXXXX draft might be preferred for stability.

@shafik
Copy link
Collaborator

shafik commented Jan 3, 2025

please use latest draft when quoting

how latest draft to use? i used before from the latest released standard c++23

You can use https://eel.is/c++draft.

But I'd say link to the latest draft is somehow unstable, and a released NXXXX draft might be preferred for stability.

It can be but it also includes issue resolutions and we are often dealing w/ core issues. If you are going to point to a specific standard, sometimes that makes sense then we should specify that but I would check if the wording has changed in the latest draft as well.

For most issues the latest draft works just fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:diagnostics New/improved warning or error message in Clang, but not in clang-tidy or static analyzer
Projects
None yet
Development

No branches or pull requests

4 participants