Skip to content

Commit a153b41

Browse files
authored
abort() on malloc() failure in C++ new with exceptions disabled + memory growth (#11079)
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.
1 parent 0ea8070 commit a153b41

File tree

5 files changed

+52
-0
lines changed

5 files changed

+52
-0
lines changed

system/lib/libcxx/new.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,17 @@ operator new(std::size_t size) _THROW_BAD_ALLOC
7474
else
7575
#ifndef _LIBCPP_NO_EXCEPTIONS
7676
throw std::bad_alloc();
77+
#else
78+
#ifdef __EMSCRIPTEN__
79+
// Abort here so that when exceptions are disabled, we do not just
80+
// return 0 when malloc returns 0.
81+
// We could also do this with set_new_handler, but that adds a
82+
// global constructor and a table entry, overhead that we can avoid
83+
// by doing it this way.
84+
abort();
7785
#else
7886
break;
87+
#endif
7988
#endif
8089
}
8190
return p;

system/lib/libcxxabi/src/stdlib_new_delete.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,17 @@ operator new(std::size_t size) _THROW_BAD_ALLOC
3838
else
3939
#ifndef _LIBCXXABI_NO_EXCEPTIONS
4040
throw std::bad_alloc();
41+
#else
42+
#ifdef __EMSCRIPTEN__
43+
// Abort here so that when exceptions are disabled, we do not just
44+
// return 0 when malloc returns 0.
45+
// We could also do this with set_new_handler, but that adds a
46+
// global constructor and a table entry, overhead that we can avoid
47+
// by doing it this way.
48+
abort();
4149
#else
4250
break;
51+
#endif
4352
#endif
4453
}
4554
return p;

tests/core/test_aborting_new.cpp

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <emscripten.h>
2+
#include <stdio.h>
3+
#include <vector>
4+
5+
EMSCRIPTEN_KEEPALIVE extern "C" void allocate_too_much() {
6+
std::vector<int> x;
7+
puts("allocating more than TOTAL_MEMORY; this will fail.");
8+
x.resize(20 * 1024 * 1024);
9+
puts("oh no, it didn't fail!");
10+
}
11+
12+
int main() {
13+
EM_ASM({
14+
// Catch the failure here so we can report it.
15+
try {
16+
_allocate_too_much();
17+
out("no abort happened");
18+
} catch (e) {
19+
assert(("" + e).indexOf("abort") >= 0, "expect an abort from new");
20+
out("new aborted as expected");
21+
}
22+
});
23+
}

tests/core/test_aborting_new.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
new aborted as expected

tests/test_core.py

+10
Original file line numberDiff line numberDiff line change
@@ -2169,6 +2169,16 @@ def test_memorygrowth_3_force_fail_reallocBuffer(self):
21692169
self.emcc_args += ['-Wno-almost-asm', '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'TEST_MEMORY_GROWTH_FAILS=1']
21702170
self.do_run_in_out_file_test('tests', 'core', 'test_memorygrowth_3')
21712171

2172+
@parameterized({
2173+
'nogrow': (['-s', 'ALLOW_MEMORY_GROWTH=0'],),
2174+
'grow': (['-s', 'ALLOW_MEMORY_GROWTH=1'],)
2175+
})
2176+
def test_aborting_new(self, args):
2177+
# test that C++ new properly errors if we fail to malloc when growth is
2178+
# enabled, with or without growth
2179+
self.emcc_args += ['-Wno-almost-asm', '-s', 'MAXIMUM_MEMORY=18MB'] + args
2180+
self.do_run_in_out_file_test('tests', 'core', 'test_aborting_new')
2181+
21722182
@no_asmjs()
21732183
@no_wasm2js('no WebAssembly.Memory()')
21742184
@no_asan('ASan alters the memory size')

0 commit comments

Comments
 (0)