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

Behaviour of new with std::nothrow #20132

Closed
M-Mueller opened this issue Aug 25, 2023 · 3 comments
Closed

Behaviour of new with std::nothrow #20132

M-Mueller opened this issue Aug 25, 2023 · 3 comments

Comments

@M-Mueller
Copy link
Contributor

I'm currently porting some code that uses std::nothrow for allocations. However, if I build without exception support, the application aborts if the allocation fails instead of returning 0.

The issue can be reproduced with the following code:

#include <iostream>
#include <new>

int main() {
  uint8_t *a = new (std::nothrow) uint8_t[(unsigned long)-518344287];

  std::cout << "Result: " << (size_t)a << std::endl;

  return 1;
}

Build with:

em++ main.cpp -o index.html -sALLOW_MEMORY_GROWTH

If I run this in the browser I get:

Cannot enlarge memory, asked to go up to 3776712704 bytes, but the limit is 2147483648 bytes! [index.js:1116:12](http://localhost:8000/index.js)
Cannot enlarge memory, asked to go up to 3776716800 bytes, but the limit is 2147483648 bytes! [index.js:1116:12](http://localhost:8000/index.js)
Aborted(native code called abort()) [index.js:656:6](http://localhost:8000/index.js)
Uncaught RuntimeError: Aborted(native code called abort())
    abort http://localhost:8000/index.js:675
    _abort http://localhost:8000/index.js:1061
    createExportWrapper http://localhost:8000/index.js:705
    callMain http://localhost:8000/index.js:4925
    doRun http://localhost:8000/index.js:4975
    run http://localhost:8000/index.js:4986
    setTimeout handler*run http://localhost:8000/index.js:4982
    runCaller http://localhost:8000/index.js:4910
    removeRunDependency http://localhost:8000/index.js:642
    receiveInstance http://localhost:8000/index.js:842
    receiveInstantiationResult http://localhost:8000/index.js:860
    promise callback*instantiateAsync/< http://localhost:8000/index.js:795
    promise callback*instantiateAsync http://localhost:8000/index.js:787
    createWasm http://localhost:8000/index.js:879
    <anonymous> http://localhost:8000/index.js:4595

If I build with -fexception the code works as expected, i.e. the "Cannot enlarge memory" errors are still there but the program doesn't abort and prints "Result: 0".

I think the problem is in system/lib/libcxx/src/new.cpp: the operator new(size_t size, const std::nothrow_t&) just calls operator new(std::size_t size) internally which always aborts in case of failure.

I agree with the reasons for abort in #11042 for the current behaviour of operator new(std::size_t size). However, I think the std::nothrow variant aborting is not correct. Without exceptions enabled, this is the only method to check whether an allocation failed. Also code that uses this should be aware that it might return 0.

Version of emscripten/emsdk:

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.44 (bec42dac7873903d09d713963e34020c22a8b
d2d)
clang version 17.0.0 (https://github.com/llvm/llvm-project a8cbd27d1f238e104a5d5ca345d93bc1f4d4ab1f)
Target: wasm32-unknown-emscripten
Thread model: posix
@sbc100
Copy link
Collaborator

sbc100 commented Aug 25, 2023

Most likely you want to set -sABORTING_MALLOC=0:

emscripten/src/settings.js

Lines 124 to 144 in e69b6cf

// If 1, then when malloc would fail we abort(). This is nonstandard behavior,
// but makes sense for the web since we have a fixed amount of memory that
// must all be allocated up front, and so (a) failing mallocs are much more
// likely than on other platforms, and (b) people need a way to find out
// how big that initial allocation (INITIAL_MEMORY) must be.
// If you set this to 0, then you get the standard malloc behavior of
// returning NULL (0) when it fails.
//
// Setting ALLOW_MEMORY_GROWTH turns this off, as in that mode we default to
// the behavior of trying to grow and returning 0 from malloc on failure, like
// a standard system would. However, you can still set this flag to override
// that.
// * This is a mostly-backwards-compatible change. Previously this option
// was ignored when growth was on. The current behavior is that growth
// turns it off by default, so for users that never specified the flag
// nothing changes. But if you do specify it, it will have an effect now,
// which it did not previously. If you don't want that, just stop passing
// it in at link time.
//
// [link]
var ABORTING_MALLOC = true;

@sbc100
Copy link
Collaborator

sbc100 commented Aug 25, 2023

Oh wait, sorry I didn't see that is actually ::new that is calling abort in this case.

If you can come up with a patch the keeps the existing behaviour for folks that don't use std::nothrow but get the behaviour you want when using std::nothrow then that seems like a reasonable patch to land.

@M-Mueller
Copy link
Contributor Author

#20154

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

No branches or pull requests

2 participants