From 7b830b72e3e421f5ffa49054f7be4e2e826b5be6 Mon Sep 17 00:00:00 2001 From: Markus Habert Date: Tue, 29 Aug 2023 16:06:28 +0200 Subject: [PATCH 1/7] Fix std::nothrow aborting when exceptions are disabled (emscripten-core#20132) --- system/lib/libcxx/src/new.cpp | 29 +++++++++++++++++++++++++++++ test/core/test_nothrow_new.cpp | 18 ++++++++++++++++++ test/core/test_nothrow_new.out | 1 + test/test_core.py | 12 ++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 test/core/test_nothrow_new.cpp create mode 100644 test/core/test_nothrow_new.out diff --git a/system/lib/libcxx/src/new.cpp b/system/lib/libcxx/src/new.cpp index cfef148f056cc..2540dbe2fe9e5 100644 --- a/system/lib/libcxx/src/new.cpp +++ b/system/lib/libcxx/src/new.cpp @@ -90,10 +90,34 @@ operator new(std::size_t size) _THROW_BAD_ALLOC return p; } +#if defined(__EMSCRIPTEN__) && defined(_LIBCPP_NO_EXCEPTIONS) +void* _new_nothrow(size_t size) noexcept +{ + /// We cannot call ::operator new(size) here because it would abort + /// when malloc returns 0 and exceptions are disabled. + /// Expected behaviour of std::nothrow is not return 0 in that case. + void* p = nullptr; + if (size == 0) + size = 1; + while ((p = ::malloc(size)) == nullptr) + { + std::new_handler nh = std::get_new_handler(); + if (nh) + nh(); + else + break; + } + return p; +} +#endif + _LIBCPP_WEAK void* operator new(size_t size, const std::nothrow_t&) noexcept { +#if defined(__EMSCRIPTEN__) && defined(_LIBCPP_NO_EXCEPTIONS) + return _new_nothrow(size); +#else void* p = nullptr; #ifndef _LIBCPP_NO_EXCEPTIONS try @@ -107,6 +131,7 @@ operator new(size_t size, const std::nothrow_t&) noexcept } #endif // _LIBCPP_NO_EXCEPTIONS return p; +#endif // __EMSCRIPTEN__ && _LIBCPP_NO_EXCEPTIONS } _LIBCPP_WEAK @@ -120,6 +145,9 @@ _LIBCPP_WEAK void* operator new[](size_t size, const std::nothrow_t&) noexcept { +#if defined(__EMSCRIPTEN__) && defined(_LIBCPP_NO_EXCEPTIONS) + return _new_nothrow(size); +#else void* p = nullptr; #ifndef _LIBCPP_NO_EXCEPTIONS try @@ -133,6 +161,7 @@ operator new[](size_t size, const std::nothrow_t&) noexcept } #endif // _LIBCPP_NO_EXCEPTIONS return p; +#endif // __EMSCRIPTEN__ && _LIBCPP_NO_EXCEPTIONS } _LIBCPP_WEAK diff --git a/test/core/test_nothrow_new.cpp b/test/core/test_nothrow_new.cpp new file mode 100644 index 0000000000000..062334d59b781 --- /dev/null +++ b/test/core/test_nothrow_new.cpp @@ -0,0 +1,18 @@ +// Copyright 2016 The Emscripten Authors. All rights reserved. +// Emscripten is available under two separate licenses, the MIT license and the +// University of Illinois/NCSA Open Source License. Both these licenses can be +// found in the LICENSE file. + +#include +#include + +int main() { + const char* data = new (std::nothrow) char[20 * 1024 * 1024]; + if (data == nullptr) { + std::cout << "success" << std::endl; + return 0; + } else { + std::cout << "failure" << std::endl; + return 1; + } +} diff --git a/test/core/test_nothrow_new.out b/test/core/test_nothrow_new.out new file mode 100644 index 0000000000000..2e9ba477f89e8 --- /dev/null +++ b/test/core/test_nothrow_new.out @@ -0,0 +1 @@ +success diff --git a/test/test_core.py b/test/test_core.py index 6465bef50af93..5198451758d6c 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -2434,6 +2434,18 @@ def test_aborting_new(self, args): self.emcc_args += args self.do_core_test('test_aborting_new.cpp') + @parameterized({ + 'nogrow': (['-sABORTING_MALLOC=0'],), + 'grow': (['-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=18MB'],) + }) + @no_asan('requires more memory when growing') + @no_lsan('requires more memory when growing') + @no_4gb('depends on MAXIMUM_MEMORY') + @no_2gb('depends on MAXIMUM_MEMORY') + def test_nothrow_new(self, args): + self.emcc_args += args + self.do_core_test('test_nothrow_new.cpp') + @no_wasm2js('no WebAssembly.Memory()') @no_asan('ASan alters the memory size') @no_lsan('LSan alters the memory size') From d709d63e584a08b5a8b328377a704e3684448bcf Mon Sep 17 00:00:00 2001 From: Markus Habert Date: Wed, 30 Aug 2023 10:54:59 +0200 Subject: [PATCH 2/7] Fix test optimized away --- test/core/test_nothrow_new.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/core/test_nothrow_new.cpp b/test/core/test_nothrow_new.cpp index 062334d59b781..32e7a9d0fcbd7 100644 --- a/test/core/test_nothrow_new.cpp +++ b/test/core/test_nothrow_new.cpp @@ -6,8 +6,10 @@ #include #include +char* data; + int main() { - const char* data = new (std::nothrow) char[20 * 1024 * 1024]; + data = new (std::nothrow) char[20 * 1024 * 1024]; if (data == nullptr) { std::cout << "success" << std::endl; return 0; From 5577066669b7c74f5077c110df1f3b8ab94747e0 Mon Sep 17 00:00:00 2001 From: Markus Habert Date: Wed, 30 Aug 2023 10:58:24 +0200 Subject: [PATCH 3/7] Explicitly set ABORTING_MALLOC in test --- test/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_core.py b/test/test_core.py index 5198451758d6c..d759ffe0c95cc 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -2436,7 +2436,7 @@ def test_aborting_new(self, args): @parameterized({ 'nogrow': (['-sABORTING_MALLOC=0'],), - 'grow': (['-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=18MB'],) + 'grow': (['-sABORTING_MALLOC=0', '-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=18MB'],) }) @no_asan('requires more memory when growing') @no_lsan('requires more memory when growing') From 6daf352ab74677a4857f8be72e4ae8a5f8c46961 Mon Sep 17 00:00:00 2001 From: Markus Habert Date: Wed, 30 Aug 2023 11:16:10 +0200 Subject: [PATCH 4/7] Fix copyright date --- test/core/test_nothrow_new.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/core/test_nothrow_new.cpp b/test/core/test_nothrow_new.cpp index 32e7a9d0fcbd7..eb3c195d403c3 100644 --- a/test/core/test_nothrow_new.cpp +++ b/test/core/test_nothrow_new.cpp @@ -1,4 +1,4 @@ -// Copyright 2016 The Emscripten Authors. All rights reserved. +// Copyright 2023 The Emscripten Authors. All rights reserved. // Emscripten is available under two separate licenses, the MIT license and the // University of Illinois/NCSA Open Source License. Both these licenses can be // found in the LICENSE file. From 256edb351e4f297ccc053e1ed543be4706bffff5 Mon Sep 17 00:00:00 2001 From: Markus Habert Date: Fri, 13 Oct 2023 10:09:00 +0200 Subject: [PATCH 5/7] Update changelog and add documentation to settings.js --- ChangeLog.md | 3 +++ src/settings.js | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 50c30dbdac539..18ed0bf5af2da 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -30,6 +30,9 @@ See docs/process.md for more on how version tagging works. incoming module but forget to include them in `-sINCOMING_MODULE_API` will see an error in debug builds so this change will not generate any silent failures. +- If exceptions are disabled, using `new` together with `std::nothrow` no + longer aborts if the allocation fails. Instead `nullptr` is returned now. + This does not change the behavior of regular usage of `new`. 3.1.45 - 08/23/23 ----------------- diff --git a/src/settings.js b/src/settings.js index 8b01ee484b8c3..f97a8b4f9245c 100644 --- a/src/settings.js +++ b/src/settings.js @@ -140,6 +140,10 @@ var MALLOC = "dlmalloc"; // which it did not previously. If you don't want that, just stop passing // it in at link time. // +// Note that this setting does not affect the behavior of operator new in C++. +// This function will always abort on allocation failure if exceptions are disabled. +// If you want new to return 0 on failure, use it with std::nothrow. +// // [link] var ABORTING_MALLOC = true; From 8e5c70053c2be2e7079ed955bacbed8b0556e4d3 Mon Sep 17 00:00:00 2001 From: Markus Habert Date: Tue, 17 Oct 2023 10:20:14 +0200 Subject: [PATCH 6/7] Move changelog entry to correct section --- ChangeLog.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 774d1e75bd7dd..4cb5f674ecf73 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -24,6 +24,9 @@ See docs/process.md for more on how version tagging works. developers and helps take a care of post-checkout tasks such as `npm install`. If this script needs to be run (e.g. becuase package.json was changed, emcc will exit with an error. (#19736) +- If exceptions are disabled, using `new` together with `std::nothrow` no + longer aborts if the allocation fails. Instead `nullptr` is returned now. + This does not change the behavior of regular usage of `new`. 3.1.47 - 10/09/23 ----------------- @@ -66,9 +69,6 @@ See docs/process.md for more on how version tagging works. incoming module but forget to include them in `-sINCOMING_MODULE_API` will see an error in debug builds so this change will not generate any silent failures. -- If exceptions are disabled, using `new` together with `std::nothrow` no - longer aborts if the allocation fails. Instead `nullptr` is returned now. - This does not change the behavior of regular usage of `new`. - JS library decorators such as `__deps` and `__async` are now type checked so that errors are not silently ignored. - The `USE_GLFW` settings now defaults to 0 rather than 2. This matches other From 2128695a114951193803c91eac059f36e6a45558 Mon Sep 17 00:00:00 2001 From: Markus Habert <147392030+imfusion-habert@users.noreply.github.com> Date: Thu, 19 Oct 2023 18:08:39 +0200 Subject: [PATCH 7/7] Fix typo in comment Co-authored-by: Ingvar Stepanyan --- system/lib/libcxx/src/new.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/lib/libcxx/src/new.cpp b/system/lib/libcxx/src/new.cpp index 2540dbe2fe9e5..b653a97a6743b 100644 --- a/system/lib/libcxx/src/new.cpp +++ b/system/lib/libcxx/src/new.cpp @@ -95,7 +95,7 @@ void* _new_nothrow(size_t size) noexcept { /// We cannot call ::operator new(size) here because it would abort /// when malloc returns 0 and exceptions are disabled. - /// Expected behaviour of std::nothrow is not return 0 in that case. + /// Expected behaviour of std::nothrow is to return 0 in that case. void* p = nullptr; if (size == 0) size = 1;