From 875391728c11339c8a6cd3338bcaa5ec0ffc2496 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Breno=20Guimar=C3=A3es?= Date: Thu, 23 Feb 2023 15:29:47 +0000 Subject: [PATCH] Avoid strict aliasing violation on type punning inside llvm::PointerIntPair llvm::PointerIntPair has methods that when used together can invoke undefined behavior by violating strict aliasing. `getPointer()` uses the underlying storage as it's declared: `intptr_t` `getAddrOfPointer()` casts the underlying storage as if it was a `PointerTy` This violates strict aliasing, so depending on how they are used, it's possible to have the compiler to optimize the code in unwanted ways. See the unit test in the patch. We declare a `PointerIntPair` and use the `getAddrOfPointer` method to fill in the a pointer value. Then, when we use `getPointer` the compiler is thrown off, thinking that `intptr_t` storage could not have possibly be changed, and the check fails. Reviewed By: efriedma Differential Revision: https://reviews.llvm.org/D124571 --- llvm/include/llvm/ADT/PointerIntPair.h | 42 ++++++++++++++++++++++++++++--- llvm/unittests/ADT/PointerIntPairTest.cpp | 18 +++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/llvm/include/llvm/ADT/PointerIntPair.h b/llvm/include/llvm/ADT/PointerIntPair.h index 9278ccd..f73f5bc 100644 --- a/llvm/include/llvm/ADT/PointerIntPair.h +++ b/llvm/include/llvm/ADT/PointerIntPair.h @@ -19,10 +19,44 @@ #include "llvm/Support/type_traits.h" #include #include +#include #include namespace llvm { +namespace detail { +template struct PunnedPointer { + static_assert(sizeof(Ptr) == sizeof(intptr_t), ""); + + // Asserts that allow us to let the compiler implement the destructor and + // copy/move constructors + static_assert(std::is_trivially_destructible::value, ""); + static_assert(std::is_trivially_copy_constructible::value, ""); + static_assert(std::is_trivially_move_constructible::value, ""); + + explicit constexpr PunnedPointer(intptr_t i = 0) { *this = i; } + + constexpr intptr_t asInt() const { + intptr_t R = 0; + std::memcpy(&R, Data, sizeof(R)); + return R; + } + + constexpr operator intptr_t() const { return asInt(); } + + constexpr PunnedPointer &operator=(intptr_t V) { + std::memcpy(Data, &V, sizeof(Data)); + return *this; + } + + Ptr *getPointerAddress() { return reinterpret_cast(Data); } + const Ptr *getPointerAddress() const { return reinterpret_cast(Data); } + +private: + alignas(Ptr) unsigned char Data[sizeof(Ptr)]; +}; +} // namespace detail + template struct DenseMapInfo; template struct PointerIntPairInfo; @@ -46,7 +80,7 @@ template Value; public: constexpr PointerIntPair() = default; @@ -86,10 +120,12 @@ public: assert(Value == reinterpret_cast(getPointer()) && "Can only return the address if IntBits is cleared and " "PtrTraits doesn't change the pointer"); - return reinterpret_cast(&Value); + return Value.getPointerAddress(); } - void *getOpaqueValue() const { return reinterpret_cast(Value); } + void *getOpaqueValue() const { + return reinterpret_cast(Value.asInt()); + } void setFromOpaqueValue(void *Val) & { Value = reinterpret_cast(Val); diff --git a/llvm/unittests/ADT/PointerIntPairTest.cpp b/llvm/unittests/ADT/PointerIntPairTest.cpp index b279005..0e1b87c 100644 --- a/llvm/unittests/ADT/PointerIntPairTest.cpp +++ b/llvm/unittests/ADT/PointerIntPairTest.cpp @@ -109,4 +109,22 @@ TEST(PointerIntPairTest, ManyUnusedBits) { "trivially copyable"); } +TEST(PointerIntPairTest, TypePunning) { + int I = 0; + int *IntPtr = &I; + + int **IntPtrBegin = &IntPtr; + int **IntPtrEnd = IntPtrBegin + 1; + + PointerIntPair Pair; + int **PairAddr = Pair.getAddrOfPointer(); + + while (IntPtrBegin != IntPtrEnd) { + *PairAddr = *IntPtrBegin; + ++PairAddr; + ++IntPtrBegin; + } + EXPECT_EQ(Pair.getPointer(), IntPtr); +} + } // end anonymous namespace -- 2.7.4