[clang] [compiler-rt] [UBSan] Fix _BitInt value display for unsigned types in runtime handlers (PR #194405)

Adam Magier via cfe-commits cfe-commits at lists.llvm.org
Mon Apr 27 08:47:04 PDT 2026


https://github.com/AdamMagierFOSS created https://github.com/llvm/llvm-project/pull/194405

EmitCheckTypeDescriptor only emitted TK_BitInt (with the actual bit width appended to the type name) for signed _BitInt types. Unsigned _BitInt was left as TK_Integer, causing the runtime to compute the bit width via 1 << (TypeInfo >> 1), which gives the wrong power-of-2 storage width instead of the actual width. This produced wrong values and wrong bit widths in diagnostics, e.g. "32-bit type" instead of "27-bit type" for _BitInt(27).

Compiler:
- Remove the isSignedIntegerType() guard so TK_BitInt is emitted for both signed and unsigned _BitInt types.

Runtime:
- Remove isSignedBitIntTy() restriction from getBitIntBitCountPointer() and getIntegerBitCount() so the appended bit width is read for all _BitInt types.
- Use getIntegerBitCount() instead of getIntegerBitWidth() in isInlineInt() and in the shift-out-of-bounds and implicit-conversion diagnostic handlers.

Tests:
- Update CodeGen CHECK for unsigned _BitInt(37) to expect TK_BitInt.
- Add shift_exponent_too_big and shift_result_too_big runtime tests that verify correct bit width and value display for _BitInt operands.
- Tighten existing CHECKs for implicit_integer_sign_change and vla_bound to verify actual bit width and value are displayed.

Assisted-by: Kiro CLI / Claude Opus 4.6 (1M context)

>From 66f1c6c36b8c995b8be5e332e918cd696e536d21 Mon Sep 17 00:00:00 2001
From: Adam Magier <adam.a.magier.foss at gmail.com>
Date: Mon, 27 Apr 2026 17:06:35 +0200
Subject: [PATCH] [UBSan] Fix _BitInt value display for unsigned types in
 runtime handlers

EmitCheckTypeDescriptor only emitted TK_BitInt (with the actual bit
width appended to the type name) for signed _BitInt types. Unsigned
_BitInt was left as TK_Integer, causing the runtime to compute the
bit width via 1 << (TypeInfo >> 1), which gives the wrong power-of-2
storage width instead of the actual width. This produced wrong values
and wrong bit widths in diagnostics, e.g. "32-bit type" instead of
"27-bit type" for _BitInt(27).

Compiler:
- Remove the isSignedIntegerType() guard so TK_BitInt is emitted for
  both signed and unsigned _BitInt types.

Runtime:
- Remove isSignedBitIntTy() restriction from getBitIntBitCountPointer()
  and getIntegerBitCount() so the appended bit width is read for all
  _BitInt types.
- Use getIntegerBitCount() instead of getIntegerBitWidth() in
  isInlineInt() and in the shift-out-of-bounds and implicit-conversion
  diagnostic handlers.

Tests:
- Update CodeGen CHECK for unsigned _BitInt(37) to expect TK_BitInt.
- Add shift_exponent_too_big and shift_result_too_big runtime tests
  that verify correct bit width and value display for _BitInt operands.
- Tighten existing CHECKs for implicit_integer_sign_change and
  vla_bound to verify actual bit width and value are displayed.

Assisted-by: Kiro CLI / Claude Opus 4.6 (1M context)
---
 clang/lib/CodeGen/CGExpr.cpp                  |  5 ++--
 clang/test/CodeGen/bit-int-ubsan.c            |  2 +-
 compiler-rt/lib/ubsan/ubsan_handlers.cpp      | 10 +++----
 compiler-rt/lib/ubsan/ubsan_value.h           |  5 ++--
 .../test/ubsan/TestCases/Integer/bit-int.c    | 28 +++++++++++++++++--
 5 files changed, 36 insertions(+), 14 deletions(-)

diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 987a96ba19178..c75b2c0a53d31 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3938,15 +3938,14 @@ llvm::Constant *CodeGenFunction::EmitCheckTypeDescriptor(QualType T) {
     // Follow suggestion from discussion of issue 64100.
     // So we can write the exact amount of bits in TypeName after '\0'
     // making it <diagnostic-like type name>.'\0'.<32-bit width>.
-    if (T->isSignedIntegerType() && T->getAs<BitIntType>()) {
+    if (T->getAs<BitIntType>()) {
       // Do a sanity checks as we are using 32-bit type to store bit length.
       assert(getContext().getTypeSize(T) > 0 &&
              " non positive amount of bits in __BitInt type");
       assert(getContext().getTypeSize(T) <= 0xFFFFFFFF &&
              " too many bits in __BitInt type");
 
-      // Redefine TypeKind with the actual __BitInt type if we have signed
-      // BitInt.
+      // Redefine TypeKind with the actual __BitInt type.
       TypeKind = TK_BitInt;
       IsBitInt = true;
     }
diff --git a/clang/test/CodeGen/bit-int-ubsan.c b/clang/test/CodeGen/bit-int-ubsan.c
index 40e4fde1d1f41..eefa2a40b81ee 100644
--- a/clang/test/CodeGen/bit-int-ubsan.c
+++ b/clang/test/CodeGen/bit-int-ubsan.c
@@ -23,7 +23,7 @@ uint32_t implicit_unsigned_integer_truncation() {
   x += float_divide_by_zero();
   x += integer_divide_by_zero();
   x = x + 0xFFFFFFFFFFFFFFFFULL;
-  // CHECK: constant { i16, i16, [23 x i8] } { i16 0, i16 12, [23 x i8] c"'unsigned _BitInt(37)'\00" }
+  // CHECK: constant { i16, i16, [29 x i8] } { i16 2, i16 12, [29 x i8] c"'unsigned _BitInt(37)'\00%\00\00\00\00\00" }
   uint32_t r = x & 0xFFFFFFFF;
   return r;
 }
diff --git a/compiler-rt/lib/ubsan/ubsan_handlers.cpp b/compiler-rt/lib/ubsan/ubsan_handlers.cpp
index 63319f46734a4..f32ebd7a7a3b2 100644
--- a/compiler-rt/lib/ubsan/ubsan_handlers.cpp
+++ b/compiler-rt/lib/ubsan/ubsan_handlers.cpp
@@ -336,7 +336,7 @@ static void handleShiftOutOfBoundsImpl(ShiftOutOfBoundsData *Data,
 
   ErrorType ET;
   if (RHSVal.isNegative() ||
-      RHSVal.getPositiveIntValue() >= Data->LHSType.getIntegerBitWidth())
+      RHSVal.getPositiveIntValue() >= Data->LHSType.getIntegerBitCount())
     ET = ErrorType::InvalidShiftExponent;
   else
     ET = ErrorType::InvalidShiftBase;
@@ -352,7 +352,7 @@ static void handleShiftOutOfBoundsImpl(ShiftOutOfBoundsData *Data,
     else
       Diag(Loc, DL_Error, ET,
            "shift exponent %0 is too large for %1-bit type %2")
-          << RHSVal << Data->LHSType.getIntegerBitWidth() << Data->LHSType;
+          << RHSVal << Data->LHSType.getIntegerBitCount() << Data->LHSType;
   } else {
     if (LHSVal.isNegative())
       Diag(Loc, DL_Error, ET, "left shift of negative value %0") << LHSVal;
@@ -621,16 +621,16 @@ static void handleImplicitConversion(ImplicitConversionData *Data,
     Diag(Loc, DL_Error, ET,
          "implicit conversion from type %0 of value %1 (%2-bit, %3signed) to "
          "type %4 changed the value to %5 (%6-bit bitfield, %7signed)")
-        << SrcTy << Value(SrcTy, Src) << SrcTy.getIntegerBitWidth()
+        << SrcTy << Value(SrcTy, Src) << SrcTy.getIntegerBitCount()
         << (SrcSigned ? "" : "un") << DstTy << Value(DstTy, Dst)
         << Data->BitfieldBits << (DstSigned ? "" : "un");
   else
     Diag(Loc, DL_Error, ET,
          "implicit conversion from type %0 of value %1 (%2-bit, %3signed) to "
          "type %4 changed the value to %5 (%6-bit, %7signed)")
-        << SrcTy << Value(SrcTy, Src) << SrcTy.getIntegerBitWidth()
+        << SrcTy << Value(SrcTy, Src) << SrcTy.getIntegerBitCount()
         << (SrcSigned ? "" : "un") << DstTy << Value(DstTy, Dst)
-        << DstTy.getIntegerBitWidth() << (DstSigned ? "" : "un");
+        << DstTy.getIntegerBitCount() << (DstSigned ? "" : "un");
 }
 
 void __ubsan::__ubsan_handle_implicit_conversion(ImplicitConversionData *Data,
diff --git a/compiler-rt/lib/ubsan/ubsan_value.h b/compiler-rt/lib/ubsan/ubsan_value.h
index ee523cf5ddda5..840fbae2beeec 100644
--- a/compiler-rt/lib/ubsan/ubsan_value.h
+++ b/compiler-rt/lib/ubsan/ubsan_value.h
@@ -139,7 +139,6 @@ class TypeDescriptor {
 
   const char *getBitIntBitCountPointer() const {
     DCHECK(isBitIntTy());
-    DCHECK(isSignedBitIntTy());
     // Scan Name for zero and return the next address
     const char *p = getTypeName();
     while (*p != '\0')
@@ -150,7 +149,7 @@ class TypeDescriptor {
 
   unsigned getIntegerBitCount() const {
     DCHECK(isIntegerTy());
-    if (isSignedBitIntTy()) {
+    if (isBitIntTy()) {
       u32 BitCountValue;
       internal_memcpy(&BitCountValue, getBitIntBitCountPointer(),
                       sizeof(BitCountValue));
@@ -188,7 +187,7 @@ class Value {
   bool isInlineInt() const {
     CHECK(getType().isIntegerTy());
     const unsigned InlineBits = sizeof(ValueHandle) * 8;
-    const unsigned Bits = getType().getIntegerBitWidth();
+    const unsigned Bits = getType().getIntegerBitCount();
     return Bits <= InlineBits;
   }
 
diff --git a/compiler-rt/test/ubsan/TestCases/Integer/bit-int.c b/compiler-rt/test/ubsan/TestCases/Integer/bit-int.c
index e15c6e6e1756a..5a254d3c975d1 100644
--- a/compiler-rt/test/ubsan/TestCases/Integer/bit-int.c
+++ b/compiler-rt/test/ubsan/TestCases/Integer/bit-int.c
@@ -39,7 +39,7 @@ uint32_t pointer_overflow() __attribute__((no_sanitize("address"))) {
 
 uint32_t vla_bound(_BitInt(37) x) {
   _BitInt(37) a[x - 1];
-  // CHECK: {{.*}}bit-int.c:[[#@LINE-1]]:17: runtime error: variable length array bound evaluates to non-positive value
+  // CHECK: {{.*}}bit-int.c:[[#@LINE-1]]:17: runtime error: variable length array bound evaluates to non-positive value 0
   return 0;
 }
 
@@ -65,7 +65,7 @@ uint32_t float_cast_overflow() {
 
 uint32_t implicit_integer_sign_change(unsigned _BitInt(37) x) {
   _BitInt(37) r = x;
-  // CHECK: {{.*}}bit-int.c:[[#@LINE-1]]:19: runtime error: implicit conversion from type '{{[^']+}}' of value
+  // CHECK: {{.*}}bit-int.c:[[#@LINE-1]]:19: runtime error: implicit conversion from type '{{[^']+}}' of value {{[0-9]+}} (37-bit, unsigned)
   return r & 0xFFFFFFFF;
 }
 
@@ -131,6 +131,27 @@ uint32_t negative_shift5(unsigned _BitInt(37) x)
   // CHECK: {{.*}}bit-int.c:[[#@LINE-1]]:12: runtime error: shift exponent -2 is negative
 }
 
+uint32_t shift_exponent_too_big1(_BitInt(27) x)
+    __attribute__((no_sanitize("memory"))) {
+  unsigned _BitInt(24) c = 27;
+  return (uint32_t)(x << c) & 0xFF;
+  // CHECK: {{.*}}bit-int.c:[[#@LINE-1]]:23: runtime error: shift exponent 27 is too large for 27-bit type '_BitInt(27)'
+}
+
+uint32_t shift_exponent_too_big2(_BitInt(27) x)
+    __attribute__((no_sanitize("memory"))) {
+  _BitInt(24) c = 29;
+  return (uint32_t)(x << c) & 0xFF;
+  // CHECK: {{.*}}bit-int.c:[[#@LINE-1]]:23: runtime error: shift exponent 29 is too large for 27-bit type '_BitInt(27)'
+}
+
+uint32_t shift_result_too_big1(_BitInt(27) x)
+    __attribute__((no_sanitize("memory"))) {
+  _BitInt(24) c = 26;
+  return (uint32_t)(x << c) & 0xFF;
+  // CHECK: {{.*}}bit-int.c:[[#@LINE-1]]:23: runtime error: left shift of 5 by 26 places cannot be represented in type '_BitInt(27)'
+}
+
 uint32_t unsigned_integer_overflow() __attribute__((no_sanitize("memory"))) {
   unsigned _BitInt(37) x = ~0U;
   ++x;
@@ -165,6 +186,9 @@ int main(int argc, char **argv) {
       negative_shift3(5) +
       negative_shift4(5) +
       negative_shift5(5) +
+      shift_exponent_too_big1(5) +
+      shift_exponent_too_big2(5) +
+      shift_result_too_big1(5) +
       unsigned_integer_overflow() +
       signed_integer_overflow();
   // clang-format on



More information about the cfe-commits mailing list