Hello ! We are developing a native application on MacOS written in C/C++. The applications consists in multiple shared libraries, linked dynamically, loaded by the main app. We encountered multiple crashes in production due to global operator new and delete override.
The main purpose of the overriding new/delete global operators is to maintain a certain memory limit and for making memory statistics. I should mention that each dylib override global new/delete operators and mark them hidden(-fvisibility=hidden). For testing purposes we compile with C++ STD 17, no optimizations and no inline(-O0, -fno-inline). As i mentioned before we override all the operators new/delete versions(for STD 17):
void* operator new[] (size_t size) {... }
void* operator new (size_t size, std::align_val_t al) {...}
void* operator new[] (size_t size, std::align_val_t al) { ...}
void* operator new (size_t size, const std::nothrow_t&) noexcept { ...}
void* operator new[] (size_t size, const std::nothrow_t&) noexcept { ... }
void* operator new (size_t size, std::align_val_t al, const std::nothrow_t&) noexcept { ... }
void* operator new[] (size_t size, std::align_val_t al, const std::nothrow_t&) noexcept { ...}
Same for delete.
A certain set of instructions(using std::string) causes an operator call mismatch, even though all others usages of STL are working correctly. These instructions are attached below:
std::string s = "constructing a constant string here as argument";
s = std::string("constructing another constant string here as argument");
The same code works correctly on linux, the only difference between these build is that shared libraries is linked with libstdc++.so on Linux and with libc++.dylib on MacOS. We cannot test if the application works corectly on MacOS with libstdc++ runtime because this feature is deprecated since XCode 10.
I should mention that our build system use: XCode 13.4 with Apple Clang 13.1.6(we need universal binaries).
As described in an Apple Technical link a possible solution could be:
__attribute__((__weak__, __visibility__("default"))) int dummy_weak_symbol_for_new;
We tried with this possible fix(placing a weak symbol in each translation unit) but it did not worked.
Below is a valgrind log, that exposes the bug:
==1992== by 0x10A7AEFC4: operator delete(void*) (our_lib.cpp:z)
==1992== by 0x10A702BF4: std::__1::_DeallocateCaller::__do_call(void*) (new:334)
==1992== by 0x10A702BD8: std::__1::_DeallocateCaller::__do_deallocate_handle_size(void*, unsigned long) (new:292)
==1992== by 0x10A70A730: std::__1::_DeallocateCaller::__do_deallocate_handle_size_align(void*, unsigned long, unsigned long) (new:262)
==1992== by 0x10A70A704: std::__1::__libcpp_deallocate(void*, unsigned long, unsigned long) (new:340)
==1992== by 0x10A70A6C5: std::__1::allocator<char>::deallocate(char*, unsigned long) (memory:1872)
==1992== by 0x10A70A5C4: std::__1::allocator_traits<std::__1::allocator<char> >::deallocate(std::__1::allocator<char>&, char*, unsigned long) (memory:1594)
==1992== by 0x10A70A52F: std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::__move_assign(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&, std::__1::integral_constant<bool, true>) (string:2299)
==1992== by 0x10A709C63: std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::operator=(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&&) (string:2320)
==1992== by 0x10A709915: our_shared_lib:y
==1992== Address 0x10b60bc50 is 16 bytes before a block of size 48 alloc'd
==1992== at 0x1009ED635: malloc (in /usr/local/Cellar/valgrind/HEAD-eec4cf7/libexec/valgrind/vgpreload_memcheck-amd64-darwin.so)
==1992== by 0x100ABEDE9: operator new(unsigned long) (in /usr/lib/libc++abi.dylib)
==1992== by 0x100A2FB87: std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) (in /usr/lib/libc++.1.dylib)
==1992== by 0x10A70DAA4: our_lib:x
We can see that operator new is called from libc++abi.dylib (even though we have any versions of it overriden) and our operator delete it is called.
We are searching for a work-around for this problem. Any help will be really appreciated. Thank you in advance !