[clang] [clang][bytecode] Check composite bitcasts for indeterminate bits (PR #118988)

Timm Baeder via cfe-commits cfe-commits at lists.llvm.org
Fri Dec 6 23:18:40 PST 2024


https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/118988

>From 7e723fd3ee3844ad83fa61118dd263a84a6145e3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Fri, 6 Dec 2024 15:52:38 +0100
Subject: [PATCH] [clang][bytecode] Check composite bitcasts for indeterminate
 bits

---
 clang/lib/AST/ByteCode/BitcastBuffer.cpp      | 34 ++++++++++++++++---
 clang/lib/AST/ByteCode/BitcastBuffer.h        |  4 +++
 clang/lib/AST/ByteCode/Interp.h               |  5 +--
 .../lib/AST/ByteCode/InterpBuiltinBitCast.cpp | 33 ++++++++++++++----
 .../ByteCode/builtin-bit-cast-bitfields.cpp   | 31 ++++++++++++-----
 5 files changed, 85 insertions(+), 22 deletions(-)

diff --git a/clang/lib/AST/ByteCode/BitcastBuffer.cpp b/clang/lib/AST/ByteCode/BitcastBuffer.cpp
index 7f29c7c2db0147..fbd500fd8f5f4d 100644
--- a/clang/lib/AST/ByteCode/BitcastBuffer.cpp
+++ b/clang/lib/AST/ByteCode/BitcastBuffer.cpp
@@ -62,11 +62,7 @@ BitcastBuffer::copyBits(Bits BitOffset, Bits BitWidth, Bits FullBitWidth,
 }
 
 bool BitcastBuffer::allInitialized() const {
-  Bits Sum;
-  for (BitRange BR : InitializedBits)
-    Sum += BR.size();
-
-  return Sum == FinalBitSize;
+  return rangeInitialized(Bits::zero(), FinalBitSize);
 }
 
 void BitcastBuffer::markInitialized(Bits Offset, Bits Length) {
@@ -111,6 +107,34 @@ void BitcastBuffer::markInitialized(Bits Offset, Bits Length) {
 #endif
 }
 
+bool BitcastBuffer::rangeInitialized(Bits Offset, Bits Length) const {
+  if (Length.isZero())
+    return true;
+
+  BitRange Range(Offset, Offset + Length - Bits(1));
+  Bits Sum;
+  bool FoundStart = false;
+  for (BitRange BR : InitializedBits) {
+    if (FoundStart) {
+      if (BR.contains(Range.End)) {
+        Sum += (Range.End - BR.Start + Bits(1));
+        break;
+      }
+
+      // Else, BR is completely inside Range.
+      Sum += BR.size();
+    }
+    if (BR.contains(Range.Start)) {
+      Sum += (BR.End - Range.Start + Bits(1));
+      FoundStart = true;
+    }
+  }
+
+  // Note that Sum can be larger than Range, e.g. when Range is fully
+  // contained in one range.
+  return Sum >= Range.size();
+}
+
 #if 0
   template<typename T>
   static std::string hex(T t) {
diff --git a/clang/lib/AST/ByteCode/BitcastBuffer.h b/clang/lib/AST/ByteCode/BitcastBuffer.h
index 00fbdc9b85421d..2a0d8a0cd9a81f 100644
--- a/clang/lib/AST/ByteCode/BitcastBuffer.h
+++ b/clang/lib/AST/ByteCode/BitcastBuffer.h
@@ -55,6 +55,7 @@ struct Bytes {
   Bits toBits() const { return Bits(N * 8); }
 };
 
+/// A bit range. Both Start and End are inclusive.
 struct BitRange {
   Bits Start;
   Bits End;
@@ -62,6 +63,8 @@ struct BitRange {
   BitRange(Bits Start, Bits End) : Start(Start), End(End) {}
   Bits size() const { return End - Start + Bits(1); }
   bool operator<(BitRange Other) const { return Start.N < Other.Start.N; }
+
+  bool contains(Bits B) { return Start <= B && End >= B; }
 };
 
 /// Track what bits have been initialized to known values and which ones
@@ -85,6 +88,7 @@ struct BitcastBuffer {
   /// Marks the bits in the given range as initialized.
   /// FIXME: Can we do this automatically in pushData()?
   void markInitialized(Bits Start, Bits Length);
+  bool rangeInitialized(Bits Offset, Bits Length) const;
 
   /// Push \p BitWidth bits at \p BitOffset from \p In into the buffer.
   /// \p TargetEndianness is the endianness of the target we're compiling for.
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index c5c2a5ef19cc4d..01ea32fa3928b8 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -3043,8 +3043,9 @@ inline bool BitCast(InterpState &S, CodePtr OpPC, bool TargetIsUCharOrByte,
                     uint32_t ResultBitWidth, const llvm::fltSemantics *Sem) {
   const Pointer &FromPtr = S.Stk.pop<Pointer>();
 
-  if (!CheckLoad(S, OpPC, FromPtr))
-    return false;
+  // Only reject uninitialized global variables.
+  if (FromPtr.isStatic() && !FromPtr.isInitialized())
+    return CheckInitialized(S, OpPC, FromPtr, AK_Read);
 
   size_t BuffSize = ResultBitWidth / 8;
   llvm::SmallVector<std::byte> Buff(BuffSize);
diff --git a/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
index 4c25a3bb132fcf..e26410c0b35e86 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
@@ -248,12 +248,10 @@ static bool readPointerToBuffer(const Context &Ctx, const Pointer &FromPtr,
         if (BitWidth.isZero())
           return true;
 
-        if (!P.isInitialized()) {
-          assert(false && "Implement uninitialized value tracking");
-          return ReturnOnUninit;
-        }
+        // Bits will be left uninitialized and diagnosed when reading.
+        if (!P.isInitialized())
+          return true;
 
-        assert(P.isInitialized());
         if (T == PT_Ptr) {
           assert(P.getType()->isNullPtrType());
           // Clang treats nullptr_t has having NO bits in its value
@@ -262,6 +260,7 @@ static bool readPointerToBuffer(const Context &Ctx, const Pointer &FromPtr,
           return true;
         }
 
+        assert(P.isInitialized());
         auto Buff =
             std::make_unique<std::byte[]>(ObjectReprChars.getQuantity());
         // Work around floating point types that contain unused padding bytes.
@@ -355,10 +354,11 @@ bool clang::interp::DoBitCastPtr(InterpState &S, CodePtr OpPC,
       ToPtr, S.getContext(), Buffer.size(),
       [&](const Pointer &P, PrimType T, Bits BitOffset,
           bool PackedBools) -> bool {
-        CharUnits ObjectReprChars = ASTCtx.getTypeSizeInChars(P.getType());
+        QualType PtrType = P.getType();
+        CharUnits ObjectReprChars = ASTCtx.getTypeSizeInChars(PtrType);
         Bits FullBitWidth = Bits(ASTCtx.toBits(ObjectReprChars));
         if (T == PT_Float) {
-          const auto &Semantics = ASTCtx.getFloatTypeSemantics(P.getType());
+          const auto &Semantics = ASTCtx.getFloatTypeSemantics(PtrType);
           Bits NumBits = Bits(llvm::APFloatBase::getSizeInBits(Semantics));
           assert(NumBits.isFullByte());
           assert(NumBits.getQuantity() <= FullBitWidth.getQuantity());
@@ -382,6 +382,25 @@ bool clang::interp::DoBitCastPtr(InterpState &S, CodePtr OpPC,
         else
           BitWidth = FullBitWidth;
 
+        // If any of the bits are uninitialized, we need to abort unless the
+        // target type is std::byte or unsigned char.
+        bool Initialized = Buffer.rangeInitialized(BitOffset, BitWidth);
+        if (!Initialized) {
+          if (!PtrType->isStdByteType() &&
+              !PtrType->isSpecificBuiltinType(BuiltinType::UChar) &&
+              !PtrType->isSpecificBuiltinType(BuiltinType::Char_U)) {
+            const Expr *E = S.Current->getExpr(OpPC);
+            S.FFDiag(E, diag::note_constexpr_bit_cast_indet_dest)
+                << PtrType << S.getLangOpts().CharIsSigned
+                << E->getSourceRange();
+
+            return false;
+          }
+          llvm::errs() << "Not all initialized\n";
+          return true;
+        }
+        llvm::errs() << "All initialized.\n";
+
         auto Memory = Buffer.copyBits(BitOffset, BitWidth, FullBitWidth,
                                       TargetEndianness);
         if (llvm::sys::IsBigEndianHost)
diff --git a/clang/test/AST/ByteCode/builtin-bit-cast-bitfields.cpp b/clang/test/AST/ByteCode/builtin-bit-cast-bitfields.cpp
index e5337a57bf0fe4..5aa4e256e46382 100644
--- a/clang/test/AST/ByteCode/builtin-bit-cast-bitfields.cpp
+++ b/clang/test/AST/ByteCode/builtin-bit-cast-bitfields.cpp
@@ -63,7 +63,7 @@ struct bytes {
 
   constexpr unsigned char operator[](size_t index) {
     if (index < N)
-      return d[index];
+      return d[index]; // expected-note {{read of uninitialized object}}
     return -1;
   }
 };
@@ -141,11 +141,11 @@ namespace BitFields {
                                                  // expected-note {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'byte' is invalid}}
 
     struct M {
-      // ref-note at +1 {{subobject declared here}}
+      // expected-note at +1 {{subobject declared here}}
       unsigned char mem[sizeof(BF)];
     };
-    // ref-error at +2 {{initialized by a constant expression}}
-    // ref-note at +1 {{not initialized}}
+    // expected-error at +2 {{initialized by a constant expression}}
+    // expected-note at +1 {{not initialized}}
     constexpr M m = bit_cast<M>(bf);
 
     constexpr auto f = []() constexpr {
@@ -156,8 +156,8 @@ namespace BitFields {
 
     static_assert(f()[0] + f()[1] + f()[2] == 0xc0 + 0xff + 0xee);
     {
-      // ref-error at +2 {{initialized by a constant expression}}
-      // ref-note at +1 {{read of uninitialized object is not allowed in a constant expression}}
+      // expected-error at +2 {{initialized by a constant expression}}
+      // expected-note at +1 {{in call to}}
       constexpr auto _bad = f()[3];
     }
 
@@ -173,8 +173,8 @@ namespace BitFields {
     };
     static_assert(g().s0 + g().s1 + g().b0 + g().b1 == 0xc0 + 0xff + 0xe + 0xe);
     {
-      // ref-error at +2 {{initialized by a constant expression}}
-      // ref-note at +1 {{read of uninitialized object is not allowed in a constant expression}}
+      // expected-error at +2 {{initialized by a constant expression}}
+      // expected-note at +1 {{read of uninitialized object is not allowed in a constant expression}}
       constexpr auto _bad = g().b2;
     }
   }
@@ -457,4 +457,19 @@ namespace IndeterminateBits {
   };
   constexpr unsigned char B = __builtin_bit_cast(unsigned char, S2{3});
   static_assert(B == (LITTLE_END ? 3 : 192));
+
+
+
+  struct S3 {
+    unsigned a : 13;
+    unsigned   : 17;
+    unsigned b : 2;
+  };
+
+  struct D {
+    unsigned a;
+  };
+  constexpr D s = __builtin_bit_cast(D, S3{12, 3}); // expected-error {{must be initialized by a constant expression}} \
+                                                    // expected-note {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'unsigned int' is invalid}}
+
 }



More information about the cfe-commits mailing list