Skip to content

Commit 3145265

Browse files
authored
[libc++] Fix the behavior of throwing operator new under -fno-exceptions (#69498)
In D144319, Clang tried to land a change that would cause some functions that are not supposed to return nullptr to optimize better. As reported in https://reviews.llvm.org/D144319#4203982, libc++ started seeing failures in its CI shortly after this change was landed. As explained in D146379, the reason for these failures is that libc++'s throwing `operator new` can in fact return nullptr when compiled with exceptions disabled. However, this contradicts the Standard, which clearly says that the throwing version of `operator new(size_t)` should never return nullptr. This is actually a long standing issue. I've previously seen a case where LTO would optimize incorrectly based on the assumption that `operator new` doesn't return nullptr, an assumption that was violated in that case because libc++.dylib was compiled with -fno-exceptions. Unfortunately, fixing this is kind of tricky. The Standard has a few requirements for the allocation functions, some of which are impossible to satisfy under -fno-exceptions: 1. `operator new(size_t)` must never return nullptr 2. `operator new(size_t, nothrow_t)` must call the throwing version and return nullptr on failure to allocate 3. We can't throw exceptions when compiled with -fno-exceptions In the case where exceptions are enabled, things work nicely. `new(size_t)` throws and `new(size_t, nothrow_t)` uses a try-catch to return nullptr. However, when compiling the library with -fno-exceptions, we can't throw an exception from `new(size_t)`, and we can't catch anything from `new(size_t, nothrow_t)`. The only thing we can do from `new(size_t)` is actually abort the program, which does not make it possible for `new(size_t, nothrow_t)` to catch something and return nullptr. This patch makes the following changes: 1. When compiled with -fno-exceptions, the throwing version of `operator new` will now abort on failure instead of returning nullptr on failure. This resolves the issue that the compiler could mis-compile based on the assumption that nullptr is never returned. This constitutes an API and ABI breaking change for folks compiling the library with -fno-exceptions (which is not the general public, who merely uses libc++ headers but use a shared library that has already been compiled). This should mostly impact vendors and other folks who compile libc++.dylib themselves. 2. When the library is compiled with -fexceptions, the nothrow version of `operator new` has no change. When the library is compiled with -fno-exceptions, the nothrow version of `operator new` will now check whether the throwing version of `operator new` has been overridden. If it has not been overridden, then it will use an implementation equivalent to that of the throwing `operator new`, except it will return nullptr on failure to allocate (instead of terminating). However, if the throwing `operator new` has been overridden, it is now an error NOT to also override the nothrow `operator new`. Indeed, there is no way for us to implement a valid nothrow `operator new` without knowing the exact implementation of the throwing version. In summary, this change will impact people who fall into the following intersection of conditions: - They use the libc++ shared/static library built with `-fno-exceptions` - They do not override `operator new(..., std::nothrow_t)` - They override `operator new(...)` (the throwing version) - They use `operator new(..., std::nothrow_t)` We believe this represents a small number of people. Fixes #60129 rdar://103958777 Differential Revision: https://reviews.llvm.org/D150610
1 parent 097a40a commit 3145265

File tree

13 files changed

+627
-152
lines changed

13 files changed

+627
-152
lines changed

libcxx/docs/ReleaseNotes/18.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,29 @@ LLVM 20
237237
ABI Affecting Changes
238238
---------------------
239239

240+
- When the shared/static library is built with ``-fno-exceptions``, the behavior of ``operator new`` was changed
241+
to make it standards-conforming. In LLVM 17 and before, the throwing versions of ``operator new`` would return
242+
``nullptr`` upon failure to allocate, when the shared/static library was built with exceptions disabled. This
243+
was non-conforming, since the throwing versions of ``operator new`` are never expected to return ``nullptr``, and
244+
this non-conformance could actually lead to miscompiles in subtle cases.
245+
246+
Starting in LLVM 18, the throwing versions of ``operator new`` will abort the program when they fail to allocate
247+
if the shared/static library has been built with ``-fno-exceptions``. This is consistent with the behavior of all
248+
other potentially-throwing functions in the library, which abort the program instead of throwing when ``-fno-exceptions``
249+
is used.
250+
251+
Furthermore, when the shared/static library is built with ``-fno-exceptions``, users who override the throwing
252+
version of ``operator new`` will now need to also override the ``std::nothrow_t`` version of ``operator new`` if
253+
they want to use it. Indeed, this is because there is no way to implement a conforming ``operator new(nothrow)``
254+
from a conforming potentially-throwing ``operator new`` when compiled with ``-fno-exceptions``. In that case, using
255+
``operator new(nothrow)`` without overriding it explicitly but after overriding the throwing ``operator new`` will
256+
result in an error.
257+
258+
Note that this change only impacts vendors/users that build the shared/static library themselves and pass
259+
``-DLIBCXX_ENABLE_EXCEPTIONS=OFF``, which is not the default configuration. If you are using the default
260+
configuration of the library, the libc++ shared/static library will be built with exceptions enabled, and
261+
there is no change between LLVM 17 and LLVM 18, even for users who build their own code using ``-fno-exceptions``.
262+
240263
- The symbol of a non-visible function part of ``std::system_error`` was removed.
241264
This is not a breaking change as the private function ``__init`` was never referenced internally outside of the dylib.
242265

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// -*- C++ -*-
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#ifndef _LIBCPP_SRC_INCLUDE_OVERRIDABLE_FUNCTION_H
11+
#define _LIBCPP_SRC_INCLUDE_OVERRIDABLE_FUNCTION_H
12+
13+
#include <__config>
14+
#include <cstdint>
15+
16+
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
17+
# pragma GCC system_header
18+
#endif
19+
20+
//
21+
// This file provides the std::__is_function_overridden utility, which allows checking
22+
// whether an overridable function (typically a weak symbol) like `operator new`
23+
// has been overridden by a user or not.
24+
//
25+
// This is a low-level utility which does not work on all platforms, since it needs
26+
// to make assumptions about the object file format in use. Furthermore, it requires
27+
// the "base definition" of the function (the one we want to check whether it has been
28+
// overridden) to be annotated with the _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE macro.
29+
//
30+
// This currently works with Mach-O files (used on Darwin) and with ELF files (used on Linux
31+
// and others). On platforms where we know how to implement this detection, the macro
32+
// _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION is defined to 1, and it is defined to 0 on
33+
// other platforms. The _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE macro is defined to
34+
// nothing on unsupported platforms so that it can be used to decorate functions regardless
35+
// of whether detection is actually supported.
36+
//
37+
// How does this work?
38+
// -------------------
39+
//
40+
// Let's say we want to check whether a weak function `f` has been overridden by the user.
41+
// The general mechanism works by placing `f`'s definition (in the libc++ built library)
42+
// inside a special section, which we do using the `__section__` attribute via the
43+
// _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE macro.
44+
//
45+
// Then, when comes the time to check whether the function has been overridden, we take
46+
// the address of the function and we check whether it falls inside the special function
47+
// we created. This can be done by finding pointers to the start and the end of the section
48+
// (which is done differently for ELF and Mach-O), and then checking whether `f` falls
49+
// within those bounds. If it falls within those bounds, then `f` is still inside the
50+
// special section and so it is the version we defined in the libc++ built library, i.e.
51+
// it was not overridden. Otherwise, it was overridden by the user because it falls
52+
// outside of the section.
53+
//
54+
// Important note
55+
// --------------
56+
//
57+
// This mechanism should never be used outside of the libc++ built library. In particular,
58+
// attempting to use this within the libc++ headers will not work at all because we don't
59+
// want to be defining special sections inside user's executables which use our headers.
60+
// This is provided inside libc++'s include tree solely to make it easier to share with
61+
// libc++abi, which needs the same mechanism.
62+
//
63+
64+
#if defined(_LIBCPP_OBJECT_FORMAT_MACHO)
65+
66+
# define _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION 1
67+
# define _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE \
68+
__attribute__((__section__("__TEXT,__lcxx_override,regular,pure_instructions")))
69+
70+
_LIBCPP_BEGIN_NAMESPACE_STD
71+
template <class _Ret, class... _Args>
72+
_LIBCPP_HIDE_FROM_ABI bool __is_function_overridden(_Ret (*__fptr)(_Args...)) noexcept {
73+
// Declare two dummy bytes and give them these special `__asm` values. These values are
74+
// defined by the linker, which means that referring to `&__lcxx_override_start` will
75+
// effectively refer to the address where the section starts (and same for the end).
76+
extern char __lcxx_override_start __asm("section$start$__TEXT$__lcxx_override");
77+
extern char __lcxx_override_end __asm("section$end$__TEXT$__lcxx_override");
78+
79+
// Now get a uintptr_t out of these locations, and out of the function pointer.
80+
uintptr_t __start = reinterpret_cast<uintptr_t>(&__lcxx_override_start);
81+
uintptr_t __end = reinterpret_cast<uintptr_t>(&__lcxx_override_end);
82+
uintptr_t __ptr = reinterpret_cast<uintptr_t>(__fptr);
83+
84+
// Finally, the function was overridden if it falls outside of the section's bounds.
85+
return __ptr < __start || __ptr > __end;
86+
}
87+
_LIBCPP_END_NAMESPACE_STD
88+
89+
#elif defined(_LIBCPP_OBJECT_FORMAT_ELF)
90+
91+
# define _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION 1
92+
# define _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE __attribute__((__section__("__lcxx_override")))
93+
94+
// This is very similar to what we do for Mach-O above. The ELF linker will implicitly define
95+
// variables with those names corresponding to the start and the end of the section.
96+
//
97+
// See https://stackoverflow.com/questions/16552710/how-do-you-get-the-start-and-end-addresses-of-a-custom-elf-section
98+
extern char __start___lcxx_override;
99+
extern char __stop___lcxx_override;
100+
101+
_LIBCPP_BEGIN_NAMESPACE_STD
102+
template <class _Ret, class... _Args>
103+
_LIBCPP_HIDE_FROM_ABI bool __is_function_overridden(_Ret (*__fptr)(_Args...)) noexcept {
104+
uintptr_t __start = reinterpret_cast<uintptr_t>(&__start___lcxx_override);
105+
uintptr_t __end = reinterpret_cast<uintptr_t>(&__stop___lcxx_override);
106+
uintptr_t __ptr = reinterpret_cast<uintptr_t>(__fptr);
107+
108+
return __ptr < __start || __ptr > __end;
109+
}
110+
_LIBCPP_END_NAMESPACE_STD
111+
112+
#else
113+
114+
# define _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION 0
115+
# define _LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE /* nothing */
116+
117+
#endif
118+
119+
#endif // _LIBCPP_SRC_INCLUDE_OVERRIDABLE_FUNCTION_H

libcxx/src/new.cpp

Lines changed: 77 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
//
77
//===----------------------------------------------------------------------===//
88

9+
#include "include/overridable_function.h"
910
#include <__memory/aligned_alloc.h>
11+
#include <cstddef>
1012
#include <cstdlib>
1113
#include <new>
1214

@@ -15,6 +17,10 @@
1517
// The code below is copied as-is into libc++abi's libcxxabi/src/stdlib_new_delete.cpp
1618
// file. The version in this file is the canonical one.
1719

20+
inline void __throw_bad_alloc_shim() { std::__throw_bad_alloc(); }
21+
22+
# define _LIBCPP_ASSERT_SHIM(expr, str) _LIBCPP_ASSERT(expr, str)
23+
1824
// ------------------ BEGIN COPY ------------------
1925
// Implement all new and delete operators as weak definitions
2026
// in this shared library, so that they can be overridden by programs
@@ -36,41 +42,63 @@ static void* operator_new_impl(std::size_t size) {
3642
return p;
3743
}
3844

39-
_LIBCPP_WEAK void* operator new(std::size_t size) _THROW_BAD_ALLOC {
45+
_LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE _LIBCPP_WEAK void* operator new(std::size_t size) _THROW_BAD_ALLOC {
4046
void* p = operator_new_impl(size);
41-
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
4247
if (p == nullptr)
43-
throw std::bad_alloc();
44-
# endif
48+
__throw_bad_alloc_shim();
4549
return p;
4650
}
4751

4852
_LIBCPP_WEAK void* operator new(size_t size, const std::nothrow_t&) noexcept {
53+
# ifdef _LIBCPP_HAS_NO_EXCEPTIONS
54+
# if _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION
55+
_LIBCPP_ASSERT_SHIM(
56+
!std::__is_function_overridden(static_cast<void* (*)(std::size_t)>(&operator new)),
57+
"libc++ was configured with exceptions disabled and `operator new(size_t)` has been overridden, "
58+
"but `operator new(size_t, nothrow_t)` has not been overridden. This is problematic because "
59+
"`operator new(size_t, nothrow_t)` must call `operator new(size_t)`, which will terminate in case "
60+
"it fails to allocate, making it impossible for `operator new(size_t, nothrow_t)` to fulfill its "
61+
"contract (since it should return nullptr upon failure). Please make sure you override "
62+
"`operator new(size_t, nothrow_t)` as well.");
63+
# endif
64+
65+
return operator_new_impl(size);
66+
# else
4967
void* p = nullptr;
50-
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
5168
try {
52-
# endif // _LIBCPP_HAS_NO_EXCEPTIONS
5369
p = ::operator new(size);
54-
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
5570
} catch (...) {
5671
}
57-
# endif // _LIBCPP_HAS_NO_EXCEPTIONS
5872
return p;
73+
# endif
5974
}
6075

61-
_LIBCPP_WEAK void* operator new[](size_t size) _THROW_BAD_ALLOC { return ::operator new(size); }
76+
_LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE _LIBCPP_WEAK void* operator new[](size_t size) _THROW_BAD_ALLOC {
77+
return ::operator new(size);
78+
}
6279

6380
_LIBCPP_WEAK void* operator new[](size_t size, const std::nothrow_t&) noexcept {
81+
# ifdef _LIBCPP_HAS_NO_EXCEPTIONS
82+
# if _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION
83+
_LIBCPP_ASSERT_SHIM(
84+
!std::__is_function_overridden(static_cast<void* (*)(std::size_t)>(&operator new[])),
85+
"libc++ was configured with exceptions disabled and `operator new[](size_t)` has been overridden, "
86+
"but `operator new[](size_t, nothrow_t)` has not been overridden. This is problematic because "
87+
"`operator new[](size_t, nothrow_t)` must call `operator new[](size_t)`, which will terminate in case "
88+
"it fails to allocate, making it impossible for `operator new[](size_t, nothrow_t)` to fulfill its "
89+
"contract (since it should return nullptr upon failure). Please make sure you override "
90+
"`operator new[](size_t, nothrow_t)` as well.");
91+
# endif
92+
93+
return operator_new_impl(size);
94+
# else
6495
void* p = nullptr;
65-
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
6696
try {
67-
# endif // _LIBCPP_HAS_NO_EXCEPTIONS
6897
p = ::operator new[](size);
69-
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
7098
} catch (...) {
7199
}
72-
# endif // _LIBCPP_HAS_NO_EXCEPTIONS
73100
return p;
101+
# endif
74102
}
75103

76104
_LIBCPP_WEAK void operator delete(void* ptr) noexcept { std::free(ptr); }
@@ -107,43 +135,66 @@ static void* operator_new_aligned_impl(std::size_t size, std::align_val_t alignm
107135
return p;
108136
}
109137

110-
_LIBCPP_WEAK void* operator new(std::size_t size, std::align_val_t alignment) _THROW_BAD_ALLOC {
138+
_LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE _LIBCPP_WEAK void*
139+
operator new(std::size_t size, std::align_val_t alignment) _THROW_BAD_ALLOC {
111140
void* p = operator_new_aligned_impl(size, alignment);
112-
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
113141
if (p == nullptr)
114-
throw std::bad_alloc();
115-
# endif
142+
__throw_bad_alloc_shim();
116143
return p;
117144
}
118145

119146
_LIBCPP_WEAK void* operator new(size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept {
147+
# ifdef _LIBCPP_HAS_NO_EXCEPTIONS
148+
# if _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION
149+
_LIBCPP_ASSERT_SHIM(
150+
!std::__is_function_overridden(static_cast<void* (*)(std::size_t, std::align_val_t)>(&operator new)),
151+
"libc++ was configured with exceptions disabled and `operator new(size_t, align_val_t)` has been overridden, "
152+
"but `operator new(size_t, align_val_t, nothrow_t)` has not been overridden. This is problematic because "
153+
"`operator new(size_t, align_val_t, nothrow_t)` must call `operator new(size_t, align_val_t)`, which will "
154+
"terminate in case it fails to allocate, making it impossible for `operator new(size_t, align_val_t, nothrow_t)` "
155+
"to fulfill its contract (since it should return nullptr upon failure). Please make sure you override "
156+
"`operator new(size_t, align_val_t, nothrow_t)` as well.");
157+
# endif
158+
159+
return operator_new_aligned_impl(size, alignment);
160+
# else
120161
void* p = nullptr;
121-
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
122162
try {
123-
# endif // _LIBCPP_HAS_NO_EXCEPTIONS
124163
p = ::operator new(size, alignment);
125-
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
126164
} catch (...) {
127165
}
128-
# endif // _LIBCPP_HAS_NO_EXCEPTIONS
129166
return p;
167+
# endif
130168
}
131169

132-
_LIBCPP_WEAK void* operator new[](size_t size, std::align_val_t alignment) _THROW_BAD_ALLOC {
170+
_LIBCPP_MAKE_OVERRIDABLE_FUNCTION_DETECTABLE _LIBCPP_WEAK void*
171+
operator new[](size_t size, std::align_val_t alignment) _THROW_BAD_ALLOC {
133172
return ::operator new(size, alignment);
134173
}
135174

136175
_LIBCPP_WEAK void* operator new[](size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept {
176+
# ifdef _LIBCPP_HAS_NO_EXCEPTIONS
177+
# if _LIBCPP_CAN_DETECT_OVERRIDDEN_FUNCTION
178+
_LIBCPP_ASSERT_SHIM(
179+
!std::__is_function_overridden(static_cast<void* (*)(std::size_t, std::align_val_t)>(&operator new[])),
180+
"libc++ was configured with exceptions disabled and `operator new[](size_t, align_val_t)` has been overridden, "
181+
"but `operator new[](size_t, align_val_t, nothrow_t)` has not been overridden. This is problematic because "
182+
"`operator new[](size_t, align_val_t, nothrow_t)` must call `operator new[](size_t, align_val_t)`, which will "
183+
"terminate in case it fails to allocate, making it impossible for `operator new[](size_t, align_val_t, "
184+
"nothrow_t)` to fulfill its contract (since it should return nullptr upon failure). Please make sure you "
185+
"override "
186+
"`operator new[](size_t, align_val_t, nothrow_t)` as well.");
187+
# endif
188+
189+
return operator_new_aligned_impl(size, alignment);
190+
# else
137191
void* p = nullptr;
138-
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
139192
try {
140-
# endif // _LIBCPP_HAS_NO_EXCEPTIONS
141193
p = ::operator new[](size, alignment);
142-
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
143194
} catch (...) {
144195
}
145-
# endif // _LIBCPP_HAS_NO_EXCEPTIONS
146196
return p;
197+
# endif
147198
}
148199

149200
_LIBCPP_WEAK void operator delete(void* ptr, std::align_val_t) noexcept { std::__libcpp_aligned_free(ptr); }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// void* operator new(std::size_t, const std::nothrow_t&);
10+
// void* operator new(std::size_t, std::align_val_t, const std::nothrow_t&);
11+
// void* operator new[](std::size_t, const std::nothrow_t&);
12+
// void* operator new[](std::size_t, std::align_val_t, const std::nothrow_t&);
13+
14+
// This test ensures that we catch the case where `new` has been overridden but `new(nothrow)`
15+
// has not been overridden, and the library is compiled with -fno-exceptions.
16+
//
17+
// In that case, it is impossible for libc++ to provide a Standards conforming implementation
18+
// of `new(nothrow)`, so the only viable option is to terminate the program.
19+
20+
// REQUIRES: has-unix-headers
21+
// UNSUPPORTED: c++03
22+
23+
// We only know how to diagnose this on platforms that use the ELF or Mach-O object file formats.
24+
// XFAIL: target={{.+}}-windows-{{.+}}
25+
26+
// TODO: We currently don't have a way to express that the built library was
27+
// compiled with -fno-exceptions, so if the library was built with support
28+
// for exceptions but we run the test suite without exceptions, this will
29+
// spuriously fail.
30+
// REQUIRES: no-exceptions
31+
32+
#include <cstddef>
33+
#include <new>
34+
35+
#include "check_assertion.h"
36+
37+
// Override the throwing versions of operator new, but not the nothrow versions.
38+
alignas(32) char DummyData[32 * 3];
39+
void* operator new(std::size_t) { return DummyData; }
40+
void* operator new(std::size_t, std::align_val_t) { return DummyData; }
41+
void* operator new[](std::size_t) { return DummyData; }
42+
void* operator new[](std::size_t, std::align_val_t) { return DummyData; }
43+
44+
void operator delete(void*) noexcept {}
45+
void operator delete(void*, std::align_val_t) noexcept {}
46+
void operator delete[](void*) noexcept {}
47+
void operator delete[](void*, std::align_val_t) noexcept {}
48+
49+
int main(int, char**) {
50+
std::size_t size = 3;
51+
std::align_val_t align = static_cast<std::align_val_t>(32);
52+
EXPECT_ANY_DEATH((void)operator new(size, std::nothrow));
53+
EXPECT_ANY_DEATH((void)operator new(size, align, std::nothrow));
54+
EXPECT_ANY_DEATH((void)operator new[](size, std::nothrow));
55+
EXPECT_ANY_DEATH((void)operator new[](size, align, std::nothrow));
56+
57+
return 0;
58+
}

0 commit comments

Comments
 (0)