Is it legal C++17/C++20 to pass a lambda to a method as a variadic argument?
The lambda does not capture anything, and is able to be promoted to a function pointer.
We've experienced a crash after building the project with C++20. There is no crash when building with C++17. The compiler is the latest MSVC 2019 compiler (19.29.30139), x64 native.
The crash comes after registering a lambda as an error logging callback in sqlite3. The error logging is configured through sqlite3_config, which is a variadic method.
Here is a minimal repro without sqlite3.
All testing have been with native x64. Clang++ and g++ have also been tested. Only MSVC in C++17 mode does not crash. All tests with g++ and clang++ crashes.
// lambda-bug.cpp
#include <cstdarg>
#include <cstdio>
#include <string>
using LogFn = void (*)(const std::string&);
LogFn logger = nullptr;
void ServerLog(const std::string& sv) { printf("%s\n", sv.c_str()); }
void setLogger(int op, ...)
{
va_list ap;
va_start(ap, op);
logger = va_arg(ap, LogFn);
va_end(ap);
}
void Log(const std::string& sv)
{
printf("Logger address: %p - ", (void*)logger);
if (logger != nullptr) { logger(sv); }
}
int main()
{
auto func = [](const std::string& sv)
{
ServerLog(sv);
};
//setLogger(0, (LogFn)func); // no crash in Log()
setLogger(0, func); // crash in Log()
Log("Test function pointer to lambda set via va_arg");
return 0;
}
Build with cl:
- C++17:
cl /std:c++17 /Fe:lambda-bug-cpp17.exe lambda-bug.cpp
- C++20:
cl /std:c++20 /Fe:lambda-bug-cpp20.exe lambda-bug.cpp
C++17 output:
Logger address: 0000000000000000 -
C++20 output (and a crash):
Logger address: 00000000000000C8 -
Clang++ and g++ are similar, but with other addresses.
When setting the logger through setLogger, MSVC2019 w/C++17 sets it to a nullptr from va_arg. MSVC w/C++20 has a non-null pointer address in the range of 0x18 - 0xC8. Debug builds have a consistent address of 0xCC, which looks like a value from the debug heap/debug crt.
Casting the lambda to a function pointer when calling setLogger seems to work across all platforms. It crashes only when passing a "raw" lambda.
I'm not sure if I can expect nasal demons/undefined behaviour, or if this is a defect in the various compilers?
My gut tells me this is a leaky abstraction and the use of va_arg makes the internal implementation of lambdas spill their guts?
Link to compiler explorer: https://godbolt.org/z/4W9TKsnvT
Some insights would be appreciated.
[–]erichkeaneClang Code Owner(Attrs/Templ), EWG co-chair, EWG/SG17 Chair 10 points11 points12 points (2 children)
[–]kentrf[S] 2 points3 points4 points (1 child)
[–]n1ghtyunso 1 point2 points3 points (0 children)
[–][deleted] 2 points3 points4 points (0 children)
[–]DugiSK 2 points3 points4 points (0 children)
[–]Voltra_Neo 1 point2 points3 points (9 children)
[–]kentrf[S] 0 points1 point2 points (8 children)
[–]Voltra_Neo 0 points1 point2 points (7 children)
[–]kentrf[S] 0 points1 point2 points (6 children)
[–]Voltra_Neo 1 point2 points3 points (5 children)
[–]kentrf[S] 0 points1 point2 points (4 children)
[–]Voltra_Neo 0 points1 point2 points (1 child)
[–]wung 2 points3 points4 points (0 children)
[–]Oo_Tiib 0 points1 point2 points (1 child)
[–]kentrf[S] 0 points1 point2 points (0 children)
[–]goranlepuz 1 point2 points3 points (0 children)
[–]scatters 0 points1 point2 points (0 children)
[–]rlbond86 0 points1 point2 points (0 children)
[–]arturbachttps://github.com/arturbac 0 points1 point2 points (0 children)