[clang] [compiler-rt] [ubsan] Improve scoped enum diagnostic printer (PR #216460)

via llvm-commits llvm-commits at lists.llvm.org
Sun Aug 16 19:57:02 PDT 2026


https://github.com/zebullax updated https://github.com/llvm/llvm-project/pull/216460

>From 59284200a3e2e65ec041fba72aaf0339e374c755 Mon Sep 17 00:00:00 2001
From: zebullax <zebullax at gmail.com>
Date: Sat, 15 Aug 2026 16:02:54 +0900
Subject: [PATCH 1/4] Improve scoped enum printer

---
 clang/lib/CodeGen/CGExpr.cpp | 23 ++++++++++++++++-------
 1 file changed, 16 insertions(+), 7 deletions(-)

diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 9201e40bc13a1..c8ac2fd53fc82 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3959,18 +3959,27 @@ llvm::Constant *CodeGenFunction::EmitCheckTypeDescriptor(QualType T) {
   uint16_t TypeInfo = 0;
   bool IsBitInt = false;
 
-  if (T->isIntegerType()) {
+  // isIntegerType() never holds for scoped enums, and getAs<BitIntType> can't
+  // see through the EnumType node to a __BitInt underlying type even for
+  // unscoped enums.
+  QualType ValueTy = T;
+  if (const EnumType *ET = T->getAs<EnumType>()) {
+    if (ET->getDecl()->isComplete())
+      ValueTy = ET->getDecl()->getIntegerType();
+  }
+
+  if (ValueTy->isIntegerType()) {
     TypeKind = TK_Integer;
-    TypeInfo = (llvm::Log2_32(getContext().getTypeSize(T)) << 1) |
-               (T->isSignedIntegerType() ? 1 : 0);
+    TypeInfo = (llvm::Log2_32(getContext().getTypeSize(ValueTy)) << 1) |
+               (ValueTy->isSignedIntegerType() ? 1 : 0);
     // 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 (ValueTy->isSignedIntegerType() && ValueTy->getAs<BitIntType>()) {
       // Do a sanity checks as we are using 32-bit type to store bit length.
-      assert(getContext().getTypeSize(T) > 0 &&
+      assert(getContext().getTypeSize(ValueTy) > 0 &&
              " non positive amount of bits in __BitInt type");
-      assert(getContext().getTypeSize(T) <= 0xFFFFFFFF &&
+      assert(getContext().getTypeSize(ValueTy) <= 0xFFFFFFFF &&
              " too many bits in __BitInt type");
 
       // Redefine TypeKind with the actual __BitInt type if we have signed
@@ -3994,7 +4003,7 @@ llvm::Constant *CodeGenFunction::EmitCheckTypeDescriptor(QualType T) {
     // The Structure is: 0 to end the string, 32 bit unsigned integer in target
     // endianness, zero.
     char S[6] = {'\0', '\0', '\0', '\0', '\0', '\0'};
-    const auto *EIT = T->castAs<BitIntType>();
+    const auto *EIT = ValueTy->castAs<BitIntType>();
     uint32_t Bits = EIT->getNumBits();
     llvm::support::endian::write32(S + 1, Bits,
                                    getTarget().isBigEndian()

>From 50fd4cfd4f816d1c0b8a162fe47ab7100775cde1 Mon Sep 17 00:00:00 2001
From: zebullax <zebullax at gmail.com>
Date: Sat, 15 Aug 2026 16:04:42 +0900
Subject: [PATCH 2/4] Update error message for enum value check

---
 compiler-rt/test/ubsan/TestCases/Misc/enum.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/compiler-rt/test/ubsan/TestCases/Misc/enum.cpp b/compiler-rt/test/ubsan/TestCases/Misc/enum.cpp
index 2684029c2960b..b51347f07c050 100644
--- a/compiler-rt/test/ubsan/TestCases/Misc/enum.cpp
+++ b/compiler-rt/test/ubsan/TestCases/Misc/enum.cpp
@@ -25,7 +25,7 @@ int main(int argc, char **argv) {
   return ((int)e1 != -1) & ((int)e2 != -1) &
          // CHECK: error: load of value 4294967295, which is not a valid value for type 'E'
          ((int)e3 != -1) & ((int)e4 == 1) &
-         // CHECK: error: load of value <unknown>, which is not a valid value for type 'enum EBool'
+         // CHECK: error: load of value 255, which is not a valid value for type 'enum EBool'
          ((int)e5 == 2) & ((int)e6 == 1) &
          // CHECK: error: load of value 2, which is not a valid value for type 'EEmpty'
          ((int)e7 == 2);

>From e1276090dd75b3a2192c6cdd6523c4253cf6cd30 Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Mon, 17 Aug 2026 10:52:27 +0900
Subject: [PATCH 3/4] Use original T as much as possible

Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
 clang/lib/CodeGen/CGExpr.cpp | 27 +++++++++++++--------------
 1 file changed, 13 insertions(+), 14 deletions(-)

diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index c8ac2fd53fc82..d7cf52d24474f 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3962,24 +3962,23 @@ llvm::Constant *CodeGenFunction::EmitCheckTypeDescriptor(QualType T) {
   // isIntegerType() never holds for scoped enums, and getAs<BitIntType> can't
   // see through the EnumType node to a __BitInt underlying type even for
   // unscoped enums.
-  QualType ValueTy = T;
-  if (const EnumType *ET = T->getAs<EnumType>()) {
+  QualType BackupT = T;
+  if (const EnumType *ET = T->getAs<EnumType>())
     if (ET->getDecl()->isComplete())
-      ValueTy = ET->getDecl()->getIntegerType();
-  }
+      T = ET->getDecl()->getIntegerType();
 
-  if (ValueTy->isIntegerType()) {
+  if (T->isIntegerType()) {
     TypeKind = TK_Integer;
-    TypeInfo = (llvm::Log2_32(getContext().getTypeSize(ValueTy)) << 1) |
-               (ValueTy->isSignedIntegerType() ? 1 : 0);
+    TypeInfo = (llvm::Log2_32(getContext().getTypeSize(T)) << 1) |
+               (T->isSignedIntegerType() ? 1 : 0);
     // 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 (ValueTy->isSignedIntegerType() && ValueTy->getAs<BitIntType>()) {
+    if (T->isSignedIntegerType() && T->getAs<BitIntType>()) {
       // Do a sanity checks as we are using 32-bit type to store bit length.
-      assert(getContext().getTypeSize(ValueTy) > 0 &&
+      assert(getContext().getTypeSize(T) > 0 &&
              " non positive amount of bits in __BitInt type");
-      assert(getContext().getTypeSize(ValueTy) <= 0xFFFFFFFF &&
+      assert(getContext().getTypeSize(T) <= 0xFFFFFFFF &&
              " too many bits in __BitInt type");
 
       // Redefine TypeKind with the actual __BitInt type if we have signed
@@ -3996,14 +3995,14 @@ llvm::Constant *CodeGenFunction::EmitCheckTypeDescriptor(QualType T) {
   // optionally an 'aka'.
   SmallString<32> Buffer;
   CGM.getDiags().ConvertArgToString(DiagnosticsEngine::ak_qualtype,
-                                    (intptr_t)T.getAsOpaquePtr(), StringRef(),
-                                    StringRef(), {}, Buffer, {});
+                                    (intptr_t)BackupT.getAsOpaquePtr(),
+                                    StringRef(), StringRef(), {}, Buffer, {});
 
   if (IsBitInt) {
     // The Structure is: 0 to end the string, 32 bit unsigned integer in target
     // endianness, zero.
     char S[6] = {'\0', '\0', '\0', '\0', '\0', '\0'};
-    const auto *EIT = ValueTy->castAs<BitIntType>();
+    const auto *EIT = T->castAs<BitIntType>();
     uint32_t Bits = EIT->getNumBits();
     llvm::support::endian::write32(S + 1, Bits,
                                    getTarget().isBigEndian()
@@ -4026,7 +4025,7 @@ llvm::Constant *CodeGenFunction::EmitCheckTypeDescriptor(QualType T) {
   CGM.getSanitizerMetadata()->disableSanitizerForGlobal(GV);
 
   // Remember the descriptor for this type.
-  CGM.setTypeDescriptorInMap(T, GV);
+  CGM.setTypeDescriptorInMap(BackupT, GV);
 
   return GV;
 }

>From dfb83a9bc989182f3c12c3db4d9d94b0753ec978 Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Mon, 17 Aug 2026 11:56:38 +0900
Subject: [PATCH 4/4] Add codegen test on diagnostic printer

Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
 .../CodeGen/ubsan-enum-type-descriptor.cpp    | 40 +++++++++++++++++++
 1 file changed, 40 insertions(+)
 create mode 100644 clang/test/CodeGen/ubsan-enum-type-descriptor.cpp

diff --git a/clang/test/CodeGen/ubsan-enum-type-descriptor.cpp b/clang/test/CodeGen/ubsan-enum-type-descriptor.cpp
new file mode 100644
index 0000000000000..459a9d7caf260
--- /dev/null
+++ b/clang/test/CodeGen/ubsan-enum-type-descriptor.cpp
@@ -0,0 +1,40 @@
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=enum -emit-llvm %s -o - | FileCheck %s
+
+// The UBSan type descriptor wraps the size and signedness of the checked type
+// so that the runtime can print the offending value. An enum does have the same
+// size and value representation as its underlying type, so the descriptor has
+// to unwrap up to the underlying type and not the enum: querying the enum type
+// itself leaves a scoped enum as TK_Unknown (i16 -1) with no width, and the
+// runtime then prints "<unknown>" instead of the value.
+
+// A scoped enum has a fixed underlying type, so it only reaches the range check
+// when that type has values outside the enumerator range, i.e. bool.
+enum class EBool : bool { a = 1 };
+
+// TypeKind is TK_Integer (0) and TypeInfo is (log2(8) << 1) => 6.
+// Name length includes the quotes (''), so 8
+// CHECK: @{{[0-9]+}} = private unnamed_addr constant { i16, i16, [8 x i8] } { i16 0, i16 6, [8 x i8] c"'EBool'\00" }
+
+enum EPlain { p = 1 };
+
+// TypeInfo is (log2(32) << 1) ==> 10.
+// CHECK: @{{[0-9]+}} = private unnamed_addr constant { i16, i16, [9 x i8] } { i16 0, i16 10, [9 x i8] c"'EPlain'\00" }
+
+// A negative enumerator gives the enum a signed underlying type, which sets the
+// low bit of TypeInfo.
+enum ENeg { n = -1 };
+
+// TypeInfo is ((log2(32) << 1) | 1) ==> 11.
+// CHECK: @{{[0-9]+}} = private unnamed_addr constant { i16, i16, [7 x i8] } { i16 0, i16 11, [7 x i8] c"'ENeg'\00" }
+
+// Descriptors are only emitted where a check is, so force a checked load of
+// each
+
+bool load_scoped(EBool *p) { return (bool)*p; }
+// CHECK: call void @__ubsan_handle_load_invalid_value_abort
+
+int load_unscoped(EPlain *p) { return (int)*p; }
+// CHECK: call void @__ubsan_handle_load_invalid_value_abort
+
+int load_signed(ENeg *p) { return (int)*p; }
+// CHECK: call void @__ubsan_handle_load_invalid_value_abort



More information about the llvm-commits mailing list