[clang] [compiler-rt] [UBSan] Add check that detects division by zero for fixed-point types (PR #165181)
Victor Chernyakin via llvm-commits
llvm-commits at lists.llvm.org
Sun Oct 26 16:59:16 PDT 2025
https://github.com/localspook updated https://github.com/llvm/llvm-project/pull/165181
>From f6574b6b59b91ce299fa6977b2b326aae0bf235d Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Sun, 26 Oct 2025 16:57:54 -0700
Subject: [PATCH] [UBSan] Add check that detects division by zero for
fixed-point types
---
clang/docs/ReleaseNotes.rst | 2 +-
clang/docs/UndefinedBehaviorSanitizer.rst | 4 ++++
clang/include/clang/Basic/Features.def | 4 ++++
clang/include/clang/Basic/Sanitizers.def | 4 +++-
clang/lib/CodeGen/CGExpr.cpp | 8 ++++++--
clang/lib/CodeGen/CGExprScalar.cpp | 13 ++++++++++++-
.../has_feature_undefined_behavior_sanitizer.cpp | 16 ++++++++++++++++
compiler-rt/lib/ubsan/ubsan_checks.inc | 2 ++
compiler-rt/lib/ubsan/ubsan_handlers.cpp | 2 ++
compiler-rt/lib/ubsan/ubsan_value.h | 4 ++++
.../TestCases/FixedPoint/divide-by-zero.cpp | 10 ++++++++++
11 files changed, 64 insertions(+), 5 deletions(-)
create mode 100644 compiler-rt/test/ubsan/TestCases/FixedPoint/divide-by-zero.cpp
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index edb872c1f388d..b4a5f7dcbfa20 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -274,7 +274,7 @@ New Compiler Flags
- New option ``-fno-sanitize-debug-trap-reasons`` added to disable emitting trap reasons into the debug info when compiling with trapping UBSan (e.g. ``-fsanitize-trap=undefined``).
- New option ``-fsanitize-debug-trap-reasons=`` added to control emitting trap reasons into the debug info when compiling with trapping UBSan (e.g. ``-fsanitize-trap=undefined``).
- New options for enabling allocation token instrumentation: ``-fsanitize=alloc-token``, ``-falloc-token-max=``, ``-fsanitize-alloc-token-fast-abi``, ``-fsanitize-alloc-token-extended``.
-
+- New option for diagnosing division by zero with fixed point numbers (``-ffixed-point``): ``-fsanitize=fixed-point-divide-by-zero``, and a new group that includes it: ``-fsanitize=fixed-point``.
Lanai Support
^^^^^^^^^^^^^^
diff --git a/clang/docs/UndefinedBehaviorSanitizer.rst b/clang/docs/UndefinedBehaviorSanitizer.rst
index 0a2d833783e57..568d3f092de3e 100644
--- a/clang/docs/UndefinedBehaviorSanitizer.rst
+++ b/clang/docs/UndefinedBehaviorSanitizer.rst
@@ -119,6 +119,8 @@ Available checks are:
- ``-fsanitize=enum``: Load of a value of an enumerated type which
is not in the range of representable values for that enumerated
type.
+ - ``-fsanitize=fixed-point-divide-by-zero``: Fixed point division by zero
+ (when compiling with ``-ffixed-point``).
- ``-fsanitize=float-cast-overflow``: Conversion to, from, or
between floating-point types which would overflow the
destination. Because the range of representable values for all
@@ -224,6 +226,8 @@ You can also use the following check groups:
``nullability-*`` group of checks.
- ``-fsanitize=undefined-trap``: Deprecated alias of
``-fsanitize=undefined``.
+ - ``-fsanitize=fixed-point``: Checks for undefined behavior with fixed point
+ values. Enables ``-fsanitize=fixed-point-divide-by-zero``.
- ``-fsanitize=implicit-integer-truncation``: Catches lossy integral
conversions. Enables ``implicit-signed-integer-truncation`` and
``implicit-unsigned-integer-truncation``.
diff --git a/clang/include/clang/Basic/Features.def b/clang/include/clang/Basic/Features.def
index 0e91b42a132c1..204150627fd8f 100644
--- a/clang/include/clang/Basic/Features.def
+++ b/clang/include/clang/Basic/Features.def
@@ -66,6 +66,10 @@ FEATURE(array_bounds_sanitizer,
LangOpts.Sanitize.has(SanitizerKind::ArrayBounds))
FEATURE(enum_sanitizer,
LangOpts.Sanitize.has(SanitizerKind::Enum))
+FEATURE(fixed_point_divide_by_zero_sanitizer,
+ LangOpts.Sanitize.has(SanitizerKind::FixedPointDivideByZero))
+FEATURE(fixed_point_sanitizer,
+ LangOpts.Sanitize.hasOneOf(SanitizerKind::FixedPoint))
FEATURE(float_cast_overflow_sanitizer,
LangOpts.Sanitize.has(SanitizerKind::FloatCastOverflow))
FEATURE(integer_divide_by_zero_sanitizer,
diff --git a/clang/include/clang/Basic/Sanitizers.def b/clang/include/clang/Basic/Sanitizers.def
index da85431625026..b77a838d71da1 100644
--- a/clang/include/clang/Basic/Sanitizers.def
+++ b/clang/include/clang/Basic/Sanitizers.def
@@ -94,6 +94,8 @@ SANITIZER("array-bounds", ArrayBounds)
SANITIZER("bool", Bool)
SANITIZER("builtin", Builtin)
SANITIZER("enum", Enum)
+SANITIZER("fixed-point-divide-by-zero", FixedPointDivideByZero)
+SANITIZER_GROUP("fixed-point", FixedPoint, FixedPointDivideByZero)
SANITIZER("float-cast-overflow", FloatCastOverflow)
SANITIZER("float-divide-by-zero", FloatDivideByZero)
SANITIZER("function", Function)
@@ -149,7 +151,7 @@ SANITIZER("shadow-call-stack", ShadowCallStack)
// ABI or address space layout implications, and only catch undefined behavior.
SANITIZER_GROUP("undefined", Undefined,
Alignment | Bool | Builtin | ArrayBounds | Enum |
- FloatCastOverflow |
+ FixedPoint | FloatCastOverflow |
IntegerDivideByZero | NonnullAttribute | Null | ObjectSize |
PointerOverflow | Return | ReturnsNonnullAttribute | Shift |
SignedIntegerOverflow | Unreachable | VLABound | Function)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index e8255b0554da8..02876e72f83cb 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -79,6 +79,8 @@ enum VariableTypeDescriptorKind : uint16_t {
TK_Float = 0x0001,
/// An _BitInt(N) type.
TK_BitInt = 0x0002,
+ /// A fixed-point type.
+ TK_FixedPoint = 0x0003,
/// Any other type. The value representation is unspecified.
TK_Unknown = 0xffff
};
@@ -3724,8 +3726,8 @@ LValue CodeGenFunction::EmitPredefinedLValue(const PredefinedExpr *E) {
///
/// followed by an array of i8 containing the type name with extra information
/// for BitInt. TypeKind is TK_Integer(0) for an integer, TK_Float(1) for a
-/// floating point value, TK_BitInt(2) for BitInt and TK_Unknown(0xFFFF) for
-/// anything else.
+/// floating point value, TK_BitInt(2) for BitInt, TK_FixedPoint(3) for a
+// fixed point value, and TK_Unknown(0xFFFF) for anything else.
llvm::Constant *CodeGenFunction::EmitCheckTypeDescriptor(QualType T) {
// Only emit each type's descriptor once.
if (llvm::Constant *C = CGM.getTypeDescriptorFromMap(T))
@@ -3757,6 +3759,8 @@ llvm::Constant *CodeGenFunction::EmitCheckTypeDescriptor(QualType T) {
} else if (T->isFloatingType()) {
TypeKind = TK_Float;
TypeInfo = getContext().getTypeSize(T);
+ } else if (T->isFixedPointType()) {
+ TypeKind = TK_FixedPoint;
}
// Format the type name as if for a diagnostic, including quotes and
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 715160d067817..65474a3f14d13 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -4591,9 +4591,20 @@ Value *ScalarExprEmitter::EmitFixedPointBinOp(const BinOpInfo &op) {
Result = FPBuilder.CreateMul(LHS, LHSFixedSema, RHS, RHSFixedSema);
break;
case BO_DivAssign:
- case BO_Div:
+ case BO_Div: {
+ SanitizerDebugLocation SanScope(&CGF,
+ {SanitizerKind::SO_FixedPointDivideByZero},
+ SanitizerHandler::DivremOverflow);
+ if (CGF.SanOpts.has(SanitizerKind::FixedPointDivideByZero)) {
+ Value *Zero = llvm::Constant::getNullValue(RHS->getType());
+ const std::pair<Value *, SanitizerKind::SanitizerOrdinal> Check = {
+ Builder.CreateICmpNE(RHS, Zero),
+ SanitizerKind::SO_FixedPointDivideByZero};
+ EmitBinOpCheck(Check, op);
+ }
Result = FPBuilder.CreateDiv(LHS, LHSFixedSema, RHS, RHSFixedSema);
break;
+ }
case BO_ShlAssign:
case BO_Shl:
Result = FPBuilder.CreateShl(LHS, LHSFixedSema, RHS);
diff --git a/clang/test/Lexer/has_feature_undefined_behavior_sanitizer.cpp b/clang/test/Lexer/has_feature_undefined_behavior_sanitizer.cpp
index e1a07d215b549..e1f9827bfed0f 100644
--- a/clang/test/Lexer/has_feature_undefined_behavior_sanitizer.cpp
+++ b/clang/test/Lexer/has_feature_undefined_behavior_sanitizer.cpp
@@ -4,6 +4,8 @@
// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=builtin %s -o - | FileCheck --check-prefixes=CHECK-UBSAN,CHECK-BUILTIN %s
// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=array-bounds %s -o - | FileCheck --check-prefixes=CHECK-UBSAN,CHECK-ARRAY-BOUNDS %s
// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=enum %s -o - | FileCheck --check-prefixes=CHECK-UBSAN,CHECK-ENUM %s
+// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=fixed-point-divide-by-zero %s -o - | FileCheck --check-prefixes=CHECK-UBSAN,CHECK-FIXED-POINT-DIVIDE-BY-ZERO,CHECK-FIXED-POINT %s
+// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=fixed-point %s -o - | FileCheck --check-prefixes=CHECK-UBSAN,CHECK-FIXED-POINT %s
// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=float-cast-overflow %s -o - | FileCheck --check-prefixes=CHECK-UBSAN,CHECK-FLOAT-CAST-OVERFLOW %s
// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=integer-divide-by-zero %s -o - | FileCheck --check-prefixes=CHECK-UBSAN,CHECK-INTEGER-DIVIDE-BY-ZERO %s
// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=nonnull-attribute %s -o - | FileCheck --check-prefixes=CHECK-UBSAN,CHECK-NONNULL-ATTRIBUTE %s
@@ -65,6 +67,18 @@ int EnumSanitizerEnabled();
int EnumSanitizerDisabled();
#endif
+#if __has_feature(fixed_point_divide_by_zero_sanitizer)
+int FixedPointDivideByZeroSanitizerEnabled();
+#else
+int FixedPointDivideByZeroSanitizerDisabled();
+#endif
+
+#if __has_feature(fixed_point_sanitizer)
+int FixedPointSanitizerEnabled();
+#else
+int FixedPointSanitizerDisabled();
+#endif
+
#if __has_feature(float_cast_overflow_sanitizer)
int FloatCastOverflowSanitizerEnabled();
#else
@@ -161,6 +175,8 @@ int FunctionSanitizerDisabled();
// CHECK-BUILTIN: BuiltinSanitizerEnabled
// CHECK-ARRAY-BOUNDS: ArrayBoundsSanitizerEnabled
// CHECK-ENUM: EnumSanitizerEnabled
+// CHECK-FIXED-POINT-DIVIDE-BY-ZERO: FixedPointDivideByZeroSanitizerEnabled
+// CHECK-FIXED-POINT: FixedPointSanitizerEnabled
// CHECK-FLOAT-CAST-OVERFLOW: FloatCastOverflowSanitizerEnabled
// CHECK-INTEGER-DIVIDE-BY-ZERO: IntegerDivideByZeroSanitizerEnabled
// CHECK-NONNULL-ATTRIBUTE: NonnullAttributeSanitizerEnabled
diff --git a/compiler-rt/lib/ubsan/ubsan_checks.inc b/compiler-rt/lib/ubsan/ubsan_checks.inc
index b1d09a9024e7e..9fe0291cab9bc 100644
--- a/compiler-rt/lib/ubsan/ubsan_checks.inc
+++ b/compiler-rt/lib/ubsan/ubsan_checks.inc
@@ -36,6 +36,8 @@ UBSAN_CHECK(UnsignedIntegerOverflow, "unsigned-integer-overflow",
UBSAN_CHECK(IntegerDivideByZero, "integer-divide-by-zero",
"integer-divide-by-zero")
UBSAN_CHECK(FloatDivideByZero, "float-divide-by-zero", "float-divide-by-zero")
+UBSAN_CHECK(FixedPointDivideByZero, "fixed-point-divide-by-zero",
+ "fixed-point-divide-by-zero")
UBSAN_CHECK(InvalidBuiltin, "invalid-builtin-use", "invalid-builtin-use")
UBSAN_CHECK(InvalidObjCCast, "invalid-objc-cast", "invalid-objc-cast")
UBSAN_CHECK(ImplicitUnsignedIntegerTruncation,
diff --git a/compiler-rt/lib/ubsan/ubsan_handlers.cpp b/compiler-rt/lib/ubsan/ubsan_handlers.cpp
index 63319f46734a4..c266298c86295 100644
--- a/compiler-rt/lib/ubsan/ubsan_handlers.cpp
+++ b/compiler-rt/lib/ubsan/ubsan_handlers.cpp
@@ -294,6 +294,8 @@ static void handleDivremOverflowImpl(OverflowData *Data, ValueHandle LHS,
ET = ErrorType::SignedIntegerOverflow;
else if (Data->Type.isIntegerTy())
ET = ErrorType::IntegerDivideByZero;
+ else if (Data->Type.isFixedPointTy())
+ ET = ErrorType::FixedPointDivideByZero;
else
ET = ErrorType::FloatDivideByZero;
diff --git a/compiler-rt/lib/ubsan/ubsan_value.h b/compiler-rt/lib/ubsan/ubsan_value.h
index ee523cf5ddda5..812cde9558076 100644
--- a/compiler-rt/lib/ubsan/ubsan_value.h
+++ b/compiler-rt/lib/ubsan/ubsan_value.h
@@ -110,6 +110,8 @@ class TypeDescriptor {
/// of the type for the signed _BitInt(N) type stored after zero bit after
/// TypeName as 32-bit unsigned integer.
TK_BitInt = 0x0002,
+ /// A fixed-point type. The value representation is currently empty.
+ TK_FixedPoint = 0x0003,
/// Any other type. The value representation is unspecified.
TK_Unknown = 0xffff
};
@@ -164,6 +166,8 @@ class TypeDescriptor {
CHECK(isFloatTy());
return TypeInfo;
}
+
+ bool isFixedPointTy() const { return getKind() == TK_FixedPoint; }
};
/// \brief An opaque handle to a value.
diff --git a/compiler-rt/test/ubsan/TestCases/FixedPoint/divide-by-zero.cpp b/compiler-rt/test/ubsan/TestCases/FixedPoint/divide-by-zero.cpp
new file mode 100644
index 0000000000000..a7631491e37fa
--- /dev/null
+++ b/compiler-rt/test/ubsan/TestCases/FixedPoint/divide-by-zero.cpp
@@ -0,0 +1,10 @@
+// RUN: %clangxx -DOP="0.0R / 0" -ffixed-point -fsanitize=fixed-point-divide-by-zero %s -o %t1 && %run %t1 2>&1 | FileCheck %s
+// RUN: %clangxx -DOP="0.5R / 0" -ffixed-point -fsanitize=fixed-point-divide-by-zero %s -o %t2 && %run %t2 2>&1 | FileCheck %s
+// RUN: %clangxx -DOP="0.0R / 0.0R" -ffixed-point -fsanitize=fixed-point-divide-by-zero %s -o %t3 && %run %t3 2>&1 | FileCheck %s
+// RUN: %clangxx -DOP="0.5R / 0.0R" -ffixed-point -fsanitize=fixed-point-divide-by-zero %s -o %t4 && %run %t4 2>&1 | FileCheck %s
+// RUN: %clangxx -fsanitize=undefined -DOP="_Fract a = 0.5R; a /= 0" -ffixed-point -fsanitize=fixed-point-divide-by-zero %s -o %t5 && %run %t5 2>&1 | FileCheck %s
+
+int main() {
+ // CHECK: divide-by-zero.cpp:[[@LINE+1]]:3: runtime error: division by zero
+ OP;
+}
More information about the llvm-commits
mailing list