[clang] [Clang] Add missing __ob_trap check for sign change (PR #188340)
Justin Stitt via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 2 09:17:02 PDT 2026
https://github.com/JustinStitt updated https://github.com/llvm/llvm-project/pull/188340
>From f33e24dfe26a15ba6a58c658a182211ad6cbe329 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Tue, 24 Mar 2026 13:06:48 -0700
Subject: [PATCH] [Clang] Add missing __ob_trap check for sign change
Add a missing OBTrapInvolved check before EmitIntegerSignChangeCheck().
This is considered "missing" as a previous attempt [1] to properly add
an __ob_trap backdoor missed this particular instance.
This backdoor is needed because __ob_trap types are very picky about
implicit conversions (including implicit sign change):
```c
unsigned int __ob_trap big = 4294967295;
(signed int)big; // should trap!
```
[1]: https://github.com/llvm/llvm-project/pull/185772
Signed-off-by: Justin Stitt <justinstitt at google.com>
---
clang/lib/CodeGen/CGExprScalar.cpp | 53 +++++++++--------
.../CodeGen/overflow-behavior-types-scl.c | 17 ++++++
clang/test/CodeGen/overflow-behavior-types.c | 59 +++++++++++++++++--
3 files changed, 100 insertions(+), 29 deletions(-)
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index d208b760396b6..a8dcf22992983 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -1355,14 +1355,18 @@ void ScalarExprEmitter::EmitIntegerSignChangeCheck(Value *Src, QualType SrcType,
return;
}
// Does an SSCL have an entry for the DstType under its respective sanitizer
- // section?
- if (DstSigned && CGF.getContext().isTypeIgnoredBySanitizer(
- SanitizerKind::ImplicitSignedIntegerTruncation, DstType))
- return;
- if (!DstSigned &&
- CGF.getContext().isTypeIgnoredBySanitizer(
- SanitizerKind::ImplicitUnsignedIntegerTruncation, DstType))
- return;
+ // section? Don't check this if an __ob_trap type is involved as it has
+ // priority to emit checks regardless of sanitizer case lists.
+ if (!OBTrapInvolved) {
+ if (DstSigned &&
+ CGF.getContext().isTypeIgnoredBySanitizer(
+ SanitizerKind::ImplicitSignedIntegerTruncation, DstType))
+ return;
+ if (!DstSigned &&
+ CGF.getContext().isTypeIgnoredBySanitizer(
+ SanitizerKind::ImplicitUnsignedIntegerTruncation, DstType))
+ return;
+ }
// That's it. We can't rule out any more cases with the data we have.
auto CheckHandler = SanitizerHandler::ImplicitConversion;
@@ -1670,6 +1674,20 @@ Value *ScalarExprEmitter::EmitScalarConversion(Value *Src, QualType SrcType,
llvm::Type *DstTy = ConvertType(DstType);
+ // Determine whether an overflow behavior of 'trap' has been specified for
+ // either the destination or the source types. If so, we can elide sanitizer
+ // capability checks as this overflow behavior kind is also capable of
+ // emitting traps without runtime sanitizer support.
+ // Also skip instrumentation if either source or destination has 'wrap'
+ // behavior - the user has explicitly indicated they accept wrapping
+ // semantics. Use non-canonical types to preserve OBT annotations.
+ const auto *DstOBT = NoncanonicalDstType->getAs<OverflowBehaviorType>();
+ const auto *SrcOBT = NoncanonicalSrcType->getAs<OverflowBehaviorType>();
+ bool OBTrapInvolved =
+ (DstOBT && DstOBT->isTrapKind()) || (SrcOBT && SrcOBT->isTrapKind());
+ bool OBWrapInvolved =
+ (DstOBT && DstOBT->isWrapKind()) || (SrcOBT && SrcOBT->isWrapKind());
+
// Cast from half through float if half isn't a native type.
if (SrcType->isHalfType() && !CGF.getContext().getLangOpts().NativeHalfType) {
// Cast to FP using the intrinsic if the half type itself isn't supported.
@@ -1696,9 +1714,10 @@ Value *ScalarExprEmitter::EmitScalarConversion(Value *Src, QualType SrcType,
// Ignore conversions like int -> uint.
if (SrcTy == DstTy) {
- if (Opts.EmitImplicitIntegerSignChangeChecks)
+ if (Opts.EmitImplicitIntegerSignChangeChecks ||
+ (OBTrapInvolved && !OBWrapInvolved))
EmitIntegerSignChangeCheck(Src, NoncanonicalSrcType, Src,
- NoncanonicalDstType, Loc);
+ NoncanonicalDstType, Loc, OBTrapInvolved);
return Src;
}
@@ -1829,20 +1848,6 @@ Value *ScalarExprEmitter::EmitScalarConversion(Value *Src, QualType SrcType,
}
}
- // Determine whether an overflow behavior of 'trap' has been specified for
- // either the destination or the source types. If so, we can elide sanitizer
- // capability checks as this overflow behavior kind is also capable of
- // emitting traps without runtime sanitizer support.
- // Also skip instrumentation if either source or destination has 'wrap'
- // behavior - the user has explicitly indicated they accept wrapping
- // semantics. Use non-canonical types to preserve OBT annotations.
- const auto *DstOBT = NoncanonicalDstType->getAs<OverflowBehaviorType>();
- const auto *SrcOBT = NoncanonicalSrcType->getAs<OverflowBehaviorType>();
- bool OBTrapInvolved =
- (DstOBT && DstOBT->isTrapKind()) || (SrcOBT && SrcOBT->isTrapKind());
- bool OBWrapInvolved =
- (DstOBT && DstOBT->isWrapKind()) || (SrcOBT && SrcOBT->isWrapKind());
-
if ((Opts.EmitImplicitIntegerTruncationChecks || OBTrapInvolved) &&
!OBWrapInvolved && !Opts.PatternExcluded)
EmitIntegerTruncationCheck(Src, NoncanonicalSrcType, Res,
diff --git a/clang/test/CodeGen/overflow-behavior-types-scl.c b/clang/test/CodeGen/overflow-behavior-types-scl.c
index 19022f083f7eb..76b7aaf5e3264 100644
--- a/clang/test/CodeGen/overflow-behavior-types-scl.c
+++ b/clang/test/CodeGen/overflow-behavior-types-scl.c
@@ -11,6 +11,10 @@
// RUN: -fexperimental-overflow-behavior-types -fsanitize=implicit-unsigned-integer-truncation,implicit-signed-integer-truncation \
// RUN: -emit-llvm -o - | FileCheck %s --check-prefix=TRUNC
+// RUN: %clang_cc1 -triple x86_64-linux-gnu %t/test.c -fsanitize-ignorelist=%t/sign-change.scl \
+// RUN: -fexperimental-overflow-behavior-types -fsanitize=implicit-integer-sign-change \
+// RUN: -emit-llvm -o - | FileCheck %s --check-prefix=SIGNCHG
+
//--- sio.scl
[signed-integer-overflow]
# ignore signed-integer-overflow instrumentation across all types
@@ -25,6 +29,10 @@ type:*
[{implicit-unsigned-integer-truncation,implicit-signed-integer-truncation}]
type:*
+//--- sign-change.scl
+[implicit-integer-sign-change]
+type:*
+
//--- test.c
#define __wrap __attribute__((overflow_behavior("wrap")))
#define __no_trap __attribute__((overflow_behavior("trap")))
@@ -62,3 +70,12 @@ void bar(int value) {
// TRUNC-NEXT: br i1 %[[TCHECK]], {{.*}}%handler.implicit_conversion
unsigned char __ob_trap a = value;
}
+
+// SIGNCHG-LABEL: define {{.*}} @obt_priority_over_scl_for_sign_change_sanitizer
+void obt_priority_over_scl_for_sign_change_sanitizer(unsigned int __ob_trap a) {
+ // SIGNCHG: %[[T0:.*]] = load i32, ptr %a.addr
+ // SIGNCHG-NEXT: %[[NEG0:.*]] = icmp slt i32 %[[T0]], 0
+ // SIGNCHG-NEXT: %[[SIGN0:.*]] = icmp eq i1 false, %[[NEG0]]
+ // SIGNCHG-NEXT: br i1 %[[SIGN0]], {{.*}} %handler.implicit_conversion
+ (signed int)a;
+}
diff --git a/clang/test/CodeGen/overflow-behavior-types.c b/clang/test/CodeGen/overflow-behavior-types.c
index 5c844d321e8ab..109888f707506 100644
--- a/clang/test/CodeGen/overflow-behavior-types.c
+++ b/clang/test/CodeGen/overflow-behavior-types.c
@@ -175,9 +175,18 @@ int explicit_truncation_cast(__ob_trap unsigned long long result) {
return (int)result;
}
+// EXCL-LABEL: define {{.*}} @pattern_exclusion_priority_over_obt
+void pattern_exclusion_priority_over_obt(unsigned char __ob_trap count) {
+ // EXCL: while.cond
+ // EXCL: br i1 %tobool, label %while.body
+ // EXCL: br label %while.cond
+ while (count--) {}
+}
+
// Make sure __ob_trap types warn on sign change with
// -fsanitize=implicit-integer-sign-change or trap otherwise.
// DEFAULT-LABEL: define {{.*}} @unsigned_to_signed_cast
+// NOSAN-LABEL: define {{.*}} @unsigned_to_signed_cast
void unsigned_to_signed_cast(__ob_trap unsigned long long a) {
// DEFAULT: %[[T0:.*]] = load i64, ptr %a.addr
// DEFAULT-NEXT: %[[CONV0:.*]] = trunc i64 %[[T0]] to i8
@@ -195,9 +204,49 @@ void unsigned_to_signed_cast(__ob_trap unsigned long long a) {
(signed char)(a);
}
-// EXCL-LABEL: define {{.*}} @pattern_exclusion_priority_over_obt
-void pattern_exclusion_priority_over_obt(unsigned char __ob_trap count) {
- // EXCL: br label %while.cond
- // EXCL-NOT: %truncheck
- while (count--) {}
+// DEFAULT-LABEL: define {{.*}} @signed_to_unsigned_cast
+// NOSAN-LABEL: define {{.*}} @signed_to_unsigned_cast
+void signed_to_unsigned_cast(__ob_trap signed long long a) {
+ // DEFAULT: %[[T0:.*]] = load i64, ptr %a.addr
+ // DEFAULT-NEXT: %[[CONV0:.*]] = trunc i64 %[[T0]] to i8
+ // DEFAULT: %[[TRUNC0:.*]] = icmp eq i64 {{.*}}, %[[T0]]
+ // DEFAULT-NEXT: br i1 %[[TRUNC0]], {{.*}} %handler.implicit_conversion
+
+ // NOSAN: %[[T0:.*]] = load i64, ptr %a.addr
+ // NOSAN-NEXT: %[[CONV0:.*]] = trunc i64 %[[T0]] to i8
+ // NOSAN: %[[TRUNC0:.*]] = icmp eq i64 {{.*}}, %[[T0]]
+ // NOSAN-NEXT: br i1 %[[TRUNC0]], {{.*}} %trap
+ (unsigned char)(a);
+}
+
+// DEFAULT-LABEL: define {{.*}} @unsigned_to_signed_cast_same_size
+// NOSAN-LABEL: define {{.*}} @unsigned_to_signed_cast_same_size
+void unsigned_to_signed_cast_same_size(__ob_trap unsigned int a) {
+ // DEFAULT: %[[T0:.*]] = load i32, ptr %a.addr
+ // DEFAULT-NEXT: %[[NEG:.*]] = icmp slt i32 %[[T0]], 0
+ // DEFAULT-NEXT: %[[SIGN0:.*]] = icmp eq i1 false, %[[NEG]]
+ // DEFUALT-NEXT: br i1 %[[SIGN0]], {{.*}}, label %handler.implicit_conversion
+
+ // NOSAN: %[[T0:.*]] = load i32, ptr %a.addr
+ // NOSAN-NEXT: %[[NEG:.*]] = icmp slt i32 %[[T0]], 0
+ // NOSAN-NEXT: %[[SIGN0:.*]] = icmp eq i1 false, %[[NEG]]
+ // NOSAN: %[[T1:.*]] = and i1 %[[SIGN0]]
+ // NOSAN-NEXT: br i1 %[[T1]], {{.*}}, label %trap
+ (signed int)(a);
+}
+
+// DEFAULT-LABEL: define {{.*}} @signed_to_unsigned_same_size
+// NOSAN-LABEL: define {{.*}} @signed_to_unsigned_same_size
+void signed_to_unsigned_same_size(__ob_trap signed int a) {
+ // DEFAULT: %[[T0:.*]] = load i32, ptr %a.addr
+ // DEFAULT-NEXT: %[[NEG:.*]] = icmp slt i32 %[[T0]], 0
+ // DEFAULT-NEXT: %[[SIGN0:.*]] = icmp eq i1 %[[NEG]], false
+ // DEFUALT-NEXT: br i1 %[[SIGN0]], {{.*}}, label %handler.implicit_conversion
+
+ // NOSAN: %[[T0:.*]] = load i32, ptr %a.addr
+ // NOSAN-NEXT: %[[NEG:.*]] = icmp slt i32 %[[T0]], 0
+ // NOSAN-NEXT: %[[SIGN0:.*]] = icmp eq i1 %[[NEG]], false
+ // NOSAN: %[[T1:.*]] = and i1 %[[SIGN0]]
+ // NOSAN-NEXT: br i1 %[[T1]], {{.*}}, label %trap
+ (unsigned int)(a);
}
More information about the cfe-commits
mailing list