diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index 84fc4dee02fa8..7bde31114bbd9 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -3467,6 +3467,60 @@ Query for this feature with ``__has_builtin(__builtin_trap)``. ``__builtin_arm_trap`` is lowered to the ``llvm.aarch64.break`` builtin, and then to ``brk #payload``. +``__builtin_verbose_trap`` +-------------------------- + +``__builtin_verbose_trap`` 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. + +**Syntax**: + +.. code-block:: c++ + + __builtin_verbose_trap(const char *category, const char *reason) + +**Description** + +``__builtin_verbose_trap`` is lowered to the ` ``llvm.trap`` `_ builtin. +Additionally, clang emits debugging information that represents an artificial +inline frame whose name encodes the category and reason strings passed to the builtin, +prefixed by a "magic" prefix. + +For example, consider the following code: + +.. code-block:: c++ + + void foo(int* p) { + if (p == nullptr) + __builtin_verbose_trap("check null", "Argument must not be null!"); + } + +The debugging information would look as if it were produced for the following code: + +.. code-block:: c++ + + __attribute__((always_inline)) + inline void "__trap_msg$check null$Argument must not be null!"() { + __builtin_trap(); + } + + void foo(int* p) { + if (p == nullptr) + "__trap_msg$check null$Argument must not be null!"(); + } + +However, the generated code would not actually contain a call to the artificial +function — it only exists in the debugging information. + +Query for this feature with ``__has_builtin(__builtin_verbose_trap)``. Note that +users need to enable debug information to enable this feature. A call to this +builtin is equivalent to a call to ``__builtin_trap`` if debug information isn't +enabled. + +The optimizer can merge calls to trap with different messages, which degrades +the debugging experience. + ``__builtin_allow_runtime_check`` --------------------------------- diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index 2bfefeabc348b..c7af59da24195 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -787,6 +787,11 @@ class Expr : public ValueStmt { const Expr *PtrExpression, ASTContext &Ctx, EvalResult &Status) const; + /// If the current Expr can be evaluated to a pointer to a null-terminated + /// constant string, return the constant string (without the terminating + /// null). + std::optional tryEvaluateString(ASTContext &Ctx) const; + /// Enumeration used to describe the kind of Null pointer constant /// returned from \c isNullPointerConstant(). enum NullPointerConstantKind { diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index de721a87b3341..1c4453d3c84e9 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -1152,6 +1152,12 @@ def Trap : Builtin { let Prototype = "void()"; } +def VerboseTrap : Builtin { + let Spellings = ["__builtin_verbose_trap"]; + let Attributes = [NoThrow, NoReturn]; + let Prototype = "void(char const*, char const*)"; +} + def Debugtrap : Builtin { let Spellings = ["__builtin_debugtrap"]; let Attributes = [NoThrow]; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 63e951daec747..46f054617f430 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -8875,6 +8875,8 @@ def err_expected_callable_argument : Error< "expected a callable expression as %ordinal0 argument to %1, found %2">; def note_building_builtin_dump_struct_call : Note< "in call to printing function with arguments '(%0)' while dumping struct">; +def err_builtin_verbose_trap_arg : Error< + "argument to __builtin_verbose_trap must be a pointer to a constant string">; def err_atomic_load_store_uses_lib : Error< "atomic %select{load|store}0 requires runtime support that is not " diff --git a/clang/include/clang/CodeGen/ModuleBuilder.h b/clang/include/clang/CodeGen/ModuleBuilder.h index edacd82bf899d..4a6200ad25dea 100644 --- a/clang/include/clang/CodeGen/ModuleBuilder.h +++ b/clang/include/clang/CodeGen/ModuleBuilder.h @@ -27,6 +27,9 @@ namespace llvm { } } +// Prefix of the name of the artificial inline frame. +#define CLANG_TRAP_PREFIX "__trap_msg" + namespace clang { class CodeGenOptions; class CoverageSourceInfo; diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index de3c2a63913e9..29ac550c21266 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -1884,7 +1884,8 @@ static bool EvaluateAtomic(const Expr *E, const LValue *This, APValue &Result, EvalInfo &Info); static bool EvaluateAsRValue(EvalInfo &Info, const Expr *E, APValue &Result); static bool EvaluateBuiltinStrLen(const Expr *E, uint64_t &Result, - EvalInfo &Info); + EvalInfo &Info, + std::string *StringResult = nullptr); /// Evaluate an integer or fixed point expression into an APResult. static bool EvaluateFixedPointOrInteger(const Expr *E, APFixedPoint &Result, @@ -16792,7 +16793,7 @@ bool Expr::tryEvaluateObjectSize(uint64_t &Result, ASTContext &Ctx, } static bool EvaluateBuiltinStrLen(const Expr *E, uint64_t &Result, - EvalInfo &Info) { + EvalInfo &Info, std::string *StringResult) { if (!E->getType()->hasPointerRepresentation() || !E->isPRValue()) return false; @@ -16819,6 +16820,8 @@ static bool EvaluateBuiltinStrLen(const Expr *E, uint64_t &Result, Str = Str.substr(0, Pos); Result = Str.size(); + if (StringResult) + *StringResult = Str; return true; } @@ -16834,12 +16837,24 @@ static bool EvaluateBuiltinStrLen(const Expr *E, uint64_t &Result, if (!Char.getInt()) { Result = Strlen; return true; - } + } else if (StringResult) + StringResult->push_back(Char.getInt().getExtValue()); if (!HandleLValueArrayAdjustment(Info, E, String, CharTy, 1)) return false; } } +std::optional Expr::tryEvaluateString(ASTContext &Ctx) const { + Expr::EvalStatus Status; + EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantFold); + uint64_t Result; + std::string StringResult; + + if (EvaluateBuiltinStrLen(this, Result, Info, &StringResult)) + return StringResult; + return {}; +} + bool Expr::EvaluateCharRangeAsString(std::string &Result, const Expr *SizeExpression, const Expr *PtrExpression, ASTContext &Ctx, diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index 7e5f2edfc732c..5c762c641235c 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -3586,6 +3586,18 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, case Builtin::BI__builtin_trap: EmitTrapCall(Intrinsic::trap); return RValue::get(nullptr); + case Builtin::BI__builtin_verbose_trap: { + llvm::DILocation *TrapLocation = Builder.getCurrentDebugLocation(); + if (getDebugInfo()) { + TrapLocation = getDebugInfo()->CreateTrapFailureMessageFor( + TrapLocation, *E->getArg(0)->tryEvaluateString(getContext()), + *E->getArg(1)->tryEvaluateString(getContext())); + } + ApplyDebugLocation ApplyTrapDI(*this, TrapLocation); + // Currently no attempt is made to prevent traps from being merged. + EmitTrapCall(Intrinsic::trap); + return RValue::get(nullptr); + } case Builtin::BI__debugbreak: EmitTrapCall(Intrinsic::debugtrap); return RValue::get(nullptr); diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp index 539ded5cca5e1..73747a70c445f 100644 --- a/clang/lib/CodeGen/CGDebugInfo.cpp +++ b/clang/lib/CodeGen/CGDebugInfo.cpp @@ -32,6 +32,7 @@ #include "clang/Basic/FileManager.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/Version.h" +#include "clang/CodeGen/ModuleBuilder.h" #include "clang/Frontend/FrontendOptions.h" #include "clang/Lex/HeaderSearchOptions.h" #include "clang/Lex/ModuleMap.h" @@ -1692,6 +1693,28 @@ llvm::DIType *CGDebugInfo::createFieldType( offsetInBits, flags, debugType, Annotations); } +llvm::DISubprogram * +CGDebugInfo::createInlinedTrapSubprogram(StringRef FuncName, + llvm::DIFile *FileScope) { + // We are caching the subprogram because we don't want to duplicate + // subprograms with the same message. Note that `SPFlagDefinition` prevents + // subprograms from being uniqued. + llvm::DISubprogram *&SP = InlinedTrapFuncMap[FuncName]; + + if (!SP) { + llvm::DISubroutineType *DIFnTy = DBuilder.createSubroutineType(nullptr); + SP = DBuilder.createFunction( + /*Scope=*/FileScope, /*Name=*/FuncName, /*LinkageName=*/StringRef(), + /*File=*/FileScope, /*LineNo=*/0, /*Ty=*/DIFnTy, + /*ScopeLine=*/0, + /*Flags=*/llvm::DINode::FlagArtificial, + /*SPFlags=*/llvm::DISubprogram::SPFlagDefinition, + /*TParams=*/nullptr, /*ThrownTypes=*/nullptr, /*Annotations=*/nullptr); + } + + return SP; +} + void CGDebugInfo::CollectRecordLambdaFields( const CXXRecordDecl *CXXDecl, SmallVectorImpl &elements, llvm::DIType *RecordTy) { @@ -3488,6 +3511,24 @@ llvm::DIMacroFile *CGDebugInfo::CreateTempMacroFile(llvm::DIMacroFile *Parent, return DBuilder.createTempMacroFile(Parent, Line, FName); } +llvm::DILocation *CGDebugInfo::CreateTrapFailureMessageFor( + llvm::DebugLoc TrapLocation, StringRef Category, StringRef FailureMsg) { + // Create a debug location from `TrapLocation` that adds an artificial inline + // frame. + const char *Prefix = CLANG_TRAP_PREFIX; + SmallString<64> FuncName(Prefix); + + FuncName += "$"; + FuncName += Category; + FuncName += "$"; + FuncName += FailureMsg; + + llvm::DISubprogram *TrapSP = + createInlinedTrapSubprogram(FuncName, TrapLocation->getFile()); + return llvm::DILocation::get(CGM.getLLVMContext(), /*Line=*/0, /*Column=*/0, + /*Scope=*/TrapSP, /*InlinedAt=*/TrapLocation); +} + static QualType UnwrapTypeForDebugInfo(QualType T, const ASTContext &C) { Qualifiers Quals; do { diff --git a/clang/lib/CodeGen/CGDebugInfo.h b/clang/lib/CodeGen/CGDebugInfo.h index d6db4d711366a..5b48155ef26aa 100644 --- a/clang/lib/CodeGen/CGDebugInfo.h +++ b/clang/lib/CodeGen/CGDebugInfo.h @@ -29,7 +29,9 @@ #include "llvm/IR/DebugInfo.h" #include "llvm/IR/ValueHandle.h" #include "llvm/Support/Allocator.h" +#include #include +#include namespace llvm { class MDNode; @@ -346,6 +348,14 @@ class CGDebugInfo { const FieldDecl *BitFieldDecl, const llvm::DIDerivedType *BitFieldDI, llvm::ArrayRef PreviousFieldsDI, const RecordDecl *RD); + /// A cache that maps names of artificial inlined functions to subprograms. + llvm::StringMap InlinedTrapFuncMap; + + /// A function that returns the subprogram corresponding to the artificial + /// inlined function for traps. + llvm::DISubprogram *createInlinedTrapSubprogram(StringRef FuncName, + llvm::DIFile *FileScope); + /// Helpers for collecting fields of a record. /// @{ void CollectRecordLambdaFields(const CXXRecordDecl *CXXDecl, @@ -602,6 +612,18 @@ class CGDebugInfo { return CoroutineParameterMappings; } + /// Create a debug location from `TrapLocation` that adds an artificial inline + /// frame where the frame name is + /// + /// * `::` + /// + /// `` is "__trap_msg". + /// + /// This is used to store failure reasons for traps. + llvm::DILocation *CreateTrapFailureMessageFor(llvm::DebugLoc TrapLocation, + StringRef Category, + StringRef FailureMsg); + private: /// Emit call to llvm.dbg.declare for a variable declaration. /// Returns a pointer to the DILocalVariable associated with the diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 51757f4cf727d..5d5b1d13e1ec9 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -172,6 +172,27 @@ static bool checkArgCount(Sema &S, CallExpr *Call, unsigned DesiredArgCount) { << /*is non object*/ 0 << Call->getArg(1)->getSourceRange(); } +static bool checkBuiltinVerboseTrap(CallExpr *Call, Sema &S) { + bool HasError = false; + + for (int I = 0; I < Call->getNumArgs(); ++I) { + Expr *Arg = Call->getArg(I); + + if (Arg->isValueDependent()) + continue; + + // FIXME: Add more checks and reject strings that can't be handled by + // debuggers. + if (!Arg->tryEvaluateString(S.Context).has_value()) { + S.Diag(Arg->getBeginLoc(), diag::err_builtin_verbose_trap_arg) + << Arg->getSourceRange(); + HasError = true; + } + } + + return !HasError; +} + static bool convertArgumentToType(Sema &S, Expr *&Value, QualType Ty) { if (Value->isTypeDependent()) return false; @@ -3204,6 +3225,11 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, case Builtin::BI__builtin_matrix_column_major_store: return BuiltinMatrixColumnMajorStore(TheCall, TheCallResult); + case Builtin::BI__builtin_verbose_trap: + if (!checkBuiltinVerboseTrap(TheCall, *this)) + return ExprError(); + break; + case Builtin::BI__builtin_get_device_side_mangled_name: { auto Check = [](CallExpr *TheCall) { if (TheCall->getNumArgs() != 1) diff --git a/clang/test/CodeGenCXX/debug-info-verbose-trap.cpp b/clang/test/CodeGenCXX/debug-info-verbose-trap.cpp new file mode 100644 index 0000000000000..a8c78343b12a3 --- /dev/null +++ b/clang/test/CodeGenCXX/debug-info-verbose-trap.cpp @@ -0,0 +1,54 @@ +// RUN: %clang_cc1 -triple arm64-apple-ios -std=c++20 -emit-llvm -debug-info-kind=limited %s -o - | FileCheck %s + +// CHECK-LABEL: define void @_Z2f0v() +// CHECK: call void @llvm.trap(), !dbg ![[LOC17:.*]] + +// CHECK: declare void @llvm.trap() #[[ATTR1:.*]] + +// CHECK-LABEL: define void @_Z2f1v() +// CHECK: call void @llvm.trap(), !dbg ![[LOC23:.*]] +// CHECK: call void @llvm.trap(), !dbg ![[LOC25:.*]] + +// CHECK-LABEL: define void @_Z2f3v() +// CHECK: call void @_Z2f2IXadsoKcL_ZL8constCatEEEXadsoS0_L_ZL8constMsgEEEEvv() + +// CHECK-LABEL: define internal void @_Z2f2IXadsoKcL_ZL8constCatEEEXadsoS0_L_ZL8constMsgEEEEvv +// CHECK: call void @llvm.trap(), !dbg ![[LOC36:.*]] + +// CHECK: attributes #[[ATTR1]] = { cold {{.*}}} + +// CHECK: ![[FILESCOPE:.*]] = !DIFile(filename: "{{.*}}debug-info-verbose-trap.cpp" + +char const constCat[] = "category2"; +char const constMsg[] = "hello"; + +// CHECK: ![[SUBPROG14:.*]] = distinct !DISubprogram(name: "f0", linkageName: "_Z2f0v", +// CHECK: ![[LOC17]] = !DILocation(line: 0, scope: ![[SUBPROG18:.*]], inlinedAt: ![[LOC20:.*]]) +// CHECK: ![[SUBPROG18]] = distinct !DISubprogram(name: "__trap_msg$category1$Argument_must_not_be_null", scope: ![[FILESCOPE]], file: ![[FILESCOPE]], type: !{{.*}}, flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: !{{.*}}) +// CHECK: ![[LOC20]] = !DILocation(line: [[@LINE+2]], column: 3, scope: ![[SUBPROG14]]) +void f0() { + __builtin_verbose_trap("category1", "Argument_must_not_be_null"); +} + +// CHECK: ![[SUBPROG22:.*]] = distinct !DISubprogram(name: "f1", linkageName: "_Z2f1v", +// CHECK: ![[LOC23]] = !DILocation(line: 0, scope: ![[SUBPROG18]], inlinedAt: ![[LOC24:.*]]) +// CHECK: ![[LOC24]] = !DILocation(line: [[@LINE+5]], column: 3, scope: ![[SUBPROG22]]) +// CHECK: ![[LOC25]] = !DILocation(line: 0, scope: ![[SUBPROG26:.*]], inlinedAt: ![[LOC27:.*]]) +// CHECK: ![[SUBPROG26]] = distinct !DISubprogram(name: "__trap_msg$category2$hello", scope: ![[FILESCOPE]], file: ![[FILESCOPE]], type: !{{.*}}, flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: !{{.*}}) +// CHECK: ![[LOC27]] = !DILocation(line: [[@LINE+3]], column: 3, scope: ![[SUBPROG22]]) +void f1() { + __builtin_verbose_trap("category1", "Argument_must_not_be_null"); + __builtin_verbose_trap("category2", "hello"); +} + +// CHECK: ![[SUBPROG32:.*]] = distinct !DISubprogram(name: "f2", linkageName: "_Z2f2IXadsoKcL_ZL8constCatEEEXadsoS0_L_ZL8constMsgEEEEvv", +// CHECK: ![[LOC36]] = !DILocation(line: 0, scope: ![[SUBPROG26]], inlinedAt: ![[LOC37:.*]]) +// CHECK: ![[LOC37]] = !DILocation(line: [[@LINE+3]], column: 3, scope: ![[SUBPROG32]]) +template +void f2() { + __builtin_verbose_trap(category, reason); +} + +void f3() { + f2(); +} diff --git a/clang/test/SemaCXX/verbose-trap.cpp b/clang/test/SemaCXX/verbose-trap.cpp new file mode 100644 index 0000000000000..a91dd01106cc1 --- /dev/null +++ b/clang/test/SemaCXX/verbose-trap.cpp @@ -0,0 +1,46 @@ +// RUN: %clang_cc1 -std=c++11 -fsyntax-only -fcxx-exceptions -verify %s +// RUN: %clang_cc1 -std=c++20 -fsyntax-only -fcxx-exceptions -verify %s + +#if !__has_builtin(__builtin_verbose_trap) +#error +#endif + +constexpr char const* constCat1 = "cat"; +char const* const constCat2 = "cat"; +char const constCat3[] = "cat"; + +constexpr char const* constMsg1 = "hello"; +char const* const constMsg2 = "hello"; +char const constMsg3[] = "hello"; + +template +void f(const char * arg) { + __builtin_verbose_trap("cat1", "Arbitrary string literals can be used!"); + __builtin_verbose_trap(" cat1 ", "Argument_must_not_be_null"); + __builtin_verbose_trap("cat" "egory1", "hello" "world"); + __builtin_verbose_trap(constCat1, constMsg1); + __builtin_verbose_trap(constCat2, constMsg2); + __builtin_verbose_trap("", ""); + __builtin_verbose_trap(); // expected-error {{too few arguments}} + __builtin_verbose_trap(""); // expected-error {{too few arguments}} + __builtin_verbose_trap("", "", ""); // expected-error {{too many arguments}} + __builtin_verbose_trap("", 0); // expected-error {{argument to __builtin_verbose_trap must be a pointer to a constant string}} + __builtin_verbose_trap(1, ""); // expected-error {{cannot initialize a parameter of type 'const char *' with an rvalue of type 'int'}} + __builtin_verbose_trap(arg, ""); // expected-error {{argument to __builtin_verbose_trap must be a pointer to a constant string}} + __builtin_verbose_trap(category, reason); + __builtin_verbose_trap(u8"cat1", u8"hello"); +#if __cplusplus >= 202002L + // FIXME: Accept c++20 u8 string literals. + // expected-error@-3 {{cannot initialize a parameter of type 'const char *' with an lvalue of type 'const char8_t[5]'}} +#endif + __builtin_verbose_trap("", "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"); +} + +template +void f2() { + __builtin_verbose_trap(category, 1); // expected-error {{cannot initialize a parameter of type 'const char *' with an rvalue of type 'int'}} +} + +void test() { + f(nullptr); +}