Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,18 @@ Non-comprehensive list of changes in this release
-------------------------------------------------
- Added ``__builtin_elementwise_minnumnum`` and ``__builtin_elementwise_maxnumnum``.

- Trapping UBSan (e.g. ``-fsanitize-trap=undefined``) now emits a string describing the reason for
trapping into the generated debug info. This feature allows debuggers (e.g. LLDB) to display
the reason for trapping if the trap is reached. The string is currently encoded in the debug
info as an artificial frame that claims to be inlined at the trap location. The function used
for the artificial frame is an artificial function whose name encodes the reason for trapping.
The encoding used is currently the same as ``__builtin_verbose_trap`` but might change in the future.
This feature is enabled by default but can be disabled by compiling with
``-fno-sanitize-annotate-debug-info-traps``.

New Compiler Flags
------------------
- New option ``-fno-sanitize-annotate-debug-info-traps`` added to disable emitting trap reasons into the debug info when compiling with trapping UBSan (e.g. ``-fsanitize-trap=undefined``).

Deprecated Compiler Flags
-------------------------
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/CodeGenOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ CODEGENOPT(SanitizeBinaryMetadataAtomics, 1, 0, Benign) ///< Emit PCs for atomic
CODEGENOPT(SanitizeBinaryMetadataUAR, 1, 0, Benign) ///< Emit PCs for start of functions
///< that are subject for use-after-return checking.
CODEGENOPT(SanitizeStats , 1, 0, Benign) ///< Collect statistics for sanitizers.
CODEGENOPT(SanitizeDebugTrapReasons, 1, 1 , Benign) ///< Enable UBSan trapping messages
CODEGENOPT(SimplifyLibCalls , 1, 1, Benign) ///< Set when -fbuiltin is enabled.
CODEGENOPT(SoftFloat , 1, 0, Benign) ///< -soft-float.
CODEGENOPT(SpeculativeLoadHardening, 1, 0, Benign) ///< Enable speculative load hardening.
Expand Down
10 changes: 10 additions & 0 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -2597,6 +2597,16 @@ def fsanitize_undefined_trap_on_error
def fno_sanitize_undefined_trap_on_error
: Flag<["-"], "fno-sanitize-undefined-trap-on-error">, Group<f_clang_Group>,
Alias<fno_sanitize_trap_EQ>, AliasArgs<["undefined"]>;
defm sanitize_debug_trap_reasons
: BoolFOption<
"sanitize-debug-trap-reasons",
CodeGenOpts<"SanitizeDebugTrapReasons">, DefaultTrue,
PosFlag<SetTrue, [], [ClangOption, CC1Option],
"Annotate trap blocks in debug info with UBSan trap reasons">,
NegFlag<SetFalse, [], [ClangOption, CC1Option],
"Do not annotate trap blocks in debug info with UBSan trap "
"reasons">>;

defm sanitize_minimal_runtime : BoolOption<"f", "sanitize-minimal-runtime",
CodeGenOpts<"SanitizeMinimalRuntime">, DefaultFalse,
PosFlag<SetTrue>,
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/CodeGen/CGDebugInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6435,7 +6435,7 @@ CodeGenFunction::LexicalScope::~LexicalScope() {
static std::string SanitizerHandlerToCheckLabel(SanitizerHandler Handler) {
std::string Label;
switch (Handler) {
#define SANITIZER_CHECK(Enum, Name, Version) \
#define SANITIZER_CHECK(Enum, Name, Version, Msg) \
case Enum: \
Label = "__ubsan_check_" #Name; \
break;
Expand Down
29 changes: 26 additions & 3 deletions clang/lib/CodeGen/CGExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ enum VariableTypeDescriptorKind : uint16_t {
// Miscellaneous Helper Methods
//===--------------------------------------------------------------------===//

static llvm::StringRef GetUBSanTrapForHandler(SanitizerHandler ID) {
switch (ID) {
#define SANITIZER_CHECK(Enum, Name, Version, Msg) \
case SanitizerHandler::Enum: \
return Msg;
LIST_SANITIZER_CHECKS
#undef SANITIZER_CHECK
}
}

/// CreateTempAlloca - This creates a alloca and inserts it into the entry
/// block.
RawAddress
Expand Down Expand Up @@ -3649,7 +3659,7 @@ struct SanitizerHandlerInfo {
}

const SanitizerHandlerInfo SanitizerHandlers[] = {
#define SANITIZER_CHECK(Enum, Name, Version) {#Name, Version},
#define SANITIZER_CHECK(Enum, Name, Version, Msg) {#Name, Version},
LIST_SANITIZER_CHECKS
#undef SANITIZER_CHECK
};
Expand Down Expand Up @@ -3954,6 +3964,8 @@ void CodeGenFunction::EmitCfiCheckFail() {
StartFunction(GlobalDecl(), CGM.getContext().VoidTy, F, FI, Args,
SourceLocation());

ApplyDebugLocation ADL = ApplyDebugLocation::CreateArtificial(*this);

// This function is not affected by NoSanitizeList. This function does
// not have a source location, but "src:*" would still apply. Revert any
// changes to SanOpts made in StartFunction.
Expand Down Expand Up @@ -4051,6 +4063,15 @@ void CodeGenFunction::EmitTrapCheck(llvm::Value *Checked,

llvm::BasicBlock *&TrapBB = TrapBBs[CheckHandlerID];

llvm::DILocation *TrapLocation = Builder.getCurrentDebugLocation();
llvm::StringRef TrapMessage = GetUBSanTrapForHandler(CheckHandlerID);

if (getDebugInfo() && !TrapMessage.empty() &&
CGM.getCodeGenOpts().SanitizeDebugTrapReasons && TrapLocation) {
TrapLocation = getDebugInfo()->CreateTrapFailureMessageFor(
TrapLocation, "Undefined Behavior Sanitizer", TrapMessage);
}

NoMerge = NoMerge || !CGM.getCodeGenOpts().OptimizationLevel ||
(CurCodeDecl && CurCodeDecl->hasAttr<OptimizeNoneAttr>());

Expand All @@ -4059,8 +4080,8 @@ void CodeGenFunction::EmitTrapCheck(llvm::Value *Checked,
auto Call = TrapBB->begin();
assert(isa<llvm::CallInst>(Call) && "Expected call in trap BB");

Call->applyMergedLocation(Call->getDebugLoc(),
Builder.getCurrentDebugLocation());
Call->applyMergedLocation(Call->getDebugLoc(), TrapLocation);

Builder.CreateCondBr(Checked, Cont, TrapBB,
MDHelper.createLikelyBranchWeights());
} else {
Expand All @@ -4069,6 +4090,8 @@ void CodeGenFunction::EmitTrapCheck(llvm::Value *Checked,
MDHelper.createLikelyBranchWeights());
EmitBlock(TrapBB);

ApplyDebugLocation applyTrapDI(*this, TrapLocation);

llvm::CallInst *TrapCall =
Builder.CreateCall(CGM.getIntrinsic(llvm::Intrinsic::ubsantrap),
llvm::ConstantInt::get(CGM.Int8Ty, CheckHandlerID));
Expand Down
88 changes: 61 additions & 27 deletions clang/lib/CodeGen/SanitizerHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,69 @@
#define LLVM_CLANG_LIB_CODEGEN_SANITIZER_HANDLER_H

#define LIST_SANITIZER_CHECKS \
SANITIZER_CHECK(AddOverflow, add_overflow, 0) \
SANITIZER_CHECK(BuiltinUnreachable, builtin_unreachable, 0) \
SANITIZER_CHECK(CFICheckFail, cfi_check_fail, 0) \
SANITIZER_CHECK(DivremOverflow, divrem_overflow, 0) \
SANITIZER_CHECK(DynamicTypeCacheMiss, dynamic_type_cache_miss, 0) \
SANITIZER_CHECK(FloatCastOverflow, float_cast_overflow, 0) \
SANITIZER_CHECK(FunctionTypeMismatch, function_type_mismatch, 0) \
SANITIZER_CHECK(ImplicitConversion, implicit_conversion, 0) \
SANITIZER_CHECK(InvalidBuiltin, invalid_builtin, 0) \
SANITIZER_CHECK(InvalidObjCCast, invalid_objc_cast, 0) \
SANITIZER_CHECK(LoadInvalidValue, load_invalid_value, 0) \
SANITIZER_CHECK(MissingReturn, missing_return, 0) \
SANITIZER_CHECK(MulOverflow, mul_overflow, 0) \
SANITIZER_CHECK(NegateOverflow, negate_overflow, 0) \
SANITIZER_CHECK(NullabilityArg, nullability_arg, 0) \
SANITIZER_CHECK(NullabilityReturn, nullability_return, 1) \
SANITIZER_CHECK(NonnullArg, nonnull_arg, 0) \
SANITIZER_CHECK(NonnullReturn, nonnull_return, 1) \
SANITIZER_CHECK(OutOfBounds, out_of_bounds, 0) \
SANITIZER_CHECK(PointerOverflow, pointer_overflow, 0) \
SANITIZER_CHECK(ShiftOutOfBounds, shift_out_of_bounds, 0) \
SANITIZER_CHECK(SubOverflow, sub_overflow, 0) \
SANITIZER_CHECK(TypeMismatch, type_mismatch, 1) \
SANITIZER_CHECK(AlignmentAssumption, alignment_assumption, 0) \
SANITIZER_CHECK(VLABoundNotPositive, vla_bound_not_positive, 0) \
SANITIZER_CHECK(BoundsSafety, bounds_safety, 0)
SANITIZER_CHECK(AddOverflow, add_overflow, 0, "Integer addition overflowed") \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this singed, unsigned or both? UBSan has both but I don't see two versions here, it is curious there is a sub overflow, which is not specifically delineated via -fsanitize, may be worth clarifying.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's both...

Value *ScalarExprEmitter::EmitAdd(const BinOpInfo &op) {
  if (op.LHS->getType()->isPointerTy() ||
      op.RHS->getType()->isPointerTy())
    return emitPointerArithmetic(CGF, op, CodeGenFunction::NotSubtraction);

  if (op.Ty->isSignedIntegerOrEnumerationType()) {
    switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
    case LangOptions::SOB_Defined:
      if (!CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow))
        return Builder.CreateAdd(op.LHS, op.RHS, "add");
      [[fallthrough]];
    case LangOptions::SOB_Undefined:
      if (!CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow))
        return Builder.CreateNSWAdd(op.LHS, op.RHS, "add");
      [[fallthrough]];
    case LangOptions::SOB_Trapping:
      if (CanElideOverflowCheck(CGF.getContext(), op))
        return Builder.CreateNSWAdd(op.LHS, op.RHS, "add");
      return EmitOverflowCheckedBinOp(op);
    }
  }

// snip...

  if (op.Ty->isUnsignedIntegerType() &&
      CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
      !CanElideOverflowCheck(CGF.getContext(), op))
    return EmitOverflowCheckedBinOp(op);

The calls to EmitOverflowCheckedBinOp(op) are what generate the call to EmitTrapCheck.

This is actually something I've asked @anthonyhatran to look at as one of his later tasks during GSoC project.

SANITIZER_CHECK(BuiltinUnreachable, builtin_unreachable, 0, \
"_builtin_unreachable(), execution reached an unreachable " \
"program point") \
SANITIZER_CHECK(CFICheckFail, cfi_check_fail, 0, \
"Control flow integrity check failed") \
SANITIZER_CHECK(DivremOverflow, divrem_overflow, 0, \
"Integer divide or remainder overflowed") \
SANITIZER_CHECK(DynamicTypeCacheMiss, dynamic_type_cache_miss, 0, \
"Dynamic type cache miss, member call made on an object " \
"whose dynamic type differs from the expected type") \
SANITIZER_CHECK(FloatCastOverflow, float_cast_overflow, 0, \
"Floating-point to integer conversion overflowed") \
SANITIZER_CHECK(FunctionTypeMismatch, function_type_mismatch, 0, \
"Function called with mismatched signature") \
SANITIZER_CHECK(ImplicitConversion, implicit_conversion, 0, \
"Implicit integer conversion overflowed or lost data") \
SANITIZER_CHECK(InvalidBuiltin, invalid_builtin, 0, \
"Invalid use of builtin function") \
SANITIZER_CHECK(InvalidObjCCast, invalid_objc_cast, 0, \
"Invalid Objective-C cast") \
SANITIZER_CHECK(LoadInvalidValue, load_invalid_value, 0, \
"Loaded an invalid or uninitialized value for the type") \
SANITIZER_CHECK(MissingReturn, missing_return, 0, \
"Execution reached the end of a value-returning function " \
"without returning a value") \
SANITIZER_CHECK(MulOverflow, mul_overflow, 0, \
"Integer multiplication overflowed") \
SANITIZER_CHECK(NegateOverflow, negate_overflow, 0, \
"Integer negation overflowed") \
SANITIZER_CHECK( \
NullabilityArg, nullability_arg, 0, \
"Passing null as an argument which is annotated with _Nonnull") \
SANITIZER_CHECK(NullabilityReturn, nullability_return, 1, \
"Returning null from a function with a return type " \
"annotated with _Nonnull") \
SANITIZER_CHECK(NonnullArg, nonnull_arg, 0, \
"Passing null pointer as an argument which is declared to " \
"never be null") \
SANITIZER_CHECK(NonnullReturn, nonnull_return, 1, \
"Returning null pointer from a function which is declared " \
"to never return null") \
SANITIZER_CHECK(OutOfBounds, out_of_bounds, 0, "Array index out of bounds") \
SANITIZER_CHECK(PointerOverflow, pointer_overflow, 0, \
"Pointer arithmetic overflowed bounds") \
SANITIZER_CHECK(ShiftOutOfBounds, shift_out_of_bounds, 0, \
"Shift exponent is too large for the type") \
SANITIZER_CHECK(SubOverflow, sub_overflow, 0, \
"Integer subtraction overflowed") \
SANITIZER_CHECK(TypeMismatch, type_mismatch, 1, \
"Type mismatch in operation") \
SANITIZER_CHECK(AlignmentAssumption, alignment_assumption, 0, \
"Alignment assumption violated") \
SANITIZER_CHECK( \
VLABoundNotPositive, vla_bound_not_positive, 0, \
"Variable length array bound evaluates to non-positive value") \
SANITIZER_CHECK(BoundsSafety, bounds_safety, 0, \
"") // BoundsSafety Msg is empty because it is not considered
// part of UBSan; therefore, no trap reason is emitted for
// this case.

enum SanitizerHandler {
#define SANITIZER_CHECK(Enum, Name, Version) Enum,
#define SANITIZER_CHECK(Enum, Name, Version, Msg) Enum,
LIST_SANITIZER_CHECKS
#undef SANITIZER_CHECK
};
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/Driver/SanitizerArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1382,6 +1382,12 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args,
CmdArgs.push_back(Args.MakeArgString("-fsanitize-annotate-debug-info=" +
toString(AnnotateDebugInfo)));

if (const Arg *A =
Args.getLastArg(options::OPT_fsanitize_debug_trap_reasons,
options::OPT_fno_sanitize_debug_trap_reasons)) {
CmdArgs.push_back(Args.MakeArgString(A->getAsString(Args)));
}

addSpecialCaseListOpt(Args, CmdArgs,
"-fsanitize-ignorelist=", UserIgnorelistFiles);
addSpecialCaseListOpt(Args, CmdArgs,
Expand Down
10 changes: 6 additions & 4 deletions clang/test/CodeGen/bounds-checking-debuginfo.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ void d(double*);
// CHECK-TRAP-NEXT: [[TMP1:%.*]] = icmp ult i64 [[TMP0]], 10, !dbg [[DBG23]], !nosanitize [[META10]]
// CHECK-TRAP-NEXT: br i1 [[TMP1]], label %[[CONT:.*]], label %[[TRAP:.*]], !dbg [[DBG23]], !prof [[PROF27:![0-9]+]], !nosanitize [[META10]]
// CHECK-TRAP: [[TRAP]]:
// CHECK-TRAP-NEXT: call void @llvm.ubsantrap(i8 18) #[[ATTR3:[0-9]+]], !dbg [[DBG23]], !nosanitize [[META10]]
// CHECK-TRAP-NEXT: unreachable, !dbg [[DBG23]], !nosanitize [[META10]]
// CHECK-TRAP-NEXT: call void @llvm.ubsantrap(i8 18) #[[ATTR3:[0-9]+]], !dbg [[DBGTRAP:![0-9]+]], !nosanitize [[META10]]
// CHECK-TRAP-NEXT: unreachable, !dbg [[DBGTRAP]], !nosanitize [[META10]]
// CHECK-TRAP: [[CONT]]:
// CHECK-TRAP-NEXT: [[IDXPROM:%.*]] = sext i32 [[CALL]] to i64, !dbg [[DBG26:![0-9]+]]
// CHECK-TRAP-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [10 x double], ptr [[A]], i64 0, i64 [[IDXPROM]], !dbg [[DBG26]]
// CHECK-TRAP-NEXT: [[TMP2:%.*]] = load double, ptr [[ARRAYIDX]], align 8, !dbg [[DBG26]]
// CHECK-TRAP-NEXT: ret double [[TMP2]], !dbg [[DBG28:![0-9]+]]
// CHECK-TRAP-NEXT: ret double [[TMP2]], !dbg [[DBG30:![0-9]+]]
//
// CHECK-NOTRAP-LABEL: define dso_local double @f1(
// CHECK-NOTRAP-SAME: i32 noundef [[B:%.*]], i32 noundef [[I:%.*]]) #[[ATTR0:[0-9]+]] !dbg [[DBG4:![0-9]+]] {
Expand Down Expand Up @@ -93,7 +93,9 @@ double f1(int b, int i) {
// CHECK-TRAP: [[META25]] = !DISubroutineType(types: null)
// CHECK-TRAP: [[DBG26]] = !DILocation(line: 66, column: 10, scope: [[DBG4]])
// CHECK-TRAP: [[PROF27]] = !{!"branch_weights", i32 1048575, i32 1}
// CHECK-TRAP: [[DBG28]] = !DILocation(line: 66, column: 3, scope: [[DBG4]])
// CHECK-TRAP: [[DBGTRAP]] = !DILocation(line: 0, scope: [[TRAPMSG:![0-9]+]], inlinedAt: [[DBG23]])
// CHECK-TRAP: [[TRAPMSG]] = distinct !DISubprogram(name: "__clang_trap_msg$Undefined Behavior Sanitizer$Array index out of bounds", scope: [[META5]], file: [[META5]], type: [[META25]], flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: [[META0]])
// CHECK-TRAP: [[DBG30]] = !DILocation(line: 66, column: 3, scope: [[DBG4]])
//.
// CHECK-NOTRAP: [[META0:![0-9]+]] = distinct !DICompileUnit(language: DW_LANG_C11, file: [[META1:![0-9]+]], isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
// CHECK-NOTRAP: [[META1]] = !DIFile(filename: "<stdin>", directory: {{.*}})
Expand Down
Loading