Skip to content

Commit 92c9fcc

Browse files
committed
Add support for builtin_verbose_trap
The builtin causes the program to stop its execution abnormally and shows a human-readable description of the reason for the termination when a debugger is attached or in a symbolicated crash log. The motivation for the builtin is explained in the following RFC: https://discourse.llvm.org/t/rfc-adding-builtin-verbose-trap-string-literal/75845
1 parent 9c4e7a1 commit 92c9fcc

12 files changed

+246
-3
lines changed

clang/docs/LanguageExtensions.rst

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3379,6 +3379,54 @@ Query for this feature with ``__has_builtin(__builtin_debugtrap)``.
33793379
33803380
Query for this feature with ``__has_builtin(__builtin_trap)``.
33813381
3382+
``__builtin_verbose_trap``
3383+
------------------
3384+
3385+
``__builtin_verbose_trap`` causes the program to stop its execution abnormally
3386+
and shows a human-readable description of the reason for the termination when a
3387+
debugger is attached or in a symbolicated crash log.
3388+
3389+
**Syntax**:
3390+
3391+
.. code-block:: c++
3392+
3393+
__builtin_verbose_trap(const char *reason)
3394+
3395+
**Description**
3396+
3397+
``__builtin_verbose_trap`` is lowered to the ` ``llvm.trap`` <https://llvm.org/docs/LangRef.html#llvm-trap-intrinsic>`_ builtin.
3398+
Additionally, clang emits debug metadata that represents an artificial inline
3399+
frame whose name encodes the string passed to the builtin, prefixed by a "magic"
3400+
prefix.
3401+
3402+
For example, consider the following code:
3403+
3404+
.. code-block:: c++
3405+
3406+
void foo(int* p) {
3407+
if (p == nullptr)
3408+
__builtin_verbose_trap("Argument_must_not_be_null");
3409+
}
3410+
3411+
The debug metadata would look as if it were produced for the following code:
3412+
3413+
.. code-block:: c++
3414+
3415+
__attribute__((always_inline))
3416+
inline void "__llvm_verbose_trap: Argument_must_not_be_null"() {
3417+
__builtin_trap();
3418+
}
3419+
3420+
void foo(int* p) {
3421+
if (p == nullptr)
3422+
"__llvm_verbose_trap: Argument_must_not_be_null"();
3423+
}
3424+
3425+
However, the LLVM IR would not actually contain a call to the artificial
3426+
function — it only exists in the debug metadata.
3427+
3428+
Query for this feature with ``__has_builtin(__builtin_verbose_trap)``.
3429+
33823430
``__builtin_nondeterministic_value``
33833431
------------------------------------
33843432

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,8 @@ Non-comprehensive list of changes in this release
268268
and vector types as return value ``19``, to match GCC 14's behavior.
269269
* The default value of `_MSC_VER` was raised from 1920 to 1933.
270270
* Since MSVC 19.33 added undocumented attribute ``[[msvc::constexpr]]``, this release adds the attribute as well.
271+
* Support for ``__builtin_verbose_trap`` has been added. See
272+
https://clang.llvm.org/docs/LanguageExtensions.html#builtin-functions.
271273

272274
* Added ``#pragma clang fp reciprocal``.
273275

clang/include/clang/AST/Expr.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,11 @@ class Expr : public ValueStmt {
774774
const Expr *PtrExpression, ASTContext &Ctx,
775775
EvalResult &Status) const;
776776

777+
/// If the current Expr can be evaluated to a pointer to a null-terminated
778+
/// constant string, return the constant string (without the terminating null)
779+
/// in Result. Return true if it succeeds.
780+
bool tryEvaluateString(std::string &Result, ASTContext &Ctx) const;
781+
777782
/// Enumeration used to describe the kind of Null pointer constant
778783
/// returned from \c isNullPointerConstant().
779784
enum NullPointerConstantKind {

clang/include/clang/Basic/Builtins.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,7 @@ BUILTIN(__builtin_expect_with_probability, "LiLiLid", "ncE")
673673
BUILTIN(__builtin_prefetch, "vvC*.", "nc")
674674
BUILTIN(__builtin_readcyclecounter, "ULLi", "n")
675675
BUILTIN(__builtin_trap, "v", "nr")
676+
BUILTIN(__builtin_verbose_trap, "vcC*", "nr")
676677
BUILTIN(__builtin_debugtrap, "v", "n")
677678
BUILTIN(__builtin_unreachable, "v", "nr")
678679
BUILTIN(__builtin_shufflevector, "v." , "nct")

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8764,6 +8764,8 @@ def err_expected_callable_argument : Error<
87648764
"expected a callable expression as %ordinal0 argument to %1, found %2">;
87658765
def note_building_builtin_dump_struct_call : Note<
87668766
"in call to printing function with arguments '(%0)' while dumping struct">;
8767+
def err_builtin_verbose_trap_arg : Error<
8768+
"argument to __builtin_verbose_trap must be a non-empty string literal">;
87678769

87688770
def err_atomic_load_store_uses_lib : Error<
87698771
"atomic %select{load|store}0 requires runtime support that is not "

clang/lib/AST/ExprConstant.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1880,7 +1880,8 @@ static bool EvaluateAtomic(const Expr *E, const LValue *This, APValue &Result,
18801880
EvalInfo &Info);
18811881
static bool EvaluateAsRValue(EvalInfo &Info, const Expr *E, APValue &Result);
18821882
static bool EvaluateBuiltinStrLen(const Expr *E, uint64_t &Result,
1883-
EvalInfo &Info);
1883+
EvalInfo &Info,
1884+
std::string *StringResult = nullptr);
18841885

18851886
/// Evaluate an integer or fixed point expression into an APResult.
18861887
static bool EvaluateFixedPointOrInteger(const Expr *E, APFixedPoint &Result,
@@ -16612,7 +16613,7 @@ bool Expr::tryEvaluateObjectSize(uint64_t &Result, ASTContext &Ctx,
1661216613
}
1661316614

1661416615
static bool EvaluateBuiltinStrLen(const Expr *E, uint64_t &Result,
16615-
EvalInfo &Info) {
16616+
EvalInfo &Info, std::string *StringResult) {
1661616617
if (!E->getType()->hasPointerRepresentation() || !E->isPRValue())
1661716618
return false;
1661816619

@@ -16639,6 +16640,8 @@ static bool EvaluateBuiltinStrLen(const Expr *E, uint64_t &Result,
1663916640
Str = Str.substr(0, Pos);
1664016641

1664116642
Result = Str.size();
16643+
if (StringResult)
16644+
*StringResult = Str;
1664216645
return true;
1664316646
}
1664416647

@@ -16654,12 +16657,21 @@ static bool EvaluateBuiltinStrLen(const Expr *E, uint64_t &Result,
1665416657
if (!Char.getInt()) {
1665516658
Result = Strlen;
1665616659
return true;
16657-
}
16660+
} else if (StringResult)
16661+
StringResult->push_back(Char.getInt().getExtValue());
1665816662
if (!HandleLValueArrayAdjustment(Info, E, String, CharTy, 1))
1665916663
return false;
1666016664
}
1666116665
}
1666216666

16667+
bool Expr::tryEvaluateString(std::string &StringResult, ASTContext &Ctx) const {
16668+
Expr::EvalStatus Status;
16669+
EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantFold);
16670+
uint64_t Result;
16671+
16672+
return EvaluateBuiltinStrLen(this, Result, Info, &StringResult);
16673+
}
16674+
1666316675
bool Expr::EvaluateCharRangeAsString(std::string &Result,
1666416676
const Expr *SizeExpression,
1666516677
const Expr *PtrExpression, ASTContext &Ctx,

clang/lib/CodeGen/CGBuiltin.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3212,6 +3212,18 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
32123212
case Builtin::BI__builtin_trap:
32133213
EmitTrapCall(Intrinsic::trap);
32143214
return RValue::get(nullptr);
3215+
case Builtin::BI__builtin_verbose_trap: {
3216+
llvm::DILocation *TrapLocation = Builder.getCurrentDebugLocation();
3217+
if (getDebugInfo()) {
3218+
std::string Str;
3219+
E->getArg(0)->tryEvaluateString(Str, getContext());
3220+
TrapLocation = getDebugInfo()->CreateTrapFailureMessageFor(
3221+
TrapLocation, "__llvm_verbose_trap", Str);
3222+
}
3223+
ApplyDebugLocation ApplyTrapDI(*this, TrapLocation);
3224+
EmitTrapCall(Intrinsic::trap);
3225+
return RValue::get(nullptr);
3226+
}
32153227
case Builtin::BI__debugbreak:
32163228
EmitTrapCall(Intrinsic::debugtrap);
32173229
return RValue::get(nullptr);

clang/lib/CodeGen/CGDebugInfo.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1628,6 +1628,27 @@ llvm::DIType *CGDebugInfo::createFieldType(
16281628
offsetInBits, flags, debugType, Annotations);
16291629
}
16301630

1631+
llvm::DISubprogram *
1632+
CGDebugInfo::getFakeFuncSubprogram(const std::string &FakeFuncName) {
1633+
llvm::DISubprogram *&SP = FakeFuncMap[FakeFuncName];
1634+
1635+
if (!SP) {
1636+
auto FileScope = TheCU->getFile();
1637+
llvm::DISubroutineType *DIFnTy = DBuilder.createSubroutineType(nullptr);
1638+
// Note: We use `FileScope` rather than `TheCU` as the scope because that's
1639+
// what LLVM's inliner seems to do.
1640+
SP = DBuilder.createFunction(
1641+
/*Scope=*/FileScope, /*Name=*/FakeFuncName, /*LinkageName=*/StringRef(),
1642+
/*File=*/FileScope, /*LineNo=*/0, /*Ty=*/DIFnTy,
1643+
/*ScopeLine=*/0,
1644+
/*Flags=*/llvm::DINode::FlagArtificial,
1645+
/*SPFlags=*/llvm::DISubprogram::SPFlagDefinition,
1646+
/*TParams=*/nullptr, /*ThrownTypes=*/nullptr, /*Annotations=*/nullptr);
1647+
}
1648+
1649+
return SP;
1650+
}
1651+
16311652
void CGDebugInfo::CollectRecordLambdaFields(
16321653
const CXXRecordDecl *CXXDecl, SmallVectorImpl<llvm::Metadata *> &elements,
16331654
llvm::DIType *RecordTy) {
@@ -3416,6 +3437,27 @@ llvm::DIMacroFile *CGDebugInfo::CreateTempMacroFile(llvm::DIMacroFile *Parent,
34163437
return DBuilder.createTempMacroFile(Parent, Line, FName);
34173438
}
34183439

3440+
llvm::DILocation *CGDebugInfo::CreateTrapFailureMessageFor(
3441+
llvm::DebugLoc TrapLocation, StringRef Prefix, StringRef FailureMsg) {
3442+
// Create debug info that describes a fake function whose name is the failure
3443+
// message.
3444+
std::string FuncName(Prefix);
3445+
if (!FailureMsg.empty()) {
3446+
// A space in the function name identifies this as not being a real function
3447+
// because it's not a valid symbol name.
3448+
FuncName += ": ";
3449+
FuncName += FailureMsg;
3450+
}
3451+
3452+
assert(FuncName.size() > 0);
3453+
assert(FuncName.find(' ') != std::string::npos &&
3454+
"Fake function name must contain a space");
3455+
3456+
llvm::DISubprogram *TrapSP = getFakeFuncSubprogram(FuncName);
3457+
return llvm::DILocation::get(CGM.getLLVMContext(), /*Line=*/0, /*Column=*/0,
3458+
/*Scope=*/TrapSP, /*InlinedAt=*/TrapLocation);
3459+
}
3460+
34193461
static QualType UnwrapTypeForDebugInfo(QualType T, const ASTContext &C) {
34203462
Qualifiers Quals;
34213463
do {

clang/lib/CodeGen/CGDebugInfo.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,14 @@ class CGDebugInfo {
346346
const FieldDecl *BitFieldDecl, const llvm::DIDerivedType *BitFieldDI,
347347
llvm::ArrayRef<llvm::Metadata *> PreviousFieldsDI, const RecordDecl *RD);
348348

349+
// A cache that maps fake function names used for __builtin_verbose_trap to
350+
// subprograms.
351+
std::map<std::string, llvm::DISubprogram *> FakeFuncMap;
352+
353+
// A function that returns the subprogram corresponding to the fake function
354+
// name.
355+
llvm::DISubprogram *getFakeFuncSubprogram(const std::string &FakeFuncName);
356+
349357
/// Helpers for collecting fields of a record.
350358
/// @{
351359
void CollectRecordLambdaFields(const CXXRecordDecl *CXXDecl,
@@ -602,6 +610,18 @@ class CGDebugInfo {
602610
return CoroutineParameterMappings;
603611
}
604612

613+
// Create a debug location from `TrapLocation` that adds a fake
614+
// inline frame where the frame name is
615+
//
616+
// * `<Prefix>: <FailureMsg>` if `<FailureMsg>` is not empty.
617+
// * `<Prefix>` if `<FailureMsg>` is empty. Note `<Prefix>` must
618+
// contain a space.
619+
//
620+
// This is used to store failure reasons for traps.
621+
llvm::DILocation *CreateTrapFailureMessageFor(llvm::DebugLoc TrapLocation,
622+
StringRef Prefix,
623+
StringRef FailureMsg);
624+
605625
private:
606626
/// Emit call to llvm.dbg.declare for a variable declaration.
607627
/// Returns a pointer to the DILocalVariable associated with the

clang/lib/Sema/SemaChecking.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,23 @@ static bool checkArgCount(Sema &S, CallExpr *Call, unsigned DesiredArgCount) {
171171
<< /*is non object*/ 0 << Call->getArg(1)->getSourceRange();
172172
}
173173

174+
static bool checkBuiltinVerboseTrap(CallExpr *Call, Sema &S) {
175+
Expr *Arg = Call->getArg(0);
176+
177+
if (Arg->isValueDependent())
178+
return true;
179+
180+
// FIXME: Add more checks and reject strings that can't be handled by
181+
// debuggers.
182+
std::string Result;
183+
if (!Arg->tryEvaluateString(Result, S.Context) || Result.empty()) {
184+
S.Diag(Arg->getBeginLoc(), diag::err_builtin_verbose_trap_arg)
185+
<< Arg->getSourceRange();
186+
return false;
187+
}
188+
return true;
189+
}
190+
174191
static bool convertArgumentToType(Sema &S, Expr *&Value, QualType Ty) {
175192
if (Value->isTypeDependent())
176193
return false;
@@ -2881,6 +2898,11 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
28812898
case Builtin::BI__builtin_matrix_column_major_store:
28822899
return SemaBuiltinMatrixColumnMajorStore(TheCall, TheCallResult);
28832900

2901+
case Builtin::BI__builtin_verbose_trap:
2902+
if (!checkBuiltinVerboseTrap(TheCall, *this))
2903+
return ExprError();
2904+
break;
2905+
28842906
case Builtin::BI__builtin_get_device_side_mangled_name: {
28852907
auto Check = [](CallExpr *TheCall) {
28862908
if (TheCall->getNumArgs() != 1)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// RUN: %clang_cc1 -std=c++20 -emit-llvm -debug-info-kind=limited %s -o - | FileCheck %s
2+
3+
// CHECK-LABEL: define void @_Z2f0v()
4+
// CHECK: call void @llvm.trap(), !dbg ![[LOC17:.*]]
5+
6+
// CHECK-LABEL: define void @_Z2f1v()
7+
// CHECK: call void @llvm.trap(), !dbg ![[LOC23:.*]]
8+
// CHECK: call void @llvm.trap(), !dbg ![[LOC25:.*]]
9+
10+
// CHECK-LABEL: define void @_Z2f3v()
11+
// CHECK: call void @_Z2f2IXadsoKcL_ZL8constMsgEEEEvv()
12+
13+
// CHECK-LABEL: define internal void @_Z2f2IXadsoKcL_ZL8constMsgEEEEvv()
14+
// CHECK: call void @llvm.trap(), !dbg ![[LOC36:.*]]
15+
16+
// CHECK: ![[FILESCOPE:.*]] = !DIFile(filename:
17+
// CHECK: ![[SUBPROG14:.*]] = distinct !DISubprogram(name: "f0", linkageName: "_Z2f0v",
18+
// CHECK: ![[LOC17]] = !DILocation(line: 0, scope: ![[SUBPROG18:.*]], inlinedAt: ![[LOC20:.*]])
19+
// CHECK: ![[SUBPROG18]] = distinct !DISubprogram(name: "__llvm_verbose_trap: Argument_must_not_be_null", scope: ![[FILESCOPE]], file: ![[FILESCOPE]], type: !{{.*}}, flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: !{{.*}})
20+
// CHECK: ![[LOC20]] = !DILocation(line: 34, column: 3, scope: ![[SUBPROG14]])
21+
// CHECK: ![[SUBPROG22:.*]] = distinct !DISubprogram(name: "f1", linkageName: "_Z2f1v",
22+
// CHECK: ![[LOC23]] = !DILocation(line: 0, scope: ![[SUBPROG18]], inlinedAt: ![[LOC24:.*]])
23+
// CHECK: ![[LOC24]] = !DILocation(line: 38, column: 3, scope: ![[SUBPROG22]])
24+
// CHECK: ![[LOC25]] = !DILocation(line: 0, scope: ![[SUBPROG26:.*]], inlinedAt: ![[LOC27:.*]])
25+
// CHECK: ![[SUBPROG26]] = distinct !DISubprogram(name: "__llvm_verbose_trap: hello", scope: ![[FILESCOPE]], file: ![[FILESCOPE]], type: !{{.*}}, flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: !{{.*}})
26+
// CHECK: ![[LOC27]] = !DILocation(line: 39, column: 3, scope: ![[SUBPROG22]])
27+
// CHECK: ![[SUBPROG32:.*]] = distinct !DISubprogram(name: "f2<constMsg>", linkageName: "_Z2f2IXadsoKcL_ZL8constMsgEEEEvv",
28+
// CHECK: ![[LOC36]] = !DILocation(line: 0, scope: ![[SUBPROG26]], inlinedAt: ![[LOC37:.*]])
29+
// CHECK: ![[LOC37]] = !DILocation(line: 44, column: 3, scope: ![[SUBPROG32]])
30+
31+
char const constMsg[] = "hello";
32+
33+
void f0() {
34+
__builtin_verbose_trap("Argument_must_not_be_null");
35+
}
36+
37+
void f1() {
38+
__builtin_verbose_trap("Argument_must_not_be_null");
39+
__builtin_verbose_trap("hello");
40+
}
41+
42+
template <const char * const str>
43+
void f2() {
44+
__builtin_verbose_trap(str);
45+
}
46+
47+
void f3() {
48+
f2<constMsg>();
49+
}

clang/test/SemaCXX/verbose-trap.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// RUN: %clang_cc1 -std=c++11 -fsyntax-only -fcxx-exceptions -verify %s
2+
3+
#if !__has_builtin(__builtin_verbose_trap)
4+
#error
5+
#endif
6+
7+
constexpr char const* constMsg1 = "hello";
8+
char const* const constMsg2 = "hello";
9+
char const constMsg3[] = "hello";
10+
11+
template <const char * const str>
12+
void f(const char * arg) {
13+
__builtin_verbose_trap("Argument_must_not_be_null");
14+
__builtin_verbose_trap("hello" "world");
15+
__builtin_verbose_trap(constMsg1);
16+
__builtin_verbose_trap(constMsg2);
17+
__builtin_verbose_trap(""); // expected-error {{argument to __builtin_verbose_trap must be a non-empty string literal}}
18+
__builtin_verbose_trap(); // expected-error {{too few arguments}}
19+
__builtin_verbose_trap(0); // expected-error {{argument to __builtin_verbose_trap must be a non-empty string literal}}
20+
__builtin_verbose_trap(1); // expected-error {{cannot initialize a parameter of type 'const char *' with}}
21+
__builtin_verbose_trap(arg); // expected-error {{argument to __builtin_verbose_trap must be a non-empty string literal}}
22+
__builtin_verbose_trap(str);
23+
__builtin_verbose_trap(u8"hello");
24+
}
25+
26+
void test() {
27+
f<constMsg3>(nullptr);
28+
}

0 commit comments

Comments
 (0)