llvm-project/clang/lib/CodeGen/TrapReasonBuilder.h
Dan Liew f1ee047320
[UBSan][BoundsSafety] Implement support for more expressive "trap reasons" (#154618)
In 29992cfd628ed5b968ccb73b17ed0521382ba317 (#145967) support was added
for "trap reasons" on traps emitted in UBSan in trapping mode (e.g.
`-fsanitize-trap=undefined`). This improved the debugging experience by
attaching the reason for trapping as a string on the debug info on trap
instructions. Consumers such as LLDB can display this trap reason string
when the trap is reached.

A limitation of that patch is that the trap reason string is hard-coded
for each `SanitizerKind` even though the compiler actually has much more
information about the trap available at compile time that could be shown
to the user.

This patch is an incremental step in fixing that. It consists of two
main steps.

**1. Introduce infrastructure for building trap reason strings**

To make it convenient to construct trap reason strings this patch
re-uses Clang's powerful diagnostic infrastructure to provide a
convenient API for constructing trap reason strings. This is achieved
by:

* Introducing a new `Trap` diagnostic kind to represent trap diagnostics
in TableGen files.
* Adding a new `Trap` diagnostic component. While this part probably
isn't technically necessary it seemed like I should follow the existing
convention used by the diagnostic system.
* Adding `DiagnosticTrapKinds.td` to describe the different trap
reasons.
* Add the `TrapReasonBuilder` and `TrapReason` classes to provide an
interface for constructing trap reason strings and the trap category.
Note this API while similar to `DiagnosticBuilder` has different
semantics which are described in the code comments. In particular the
behavior when the destructor is called is very different.
* Adding `CodeGenModule::BuildTrapReason()` as a convenient constructor
for the `TrapReasonBuilder`.

This use of the diagnostic system is a little unusual in that the
emitted trap diagnostics aren't actually consumed by normal diagnostic
consumers (e.g. the console). Instead the `TrapReasonBuilder` is just
used to format a string, so in effect the builder is somewhat analagous
to "printf". However, re-using the diagnostics system in this way brings
a several benefits:

* The powerful diagnostic templating languge (e.g. `%select`) can be
used.
* Formatting Clang data types (e.g. `Type`, `Expr`, etc.) just work
out-of-the-box.
* Describing trap reasons in tablegen files opens the door for
translation to different languages in the future.
* The `TrapReasonBuilder` API is very similar to `DiagnosticBuilder`
which makes it easy to use by anyone already familiar with Clang's
diagnostic system.

While UBSan is the first consumer of this new infrastructure the intent
is to use this to overhaul how trap reasons are implemented in the
`-fbounds-safety` implementation (currently exists downstream).

**2. Apply the new infrastructure to UBSan checks for arithmetic
overflow**

To demonstrate using `TrapReasonBuilder` this patch applies it to UBSan
traps for arithmetic overflow. The intention is that we would
iteratively switch to using the `TrapReasonBuilder` for all UBSan traps
where it makes sense in future patches.

Previously for code like

```
int test(int a, int b) { return a + b; }
```

The trap reason string looked like

```
Undefined Behavior Sanitizer: Integer addition overflowed
```

now the trap message looks like:

```
Undefined Behavior Sanitizer: signed integer addition overflow in 'a + b'
```

This string is much more specific because

* It explains if signed or unsigned overflow occurred
* It actually shows the expression that overflowed

One possible downside of this approach is it may blow up Debug info size
because now there can be many more distinct trap reason strings. To
allow users to avoid this a new driver/cc1 flag
`-fsanitize-debug-trap-reasons=` has been added which can either be
`none` (disable trap reasons entirely), `basic` (use the per
`SanitizerKind` hard coded strings), and `detailed` (use the new
expressive trap reasons implemented in this patch). The default is
`detailed` to give the best out-of-the-box debugging experience. The
existing `-fsanitize-debug-trap-reasons` and
`-fno-sanitize-debug-trap-reasons` have been kept for compatibility and
are aliases of the new flag with `detailed` and `none` arguments passed
respectively.


rdar://158612755
2025-08-27 13:07:15 -07:00

113 lines
3.9 KiB
C++

//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file contains the declaration of TrapReasonBuilder and related classes.
///
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_CODEGEN_TRAP_REASON_BUILDER_H
#define LLVM_CLANG_CODEGEN_TRAP_REASON_BUILDER_H
#include "clang/Basic/Diagnostic.h"
namespace clang {
namespace CodeGen {
/// Helper class for \class TrapReasonBuilder. \class TrapReason stores the
/// "trap reason" built by \class TrapReasonBuilder. This consists of
/// a trap message and trap category.
///
/// It is intended that this object be allocated on the stack.
class TrapReason {
public:
TrapReason() = default;
/// \return The trap message. Note the lifetime of the underlying storage for
/// the returned StringRef lives in this class which means the returned
/// StringRef should not be used after this class is destroyed.
StringRef getMessage() const { return Message; }
/// \return the trap category (e.g. "Undefined Behavior Sanitizer")
StringRef getCategory() const { return Category; }
bool isEmpty() const {
// Note both Message and Category are checked because it is legitimate for
// the Message to be empty but for the Category to be non-empty when the
// trap category is known but the specific reason is not available during
// codegen.
return Message.size() == 0 && Category.size() == 0;
}
private:
llvm::SmallString<64> Message;
// The Category doesn't need its own storage because the StringRef points
// to a global constant string.
StringRef Category;
// Only this class can set the private fields.
friend class TrapReasonBuilder;
};
/// Class to make it convenient to initialize TrapReason objects which can be
/// used to attach the "trap reason" to trap instructions.
///
/// Although this class inherits from \class DiagnosticBuilder it has slightly
/// different semantics.
///
/// * This class should only be used with trap diagnostics (declared in
/// `DiagnosticTrapKinds.td`).
/// * The `TrapReasonBuilder` does not emit diagnostics to the normal
/// diagnostics consumers on destruction like normal Diagnostic builders.
/// Instead on destruction it assigns to the TrapReason object passed into
/// the constructor.
///
/// Given that this class inherits from `DiagnosticBuilder` it inherits all of
/// its abilities to format diagnostic messages and consume various types in
/// class (e.g. Type, Exprs, etc.). This makes it particularly suited to
/// printing types and expressions from the AST while codegen-ing runtime
/// checks.
///
///
/// Example use via the `CodeGenModule::BuildTrapReason` helper.
///
/// \code
/// {
/// TrapReason TR;
/// CGM.BuildTrapReason(diag::trap_diagnostic, TR) << 0 << SomeExpr;
/// consume(&TR);
/// }
/// \endcode
///
///
class TrapReasonBuilder : public DiagnosticBuilder {
public:
TrapReasonBuilder(DiagnosticsEngine *DiagObj, unsigned DiagID,
TrapReason &TR);
~TrapReasonBuilder();
// Prevent accidentally copying or assigning
TrapReasonBuilder &operator=(const TrapReasonBuilder &) = delete;
TrapReasonBuilder &operator=(const TrapReasonBuilder &&) = delete;
TrapReasonBuilder(const TrapReasonBuilder &) = delete;
TrapReasonBuilder(const TrapReasonBuilder &&) = delete;
private:
/// \return Format the trap message into `Storage`.
void getMessage(SmallVectorImpl<char> &Storage);
/// \return Return the trap category. These are the `CategoryName` property
/// of `trap` diagnostics declared in `DiagnosticTrapKinds.td`.
StringRef getCategory();
private:
TrapReason &TR;
};
} // namespace CodeGen
} // namespace clang
#endif