From a67364f35f5ef7e3c26148dc5985affe5fef0cf9 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 10 Jun 2025 10:02:52 -0700 Subject: [PATCH] Enable libc++ error message in debug builds Fixes: #24541 --- embuilder.py | 7 +++++++ system/lib/libcxx/include/__verbose_abort | 5 +++-- system/lib/libcxx/src/verbose_abort.cpp | 7 +++++++ test/test_other.py | 17 +++++++++++++++++ test/test_sanity.py | 10 +++++++--- tools/system_libs.py | 2 +- 6 files changed, 42 insertions(+), 6 deletions(-) diff --git a/embuilder.py b/embuilder.py index cf09de2ce0e30..80549f7407e27 100755 --- a/embuilder.py +++ b/embuilder.py @@ -54,6 +54,11 @@ 'libc++-wasmexcept', 'libc++-noexcept', 'libc++-ww-noexcept', + 'libc++-debug', + 'libc++-debug-wasmexcept', + 'libc++-debug-legacyexcept', + 'libc++-debug-noexcept', + 'libc++-debug-ww-noexcept', 'libal', 'libdlmalloc', 'libdlmalloc-tracing', @@ -106,6 +111,8 @@ 'libc++abi-debug-mt-noexcept', 'libc++-mt', 'libc++-mt-noexcept', + 'libc++-debug-mt', + 'libc++-debug-mt-noexcept', 'libdlmalloc-mt', 'libdlmalloc-mt-debug', 'libGL-emu', diff --git a/system/lib/libcxx/include/__verbose_abort b/system/lib/libcxx/include/__verbose_abort index d3db2afc8c6e2..c66a45ca378db 100644 --- a/system/lib/libcxx/include/__verbose_abort +++ b/system/lib/libcxx/include/__verbose_abort @@ -43,8 +43,9 @@ _LIBCPP_BEGIN_NAMESPACE_STD // make sure that the program terminates but without taking any complex dependencies in this header. #if !defined(_LIBCPP_VERBOSE_ABORT) -// XXX EMSCRIPTEN __libcpp_verbose_abort creases code size too much -# if !_LIBCPP_AVAILABILITY_HAS_VERBOSE_ABORT || defined (__EMSCRIPTEN__) +// XXX EMSCRIPTEN avoid __libcpp_verbose_abort in release builds due to code +// size +# if !_LIBCPP_AVAILABILITY_HAS_VERBOSE_ABORT || (defined(__EMSCRIPTEN__) && defined(NDEBUG)) // The decltype is there to suppress -Wunused warnings in this configuration. void __use(const char*, ...); # define _LIBCPP_VERBOSE_ABORT(...) (decltype(::std::__use(__VA_ARGS__))(), __builtin_abort()) diff --git a/system/lib/libcxx/src/verbose_abort.cpp b/system/lib/libcxx/src/verbose_abort.cpp index fd6bc4943d6ba..6a6d76bcdcd9f 100644 --- a/system/lib/libcxx/src/verbose_abort.cpp +++ b/system/lib/libcxx/src/verbose_abort.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #ifdef __BIONIC__ # include @@ -30,6 +31,12 @@ _LIBCPP_WEAK void __libcpp_verbose_abort(char const* format, ...) _LIBCPP_VERBOS va_list list; va_start(list, format); std::vfprintf(stderr, format, list); + // TODO(sbc): Add newline here unconditionally. libc++ seems inconsistent about strings + // passed to __libcpp_verbose_abort. The _LIBCPP_VERBOSE_ABORT macro seems to never use + // newlines, but _LIBCPP_ASSERTION_HANDLER does include a newline. + if (format[strlen(format) - 1] != '\n') { + std::fprintf(stderr, "\n"); + } va_end(list); } diff --git a/test/test_other.py b/test/test_other.py index 8900a5299776e..a9f8dff8fec06 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -16219,3 +16219,20 @@ def test_unsupported_min_version_when_unsupported_env(self, env): }) def test_automatic_env_worker(self, env, emcc_args): self.emcc(test_file('hello_world.c'), [f'-sENVIRONMENT={env}'] + emcc_args) + + def test_libcxx_errors(self): + create_file('main.cpp', ''' + #include + void func() { + } + + int main() { + std::thread t(func); + t.join(); + } + ''') + + # Since we are building without -pthread the thread constructor will fail, + # and in debug mode at least we expect to see the error message from libc++ + expected = 'system_error was thrown in -fno-exceptions mode with error 6 and message "thread constructor failed"' + self.do_runf('main.cpp', expected, assert_returncode=NON_ZERO) diff --git a/test/test_sanity.py b/test/test_sanity.py index 0386b8720b075..322c5994402c9 100644 --- a/test/test_sanity.py +++ b/test/test_sanity.py @@ -397,13 +397,17 @@ def test_emcc_caching(self): # Building a file that *does* need something *should* trigger cache # generation, but only the first time - libname = cache.get_lib_name('libc++.a') for i in range(3): print(i) self.clear() output = self.do([EMCC, '-O' + str(i), test_file('hello_libcxx.cpp'), '-sDISABLE_EXCEPTION_CATCHING=0']) - print('\n\n\n', output) - self.assertContainedIf(BUILDING_MESSAGE % libname, output, i == 0) + if i == 0: + libname = cache.get_lib_name('libc++-debug.a') + else: + libname = cache.get_lib_name('libc++.a') + # -O0 and -O1 will each build a version of libc++.a, but higher level will re-use the + # one built at -O1. + self.assertContainedIf(BUILDING_MESSAGE % libname, output, i < 2) self.assertContained('hello, world!', self.run_js('a.out.js')) self.assertExists(cache.cachedir) self.assertExists(os.path.join(cache.cachedir, libname)) diff --git a/tools/system_libs.py b/tools/system_libs.py index b83a50f4708a2..1d5c09bcbba4b 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -1665,7 +1665,7 @@ def get_files(self): filenames=filenames) -class libcxx(ExceptionLibrary, MTLibrary): +class libcxx(ExceptionLibrary, MTLibrary, DebugLibrary): name = 'libc++' cflags = [