CI updates; cxxlib.h cleanups

This commit is contained in:
Markus F.X.J. Oberhumer 2023-12-28 04:42:41 +01:00
parent 778663ae69
commit 0500e7d016
11 changed files with 187 additions and 100 deletions

View File

@ -17,5 +17,5 @@ jobs:
uses: actions/checkout@v4
with: { submodules: false }
- name: 'Spell check with crate-ci/typos'
uses: crate-ci/typos@5bd389de715c63ba86568420809e324fcea78660 # v1.16.25
uses: crate-ci/typos@45a880d9f898547e8bfe6525b6059d4b3dea4d71 # v1.16.26
with: { config: ./.github/typos_config.toml }

View File

@ -24,6 +24,7 @@ jobs:
- { container: 'alpine:3.19', release: debug, qemu: 'qemu-x86_64 -cpu Nehalem' }
- { container: 'alpine:3.19', release: release, qemu: 'qemu-x86_64 -cpu Nehalem' }
- { container: 'alpine:edge', release: release, qemu: 'qemu-x86_64 -cpu Nehalem' }
- { container: 'i386/alpine:3.19', release: release, qemu: 'qemu-i386' }
- { container: 'i386/alpine:edge', release: release, qemu: 'qemu-i386' }
name: ${{ format('{0} {1}', matrix.container, matrix.release) }}
runs-on: ubuntu-latest
@ -45,7 +46,8 @@ jobs:
- name: 'Build clang-static'
run: |
export CC="clang -static" CXX="clang++ -static"
flags="-static -fno-omit-frame-pointer"
export CC="clang $flags" CXX="clang++ $flags"
make UPX_XTARGET=clang-static xtarget/$release
- name: 'Build clang-asan'
if: ${{ !startsWith(matrix.container, 'i386/') }} # i386: ASAN not supported

View File

@ -72,7 +72,7 @@ function(upx_print_var) # ARGV
endforeach()
endfunction()
function(upx_print_have_symbol) # ARGV
function(upx_print_have_symbol) # ARGV; needs include(CheckSymbolExists)
foreach(symbol ${ARGV})
set(cache_var_name "HAVE_symbol_${symbol}")
check_symbol_exists(${symbol} "limits.h;stddef.h;stdint.h" ${cache_var_name})
@ -147,7 +147,7 @@ endfunction()
# compilation flags
#***********************************************************************
function(upx_internal_add_definitions_with_prefix) # ARGV
function(upx_internal_add_definitions_with_prefix) # ARGV; needs include(CheckCCompilerFlag)
set(flag_prefix "${ARGV0}")
if(flag_prefix MATCHES "^empty$") # need "empty" to work around bug in old CMake versions
set(flag_prefix "")
@ -168,7 +168,7 @@ function(upx_internal_add_definitions_with_prefix) # ARGV
set(failed_flags "${failed}" PARENT_SCOPE) # return value
endfunction()
function(upx_add_definitions) # ARGV
function(upx_add_definitions) # ARGV; needs include(CheckCCompilerFlag)
set(failed_flags "")
if(MSVC_FRONTEND AND CMAKE_C_COMPILER_ID MATCHES "Clang")
# for clang-cl try "-clang:" flag prefix first

View File

@ -79,12 +79,9 @@ struct AbstractPolicy {
private:
// disable copy and move
AbstractPolicy(const AbstractPolicy &) DELETED_FUNCTION;
AbstractPolicy &operator=(const AbstractPolicy &) DELETED_FUNCTION;
AbstractPolicy(AbstractPolicy &&) noexcept DELETED_FUNCTION;
AbstractPolicy &operator=(AbstractPolicy &&) noexcept DELETED_FUNCTION;
UPX_CXX_DISABLE_COPY_MOVE(AbstractPolicy)
// disable dynamic allocation
UPX_CXX_DISABLE_NEW_DELETE
UPX_CXX_DISABLE_NEW_DELETE(AbstractPolicy)
};
#endif
@ -147,12 +144,9 @@ struct BEPolicy
private:
// disable copy and move
BEPolicy(const BEPolicy &) DELETED_FUNCTION;
BEPolicy &operator=(const BEPolicy &) DELETED_FUNCTION;
BEPolicy(BEPolicy &&) noexcept DELETED_FUNCTION;
BEPolicy &operator=(BEPolicy &&) noexcept DELETED_FUNCTION;
UPX_CXX_DISABLE_COPY_MOVE(BEPolicy)
// disable dynamic allocation
UPX_CXX_DISABLE_NEW_DELETE
UPX_CXX_DISABLE_NEW_DELETE(BEPolicy)
};
struct LEPolicy
@ -209,12 +203,9 @@ struct LEPolicy
private:
// disable copy and move
LEPolicy(const LEPolicy &) DELETED_FUNCTION;
LEPolicy &operator=(const LEPolicy &) DELETED_FUNCTION;
LEPolicy(LEPolicy &&) noexcept DELETED_FUNCTION;
LEPolicy &operator=(LEPolicy &&) noexcept DELETED_FUNCTION;
UPX_CXX_DISABLE_COPY_MOVE(LEPolicy)
// disable dynamic allocation
UPX_CXX_DISABLE_NEW_DELETE
UPX_CXX_DISABLE_NEW_DELETE(LEPolicy)
};
// Native Endianness policy (aka host policy)

View File

@ -564,6 +564,17 @@ TEST_CASE("acc_vget") {
CHECK_EQ(acc_vget_acc_hvoid_p(nullptr, 0), nullptr);
}
TEST_CASE("ptr_invalidate_and_poison") {
int *ip = nullptr;
ptr_invalidate_and_poison(ip);
assert(ip != nullptr);
(void) ip;
double *dp;
ptr_invalidate_and_poison(dp);
assert(dp != nullptr);
(void) dp;
}
TEST_CASE("working -fno-strict-aliasing") {
bool ok;
long v = 0;

View File

@ -98,9 +98,54 @@ ACC_COMPILE_TIME_ASSERT_HEADER(!compile_time::string_ge("abc", "abz"))
ACC_COMPILE_TIME_ASSERT_HEADER(compile_time::string_le("abc", "abz"))
/*************************************************************************
// UPX_CXX_DISABLE_NEW_DELETE
// UPX_CXX_DISABLE_xxx
**************************************************************************/
namespace {
template <class TA, class TB, int TC = 0>
struct MyType1 {
MyType1() noexcept {}
UPX_CXX_DISABLE_ADDRESS(MyType1)
UPX_CXX_DISABLE_COPY_MOVE(MyType1)
UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL(MyType1)
};
template <class TA, class TB, int TC = 0>
struct MyType2 {
MyType2() noexcept {}
UPX_CXX_DISABLE_COPY_MOVE(MyType2)
typedef MyType2<TA, TB, TC> Self;
UPX_CXX_DISABLE_ADDRESS(Self)
UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL(Self)
};
template <class TA, class TB, int TC = 0>
struct MyVType1 {
MyVType1() noexcept {}
virtual ~MyVType1() noexcept {}
UPX_CXX_DISABLE_ADDRESS(MyVType1)
UPX_CXX_DISABLE_COPY_MOVE(MyVType1)
UPX_CXX_DISABLE_NEW_DELETE(MyVType1)
};
template <class TA, class TB, int TC = 0>
struct MyVType2 {
MyVType2() noexcept {}
virtual ~MyVType2() noexcept {}
UPX_CXX_DISABLE_COPY_MOVE(MyVType2)
typedef MyVType2<TA, TB, TC> Self;
UPX_CXX_DISABLE_ADDRESS(Self)
UPX_CXX_DISABLE_NEW_DELETE(Self)
};
TEST_CASE("upx_cxx_disable") {
MyType1<char, int, 1> dummy1;
MyType2<char, int, 2> dummy2;
MyVType1<char, int, 1> vdummy1;
MyVType2<char, int, 2> vdummy2;
(void) dummy1;
(void) dummy2;
(void) vdummy1;
(void) vdummy2;
}
} // namespace
namespace test_disable_new_delete {
struct A1 {
@ -108,7 +153,7 @@ struct A1 {
};
struct A2 {
int a;
UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL
UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL(A2)
};
struct B1_A1 : public A1 {
int b;
@ -118,11 +163,11 @@ struct B1_A2 : public A2 {
};
struct B2_A1 : public A1 {
int b;
UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL
UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL(B2_A1)
};
struct B2_A2 : public A2 {
int b;
UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL
UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL(B2_A2)
};
struct X1 {
@ -132,7 +177,7 @@ struct X1 {
struct X2 {
virtual ~X2() noexcept {}
int x;
UPX_CXX_DISABLE_NEW_DELETE
UPX_CXX_DISABLE_NEW_DELETE(X2)
};
struct Y1_X1 : public X1 {
int y;
@ -142,11 +187,11 @@ struct Y1_X2 : public X2 {
};
struct Y2_X1 : public X1 {
int y;
UPX_CXX_DISABLE_NEW_DELETE
UPX_CXX_DISABLE_NEW_DELETE(Y2_X1)
};
struct Y2_X2 : public X2 {
int y;
UPX_CXX_DISABLE_NEW_DELETE
UPX_CXX_DISABLE_NEW_DELETE(Y2_X2)
};
struct Z1_X1 : public X1 {
virtual ~Z1_X1() noexcept {}
@ -159,12 +204,12 @@ struct Z1_X2 : public X2 {
struct Z2_X1 : public X1 {
virtual ~Z2_X1() noexcept {}
int z;
UPX_CXX_DISABLE_NEW_DELETE
UPX_CXX_DISABLE_NEW_DELETE(Z2_X1)
};
struct Z2_X2 : public X2 {
virtual ~Z2_X2() noexcept {}
int z;
UPX_CXX_DISABLE_NEW_DELETE
UPX_CXX_DISABLE_NEW_DELETE(Z2_X2)
};
} // namespace test_disable_new_delete

View File

@ -43,7 +43,7 @@ protected:
Throwable(const char *m = nullptr, int e = 0, bool w = false) noexcept;
public:
Throwable(const Throwable &) noexcept;
Throwable(const Throwable &) noexcept; // copy constructor
virtual ~Throwable() noexcept;
const char *getMsg() const noexcept { return msg; }
int getErrno() const noexcept { return err; }
@ -57,14 +57,13 @@ protected:
private:
// disable copy assignment and move
Throwable &operator=(const Throwable &) DELETED_FUNCTION;
Throwable(Throwable &&) noexcept DELETED_FUNCTION;
Throwable &operator=(Throwable &&) noexcept DELETED_FUNCTION;
Throwable &operator=(const Throwable &) noexcept DELETED_FUNCTION; // copy assignment
UPX_CXX_DISABLE_MOVE(Throwable)
// disable dynamic allocation => force throwing by value
UPX_CXX_DISABLE_NEW_DELETE
UPX_CXX_DISABLE_NEW_DELETE(Throwable)
// disable taking the address => force passing by reference
// [I'm not too sure about this design decision, but we can always allow it if needed]
Throwable *operator&() const noexcept DELETED_FUNCTION;
UPX_CXX_DISABLE_ADDRESS(Throwable)
private:
static upx_std_atomic(size_t) debug_counter; // for debugging

View File

@ -321,10 +321,7 @@ private:
private:
// disable copy and move
Packer(const Packer &) DELETED_FUNCTION;
Packer &operator=(const Packer &) DELETED_FUNCTION;
Packer(Packer &&) noexcept DELETED_FUNCTION;
Packer &operator=(Packer &&) noexcept DELETED_FUNCTION;
UPX_CXX_DISABLE_COPY_MOVE(Packer)
};
/* vim:set ts=4 sw=4 et: */

View File

@ -33,55 +33,105 @@
namespace upx {
/*************************************************************************
// core
// core util
**************************************************************************/
// disable taking the address => force passing by reference (instead of pointer)
#define UPX_CXX_DISABLE_ADDRESS(Klass) \
private: \
Klass *operator&() const noexcept DELETED_FUNCTION;
// disable copy and move
#define UPX_CXX_DISABLE_COPY(KlassName) \
private: \
KlassName(const KlassName &) noexcept DELETED_FUNCTION; /* copy constructor */ \
KlassName &operator=(const KlassName &) noexcept DELETED_FUNCTION; /* copy assignment */
#define UPX_CXX_DISABLE_MOVE(KlassName) \
private: \
KlassName(KlassName &&) noexcept DELETED_FUNCTION; /* move constructor */ \
KlassName &operator=(KlassName &&) noexcept DELETED_FUNCTION; /* move assignment */
#define UPX_CXX_DISABLE_COPY_MOVE(KlassName) \
UPX_CXX_DISABLE_COPY(KlassName) \
UPX_CXX_DISABLE_MOVE(KlassName)
// fun with C++: disable common "new" and ALL "delete" operators
#define UPX_CXX_DISABLE_NEW_DELETE_COMMON__ \
// https://en.cppreference.com/w/cpp/memory/new/operator_delete
#define UPX_CXX_DISABLE_NEW_DELETE_IMPL__(Klass) \
private: \
/* common allocation functions (4) */ \
static void *operator new(std::size_t) = delete; \
static void *operator new[](std::size_t) = delete; \
static void *operator new(std::size_t, void *) = delete; \
static void *operator new[](std::size_t, void *) = delete; \
static void *operator new(std::size_t) DELETED_FUNCTION; \
static void *operator new[](std::size_t) DELETED_FUNCTION; \
static void *operator new(std::size_t, void *) DELETED_FUNCTION; \
static void *operator new[](std::size_t, void *) DELETED_FUNCTION; \
/* replaceable placement deallocation functions (4) */ \
static void operator delete(void *, const std::nothrow_t &) noexcept = delete; \
static void operator delete[](void *, const std::nothrow_t &) noexcept = delete; \
static void operator delete(void *, std::align_val_t, const std::nothrow_t &) noexcept = \
delete; \
static void operator delete[](void *, std::align_val_t, const std::nothrow_t &) noexcept = \
delete; \
static void operator delete(void *, const std::nothrow_t &) noexcept DELETED_FUNCTION; \
static void operator delete[](void *, const std::nothrow_t &) noexcept DELETED_FUNCTION; \
static void operator delete(void *, std::align_val_t, const std::nothrow_t &) \
noexcept DELETED_FUNCTION; \
static void operator delete[](void *, std::align_val_t, const std::nothrow_t &) \
noexcept DELETED_FUNCTION; \
/* non-allocating placement deallocation functions (2) */ \
static void operator delete(void *, void *) noexcept = delete; \
static void operator delete[](void *, void *) noexcept = delete;
static void operator delete(void *, void *) noexcept DELETED_FUNCTION; \
static void operator delete[](void *, void *) noexcept DELETED_FUNCTION;
// for classes with virtual methods
#define UPX_CXX_DISABLE_NEW_DELETE \
UPX_CXX_DISABLE_NEW_DELETE_COMMON__ \
/* replaceable usual deallocation functions (8) */ \
/* class-specific usual deallocation functions (8) */
#define UPX_CXX_DISABLE_NEW_DELETE_IMPL_CSUDF_A__(Klass) \
protected: \
static void operator delete(void *) noexcept {} \
static void operator delete[](void *) noexcept = delete; \
static void operator delete(void *, std::align_val_t) {} \
static void operator delete[](void *, std::align_val_t) noexcept = delete; \
static void operator delete(void *, std::size_t) {} \
static void operator delete[](void *, std::size_t) noexcept = delete; \
static void operator delete(void *, std::size_t, std::align_val_t) {} \
static void operator delete[](void *, std::size_t, std::align_val_t) noexcept = delete; \
private:
static void operator delete(void *, std::align_val_t) noexcept {} \
static void operator delete(void *, std::size_t) noexcept {} \
static void operator delete(void *, std::size_t, std::align_val_t) noexcept {} \
private: \
static void operator delete[](void *) noexcept DELETED_FUNCTION; \
static void operator delete[](void *, std::align_val_t) noexcept DELETED_FUNCTION; \
static void operator delete[](void *, std::size_t) noexcept DELETED_FUNCTION; \
static void operator delete[](void *, std::size_t, std::align_val_t) noexcept DELETED_FUNCTION;
#define UPX_CXX_DISABLE_NEW_DELETE_IMPL_CSUDF_B__(Klass) \
private: \
static void operator delete(void *) noexcept DELETED_FUNCTION; \
static void operator delete[](void *) noexcept DELETED_FUNCTION; \
static void operator delete(void *, std::align_val_t) noexcept DELETED_FUNCTION; \
static void operator delete[](void *, std::align_val_t) noexcept DELETED_FUNCTION; \
static void operator delete(void *, std::size_t) noexcept DELETED_FUNCTION; \
static void operator delete[](void *, std::size_t) noexcept DELETED_FUNCTION; \
static void operator delete(void *, std::size_t, std::align_val_t) noexcept DELETED_FUNCTION; \
static void operator delete[](void *, std::size_t, std::align_val_t) noexcept DELETED_FUNCTION;
// for classes WITHOUT any virtual methods
#define UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL \
UPX_CXX_DISABLE_NEW_DELETE_COMMON__ \
/* replaceable usual deallocation functions (8) */ \
static void operator delete(void *) noexcept = delete; \
static void operator delete[](void *) noexcept = delete; \
static void operator delete(void *, std::align_val_t) = delete; \
static void operator delete[](void *, std::align_val_t) noexcept = delete; \
static void operator delete(void *, std::size_t) = delete; \
static void operator delete[](void *, std::size_t) noexcept = delete; \
static void operator delete(void *, std::size_t, std::align_val_t) = delete; \
static void operator delete[](void *, std::size_t, std::align_val_t) noexcept = delete;
/* class-specific usual destroying deallocation functions (4) */
#if __cplusplus >= 202002L
#define UPX_CXX_DISABLE_NEW_DELETE_IMPL_CSUDDF_A__(Klass) \
protected: \
static void operator delete(Klass *, std::destroying_delete_t) noexcept {} \
static void operator delete(Klass *, std::destroying_delete_t, std::align_val_t) noexcept {} \
static void operator delete(Klass *, std::destroying_delete_t, std::size_t) noexcept {} \
static void operator delete(Klass *, std::destroying_delete_t, std::size_t, std::align_val_t) \
noexcept {} \
private:
#define UPX_CXX_DISABLE_NEW_DELETE_IMPL_CSUDDF_B__(Klass) \
private: \
static void operator delete(Klass *, std::destroying_delete_t) noexcept DELETED_FUNCTION; \
static void operator delete(Klass *, std::destroying_delete_t, std::align_val_t) \
noexcept DELETED_FUNCTION; \
static void operator delete(Klass *, std::destroying_delete_t, std::size_t) \
noexcept DELETED_FUNCTION; \
static void operator delete(Klass *, std::destroying_delete_t, std::size_t, std::align_val_t) \
noexcept DELETED_FUNCTION;
#else
#define UPX_CXX_DISABLE_NEW_DELETE_IMPL_CSUDDF_A__(Klass) private:
#define UPX_CXX_DISABLE_NEW_DELETE_IMPL_CSUDDF_B__(Klass) private:
#endif
// for classes which may have virtual methods
#define UPX_CXX_DISABLE_NEW_DELETE(Klass) \
UPX_CXX_DISABLE_NEW_DELETE_IMPL__(Klass) \
UPX_CXX_DISABLE_NEW_DELETE_IMPL_CSUDF_A__(Klass) \
UPX_CXX_DISABLE_NEW_DELETE_IMPL_CSUDDF_A__(Klass)
// this only works for classes WITHOUT any virtual methods
#define UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL(Klass) \
UPX_CXX_DISABLE_NEW_DELETE_IMPL__(Klass) \
UPX_CXX_DISABLE_NEW_DELETE_IMPL_CSUDF_B__(Klass) \
UPX_CXX_DISABLE_NEW_DELETE_IMPL_CSUDDF_B__(Klass)
/*************************************************************************
// type_traits
@ -137,11 +187,7 @@ protected:
#else
forceinline ~noncopyable() noexcept = default;
#endif
private:
noncopyable(const noncopyable &) noexcept DELETED_FUNCTION; // copy constructor
noncopyable &operator=(const noncopyable &) noexcept DELETED_FUNCTION; // copy assignment
noncopyable(noncopyable &&) noexcept DELETED_FUNCTION; // move constructor
noncopyable &operator=(noncopyable &&) noexcept DELETED_FUNCTION; // move assignment
UPX_CXX_DISABLE_COPY_MOVE(noncopyable)
};
namespace compile_time {
@ -178,7 +224,7 @@ struct TriBool final {
// constructors
forceinline constexpr TriBool() noexcept {}
forceinline constexpr TriBool(value_type x) noexcept : value(x) {}
// permissive, converts all other values to Third!!
// IMPORTANT NOTE: permissive, converts all other values to Third!
constexpr TriBool(promoted_type x) noexcept : value(x == 0 ? False : (x == 1 ? True : Third)) {}
#if __cplusplus >= 202002L
forceinline constexpr ~TriBool() noexcept = default;
@ -214,8 +260,8 @@ struct TriBool final {
#endif
private:
value_type value = False; // the actual value of this type
UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL // UPX convention
value_type value = False; // the actual value of this type
UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL(TriBool) // UPX convention
};
typedef TriBool<> tribool;
@ -245,7 +291,7 @@ struct OptVar final {
void assertValue() const noexcept { assertValue(value); }
constexpr OptVar() noexcept {}
OptVar &operator=(const T &other) noexcept {
OptVar &operator=(const T &other) noexcept { // copy constructor
assertValue(other);
value = other;
is_set = true;
@ -259,6 +305,7 @@ struct OptVar final {
value_type value = default_value;
bool is_set = false;
UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL(OptVar) // UPX convention
};
// optional assignments
@ -312,9 +359,9 @@ struct OwningPointer final {
constexpr const_pointer operator->() const noexcept { return ptr; }
private:
pointer ptr;
reference operator[](std::ptrdiff_t) noexcept = delete;
const_reference operator[](std::ptrdiff_t) const noexcept = delete;
UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL // UPX convention
reference operator[](std::ptrdiff_t) noexcept DELETED_FUNCTION;
const_reference operator[](std::ptrdiff_t) const noexcept DELETED_FUNCTION;
UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL(OwningPointer) // UPX convention
};
// must overload mem_clear()
template <class T>

View File

@ -84,7 +84,7 @@ public: // raw access
private:
// disable taking the address => force passing by reference
// [I'm not too sure about this design decision, but we can always allow it if needed]
MemBufferBase<T> *operator&() const noexcept DELETED_FUNCTION;
UPX_CXX_DISABLE_ADDRESS(MemBufferBase)
};
/*************************************************************************
@ -209,15 +209,8 @@ private:
Debug debug;
#endif
// disable copy and move
MemBuffer(const MemBuffer &) DELETED_FUNCTION;
MemBuffer &operator=(const MemBuffer &) DELETED_FUNCTION;
#if __cplusplus >= 201103L
MemBuffer(MemBuffer &&) noexcept DELETED_FUNCTION;
MemBuffer &operator=(MemBuffer &&) noexcept DELETED_FUNCTION;
#endif
// disable dynamic allocation
UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL
UPX_CXX_DISABLE_COPY_MOVE(MemBuffer) // disable copy and move
UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL(MemBuffer) // disable dynamic allocation
};
/* vim:set ts=4 sw=4 et: */

View File

@ -128,8 +128,10 @@ forceinline void ptr_check_no_overlap(const void *a, size_t a_size, const void *
// invalidate and poison a pointer: point to a non-null invalid address
// - resulting pointer should crash on dereference
// - this should be efficient, so no mmap() guard page etc.
// - this should play nice with runtime checkers like ASAN, MSAN, valgrind, etc.
// - this should play nice with runtime checkers like ASAN, MSAN, UBSAN, valgrind, etc.
// - this should play nice with static analyzers like clang-tidy etc.
// NOTE: this is clearly UB (Undefined Behaviour), and stricter compilers or
// architectures may need a more advanced/costly implementation in the future
template <class T>
inline void ptr_invalidate_and_poison(T *(&ptr)) noexcept {
ptr = (T *) (void *) 251; // 0x000000fb // NOLINT(performance-no-int-to-ptr)