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

abort() on malloc() failure in new with exceptions disabled #11079

Merged
merged 10 commits into from
May 5, 2020
Merged

Conversation

kripken
Copy link
Member

@kripken kripken commented May 4, 2020

When libc++/libc++abi are built with exceptions disabled, the
new implementation there does not throw an exception for
an error, but it also does nothing else. So new ends up returning
a zero if malloc did, which can break programs.

Technically libc++/libc++abi are doing a reasonable thing here,
just removing all exceptions-related code when exceptions are
disabled. The assumption is likely that a user program would
set a new_handler if an error is desired. For us, we have to change
this as our default mode is to have exceptions disabled, and we
don't want users to need to know they need to do anything.

This makes it abort instead. (Note that without growth this
happened to always work, since we abort on any failing allocation.
With growth enabled, though, malloc returns 0, and we end up
in this situation.)

Fixes #11042 As discussed there I also looked at the option of
installing a set_new_handler that does an abort. That ends up
increasing code size by a little (bit less than 1%) because if adds
a global constructor, a function to the table, and some memory
operations. It seems better to just modify new itself which avoids
all that.

@kripken kripken requested a review from sbc100 May 4, 2020 18:40
def test_memorygrowth_new_error(self):
# test that C++ new properly errors if we fail to malloc
self.emcc_args += ['-Wno-almost-asm', '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'MAXIMUM_MEMORY=18MB']
self.do_run_in_out_file_test('tests', 'core', 'test_memorygrowth_new_error')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does doesn't seem related to memory growth. Why not just not set any settings and leave the default 16Mb memory. The result should be the same.

Then we can just call this test something like: test_aborting_new? (The name "new error" makes it sounds to me like new is an adjective).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a comment - this is actually related to memory growth. When growth is disabled, we abort() in another code path (the code to grow memory looks very different in the two cases).

@sbc100
Copy link
Collaborator

sbc100 commented May 4, 2020

Nice! I like it.

It does make me a little sad the we have to case much about such a small number of bytes that we can't use choose not to use standard mechanisms. But I accept those are the compromises we are making.

@kripken
Copy link
Member Author

kripken commented May 4, 2020

Renamed test, added comments, and adjusted the error messages, as suggested.

It does make me a little sad the we have to case much about such a small number of bytes that we can't use choose not to use standard mechanisms.

One possibility to make this more standard is to optimize out that overhead. If the ctor-evaller worked then at least the constructor wouldn't be an issue, which might be good enough.

@sbc100
Copy link
Collaborator

sbc100 commented May 4, 2020

Renamed test, added comments, and adjusted the error messages, as suggested.

It does make me a little sad the we have to case much about such a small number of bytes that we can't use choose not to use standard mechanisms.

One possibility to make this more standard is to optimize out that overhead. If the ctor-evaller worked then at least the constructor wouldn't be an issue, which might be good enough.

Hmm.. that makes me see this change differently then. From looking at the change I can't see why memory growth should effect this. Does malloc(X) return NULL in the non-growable mode too?

@sbc100
Copy link
Collaborator

sbc100 commented May 4, 2020

The title and description of the change don't talk about growth....

@kripken
Copy link
Member Author

kripken commented May 4, 2020

There are two factors here.

First, that there are multiple code paths. The code for growth looks very different if growth is enabled or not. If growth is not enabled, then the grow memory function is trivial, and always worked! But if growth is allowed then we have a bunch of code there to try pick how much to grow memory, that tries to grow, and that handles things, etc. And that code path returns "failure" if growth fails. (Note that as I mentioned in the issue, perhaps it is slightly inconsistent that growth traps if growth is not enabled at all, but returns failure if growth is allowed but it failed in practice. I don't want to change that behavior now, though.)

Second, this is specific to C++. With plain C, when growth is enabled but it failed and it returned 0, it would return 0 from malloc. That's correct for C. With C++, it's not ok for new to return 0, it should never do that. But that's the default behavior in libc++/libc++abi, so this PR changes that.

@sbc100
Copy link
Collaborator

sbc100 commented May 4, 2020

Sorry, I still don't understand why this test doesn't pass when growth is disabled.

Why doesn't the malloc on line 67 return 0 in both cases? its malloc trapping in the non-growth case? Should we fix that first?

@kripken
Copy link
Member Author

kripken commented May 4, 2020

its malloc trapping in the non-growth case?

Yes.

Should we fix that first?

While, as I said, it's arguable that there is an inconsistency, I don't think so. First, it's always wrong for new to return 0. That it happens to only happen in this path is irrelevant - we should ensure new never returns 0, period. That's all this PR does.

Second, as I said it's arguable whether we should change that behavior. It may be a little inconsistent, but it is quite convenient in practice and matches what most users want. We've not gotten complaints that I can remember. The current model is:

  • Without growth, trap on running out of memory.
  • With growth, you are opting into a more standard memory management model. Memory can grow and an attempt to grow can succeed or fail, and when it fails, malloc returns 0.

@sbc100
Copy link
Collaborator

sbc100 commented May 4, 2020

OK, can you at least add a comment about why this test only passed with growth enabled. Something along the lines of: "Only makes since with growth enabled, since when growth is disabled, malloc already traps".

But wait.. now that I write the sentence it seems that the trapping behaviour should happen both with and without growth. In one case malloc itself traps and in the other case we are adding this abort so new will trap. If this is true, can we run the test both with and without growth to make sure it traps in both cases? .. then we can can call the test test_trapping_new since its checking for trapping under all circumstances? :)

@kripken
Copy link
Member Author

kripken commented May 4, 2020

Sure, added a comment, renamed, and made it run on non-growth too.

Copy link
Collaborator

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Thanks for being patient with all comments.

@kripken kripken merged commit a153b41 into master May 5, 2020
@kripken kripken deleted the new branch May 5, 2020 15:20
aheejin added a commit that referenced this pull request Apr 3, 2024
This updates libcxx and libcxxabi to LLVM 18.1.2. Due to
llvm/llvm-project#65534 we need to update both
at the same time.

Things to note:

- Disable hardening mode:
  LLVM 18.1.2 adds support for "Hardening Modes" to libcxx
  (https://libcxx.llvm.org/Hardening.html). There are four modes: none,
  fast, extensive and debug. Different hardening modes make different
  trade-offs between the amount of checking and runtime performance. We
  for now disable it (i.e., set it to 'none') so that we don't have any
  effects on the performance. We can consider enabling it when we ever
  get to enable the debug version of libcxx.

- Disable C++20 time zone support:
  LLVM 18.1.2 adds C++20 time zone support
  (https://libcxx.llvm.org/DesignDocs/TimeZone.html) to libcxx, which
  requires access IANA Time Zone Database. Currently it seems it only
  supports Linux:
  https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/src/tz.cpp#L45-L49
  So this excludes the two source files from build (which is done via
  `CMakeLists.txt` in the upstream LLVM) and sets
  `_LIBCPP_HAS_NO_TIME_ZONE_DATABASE` macro in `__config_site`. In
  future maybe we can consider implementing this in JS.

- `__cxa_init_primary_exception` support:
  llvm/llvm-project#65534 introduces
  `__cxa_init_primary_exception` and uses this in libcxx. Like several
  other methods like the below in `cxa_exception.cpp`,
  https://github.com/emscripten-core/emscripten/blob/fbdd9249e939660cb0d20f00d6bc1897c2f3905e/system/lib/libcxxabi/src/cxa_exception.cpp#L264-L269
  this new function takes a pointer to a destructor, and in Wasm
  destructor returns a `this` pointer, which requires `ifdef
  __USING_WASM_EXCEPTION__` on its signature. And that it is also used
  in libcxx means we need to guard some code in libcxx with `ifdef
  __USING_WASM_EXCEPTION__` for the signature difference, and we need to
  build libcxx with `-D__USING_WASM_EXCEPTIONS__` when Wasm EH is used.
  Also we add Emscripte EH's counterpart in
  `cxa_exception_emscripten.cpp` too.

- This version fixes long-running misbehaviors of `operator new`
  llvm/llvm-project#69498 seems to fix some
  long-running misbhaviors of `operator new`, which in emscripten we
  tried to fix in #11079 and #20154. So while resolving the conflicts, I
  effectively reverted #11079 and #20154 in
  `libcxx/src/stdlib_new_delete.cpp` and `libcxx/src/new.cpp`.

- Add default `__assertion_handler` to `libcxx/include`:
  llvm/llvm-project#77883 adds a way for vendors
  to override how we handle assertions. If a vendor doesn't want to
  override it, CMake will copy the provided [default template assertion
  handler
  file](https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/vendor/llvm/default_assertion_handler.in)
  to libcxx's generated include dir, which defaults to
  `SYSROOT/include/c++/v1`:
  https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/CMakeLists.txt#L72-L73
  https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/CMakeLists.txt#L439
  https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/include/CMakeLists.txt#L1024
  We don't use CMake, so this renames the provided
  `vendor/llvm/default_assertion_handler.in` to `__assert_handler` in
  our `libcxx/include` directory, which will be copied to
  `SYSROOT/include/c++/v1`.

- Disable `__libcpp_verbose_abort directly` in code:
  In #20707 we decided to disable `__libcpp_verbose_abort` not to incur
  the code size increase that brings (it brings in `vfprintf` and its
  family). We disabled it in adding
  `#define _LIBCPP_AVAILABILITY_HAS_NO_VERBOSE_ABORT` in
  `__config_site`. But that `_NO_` macros are gone in LLVM 18.1.2, and
  changing it to `#define _LIBCPP_AVAILABILITY_HAS_VERBOSE_ABORT 0` does
  not work because it is overridden by this line, unless we provide our
  own vendor annotations:
  https://github.com/llvm/llvm-project/blob/38f5596feda3276a8aa64fc14e074334017088ca/libcxx/include/__availability#L138
  I asked about this in
  llvm/llvm-project#87012 and
  llvm/llvm-project#71002 (comment)
  (and more comments below)
  but didn't get an actionable answer yet. So this disables
  `__libcpp_verbose_abort` in the code directly for now, hopefully
  temporarily.

Each fix is described in its own commit, except for the fixes required
to resolve conflicts when merging our changes, which I wasn't able to
commit separately.

Fixes #21603.
impact-maker pushed a commit to impact-maker/emscripten that referenced this pull request Apr 11, 2024
This updates libcxx and libcxxabi to LLVM 18.1.2. Due to
llvm/llvm-project#65534 we need to update both
at the same time.

Things to note:

- Disable hardening mode:
  LLVM 18.1.2 adds support for "Hardening Modes" to libcxx
  (https://libcxx.llvm.org/Hardening.html). There are four modes: none,
  fast, extensive and debug. Different hardening modes make different
  trade-offs between the amount of checking and runtime performance. We
  for now disable it (i.e., set it to 'none') so that we don't have any
  effects on the performance. We can consider enabling it when we ever
  get to enable the debug version of libcxx.

- Disable C++20 time zone support:
  LLVM 18.1.2 adds C++20 time zone support
  (https://libcxx.llvm.org/DesignDocs/TimeZone.html) to libcxx, which
  requires access IANA Time Zone Database. Currently it seems it only
  supports Linux:
  https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/src/tz.cpp#L45-L49
  So this excludes the two source files from build (which is done via
  `CMakeLists.txt` in the upstream LLVM) and sets
  `_LIBCPP_HAS_NO_TIME_ZONE_DATABASE` macro in `__config_site`. In
  future maybe we can consider implementing this in JS.

- `__cxa_init_primary_exception` support:
  llvm/llvm-project#65534 introduces
  `__cxa_init_primary_exception` and uses this in libcxx. Like several
  other methods like the below in `cxa_exception.cpp`,
  https://github.com/emscripten-core/emscripten/blob/fbdd9249e939660cb0d20f00d6bc1897c2f3905e/system/lib/libcxxabi/src/cxa_exception.cpp#L264-L269
  this new function takes a pointer to a destructor, and in Wasm
  destructor returns a `this` pointer, which requires `ifdef
  __USING_WASM_EXCEPTION__` on its signature. And that it is also used
  in libcxx means we need to guard some code in libcxx with `ifdef
  __USING_WASM_EXCEPTION__` for the signature difference, and we need to
  build libcxx with `-D__USING_WASM_EXCEPTIONS__` when Wasm EH is used.
  Also we add Emscripte EH's counterpart in
  `cxa_exception_emscripten.cpp` too.

- This version fixes long-running misbehaviors of `operator new`
  llvm/llvm-project#69498 seems to fix some
  long-running misbhaviors of `operator new`, which in emscripten we
  tried to fix in emscripten-core#11079 and emscripten-core#20154. So while resolving the conflicts, I
  effectively reverted emscripten-core#11079 and emscripten-core#20154 in
  `libcxx/src/stdlib_new_delete.cpp` and `libcxx/src/new.cpp`.

- Add default `__assertion_handler` to `libcxx/include`:
  llvm/llvm-project#77883 adds a way for vendors
  to override how we handle assertions. If a vendor doesn't want to
  override it, CMake will copy the provided [default template assertion
  handler
  file](https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/vendor/llvm/default_assertion_handler.in)
  to libcxx's generated include dir, which defaults to
  `SYSROOT/include/c++/v1`:
  https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/CMakeLists.txt#L72-L73
  https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/CMakeLists.txt#L439
  https://github.com/llvm/llvm-project/blob/26a1d6601d727a96f4301d0d8647b5a42760ae0c/libcxx/include/CMakeLists.txt#L1024
  We don't use CMake, so this renames the provided
  `vendor/llvm/default_assertion_handler.in` to `__assert_handler` in
  our `libcxx/include` directory, which will be copied to
  `SYSROOT/include/c++/v1`.

- Disable `__libcpp_verbose_abort directly` in code:
  In emscripten-core#20707 we decided to disable `__libcpp_verbose_abort` not to incur
  the code size increase that brings (it brings in `vfprintf` and its
  family). We disabled it in adding
  `#define _LIBCPP_AVAILABILITY_HAS_NO_VERBOSE_ABORT` in
  `__config_site`. But that `_NO_` macros are gone in LLVM 18.1.2, and
  changing it to `#define _LIBCPP_AVAILABILITY_HAS_VERBOSE_ABORT 0` does
  not work because it is overridden by this line, unless we provide our
  own vendor annotations:
  https://github.com/llvm/llvm-project/blob/38f5596feda3276a8aa64fc14e074334017088ca/libcxx/include/__availability#L138
  I asked about this in
  llvm/llvm-project#87012 and
  llvm/llvm-project#71002 (comment)
  (and more comments below)
  but didn't get an actionable answer yet. So this disables
  `__libcpp_verbose_abort` in the code directly for now, hopefully
  temporarily.

Each fix is described in its own commit, except for the fixes required
to resolve conflicts when merging our changes, which I wasn't able to
commit separately.

Fixes emscripten-core#21603.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

OOM during std::vector allocation doesn't terminate the program
2 participants