Skip to content

Commit 5ad987d

Browse files
committed
[BoundsSafety] Add warning diagnostics for uses of legacy bounds checks
This adds warning diagnostics when any of the new bounds checks that can be enabled with `-fbounds-safety-bringup-missing-checks=batch_0` are disabled. If all bounds checks in the batch are disabled a single diagnostic is emitted. If only some of the bounds checks in the batch are disabled then a diagnostic is emitted for each disabled bounds check. The current implementation supports there being multple batches of checks. However, there is currently only one batch (`batch_0`). Unfortunately I've discovered these diagnostics don't respect `-Werror` or `-Wno-bounds-safety-legacy-checks-enabled`. Fixing that is out-of-scope for this patch and is tracked by rdar://152730261. The intention is to make these warnings be errors eventually. rdar://150805550
1 parent 8011632 commit 5ad987d

File tree

5 files changed

+372
-6
lines changed

5 files changed

+372
-6
lines changed

clang/include/clang/Basic/DiagnosticFrontendKinds.td

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,36 @@ def warn_bounds_safety_adoption_mode_ignored : Warning<
402402
"-fbounds-safety-adoption-mode without -fbounds-safety is "
403403
"ignored">,
404404
InGroup<BoundsSafetyAdoptionModeIgnored>;
405+
406+
def warn_bounds_safety_new_checks_none : Warning<
407+
"compiling with legacy -fbounds-safety bounds checks is deprecated;"
408+
" compile with -fbounds-safety-bringup-missing-checks=%0 to use the "
409+
"new bound checks">,
410+
InGroup<BoundsSafetyLegacyChecksEnabled>;
411+
def warn_bounds_safety_new_checks_mixed : Warning<
412+
"compiling with \"%select{"
413+
"access_size|" // BS_CHK_AccessSize
414+
"indirect_count_update|" // BS_CHK_IndirectCountUpdate
415+
"return_size|" // BS_CHK_ReturnSize
416+
"ended_by_lower_bound|" // BS_CHK_EndedByLowerBound
417+
"compound_literal_init|" // BS_CHK_CompoundLiteralInit
418+
"libc_attributes|" // BS_CHK_LibCAttributes
419+
"array_subscript_agg" // BS_CHK_ArraySubscriptAgg
420+
"}0\" bounds check disabled is deprecated;"
421+
" %select{"
422+
"compile with -fbounds-safety-bringup-missing-checks=%2|"
423+
"remove -fno-bounds-safety-bringup-missing-checks=%select{"
424+
"access_size|" // BS_CHK_AccessSize
425+
"indirect_count_update|" // BS_CHK_IndirectCountUpdate
426+
"return_size|" // BS_CHK_ReturnSize
427+
"ended_by_lower_bound|" // BS_CHK_EndedByLowerBound
428+
"compound_literal_init|" // BS_CHK_CompoundLiteralInit
429+
"libc_attributes|" // BS_CHK_LibCAttributes
430+
"array_subscript_agg" // BS_CHK_ArraySubscriptAgg
431+
"}0"
432+
"|}1"
433+
" to enable the new bound checks">,
434+
InGroup<BoundsSafetyLegacyChecksEnabled>;
405435
// TO_UPSTREAM(BoundsSafety) OFF
406436

407437
let CategoryName = "Instrumentation Issue" in {

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1670,6 +1670,8 @@ def BoundsSafetyStrictTerminatedByCast
16701670
: DiagGroup<"bounds-safety-strict-terminated-by-cast">;
16711671
def BoundsSafetyCountAttrArithConstantCount :
16721672
DiagGroup<"bounds-safety-externally-counted-ptr-arith-constant-count">;
1673+
def BoundsSafetyLegacyChecksEnabled :
1674+
DiagGroup<"bounds-safety-legacy-checks-enabled">;
16731675
// TO_UPSTREAM(BoundsSafety) OFF
16741676

16751677
// -fbounds-safety and bounds annotation related warnings

clang/include/clang/Basic/LangOptions.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,8 @@ class LangOptionsBase {
454454
BS_CHK_CompoundLiteralInit = 1 << 4, // rdar://110871666
455455
BS_CHK_LibCAttributes = 1 << 5, // rdar://84733153
456456
BS_CHK_ArraySubscriptAgg = 1 << 6, // rdar://145020583
457+
458+
BS_CHK_MaxMask = BS_CHK_ArraySubscriptAgg,
457459
};
458460
using BoundsSafetyNewChecksMaskIntTy =
459461
std::underlying_type_t<BoundsSafetyNewChecks>;

clang/lib/Driver/BoundsSafetyArgs.cpp

Lines changed: 115 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
//===----------------------------------------------------------------------===//
88
#include "clang/Driver/BoundsSafetyArgs.h"
99
#include "clang/Basic/DiagnosticDriver.h"
10+
#include "clang/Basic/DiagnosticFrontend.h"
1011
#include "clang/Driver/Options.h"
12+
#include "llvm/ADT/StringSet.h"
1113

1214
using namespace llvm::opt;
1315
using namespace clang::driver::options;
@@ -16,15 +18,101 @@ namespace clang {
1618

1719
namespace driver {
1820

21+
template <class AllocatorTy = llvm::MallocAllocator>
22+
static void DiagnoseDisabledBoundsSafetyChecks(
23+
LangOptions::BoundsSafetyNewChecksMaskIntTy EnabledChecks,
24+
DiagnosticsEngine *Diags,
25+
LangOptions::BoundsSafetyNewChecksMaskIntTy
26+
IndividualChecksExplicitlyDisabled) {
27+
struct BoundsCheckBatch {
28+
const char *Name;
29+
LangOptionsBase::BoundsSafetyNewChecksMaskIntTy Checks;
30+
};
31+
32+
// Batches of checks should be ordered with newest first
33+
BoundsCheckBatch Batches[] = {
34+
// We deliberately don't include `all` here because that batch
35+
// isn't stable over time (unlike batches like `batch_0`) so we
36+
// don't want to suggest users start using it.
37+
{"batch_0", LangOptions::getBoundsSafetyNewChecksMaskForGroup("batch_0")},
38+
{"none", LangOptions::BS_CHK_None}};
39+
40+
ArrayRef<BoundsCheckBatch> BatchesAR(Batches);
41+
LangOptionsBase::BoundsSafetyNewChecksMaskIntTy DiagnosedDisabledChecks =
42+
LangOptions::BS_CHK_None;
43+
44+
// Loop over all batches except "none"
45+
for (size_t BatchIdx = 0; BatchIdx < BatchesAR.size() - 1; ++BatchIdx) {
46+
auto ChecksInCurrentBatch = BatchesAR[BatchIdx].Checks;
47+
auto ChecksInOlderBatch = BatchesAR[BatchIdx + 1].Checks;
48+
auto ChecksInCurrentBatchOnly = ChecksInCurrentBatch & ~ChecksInOlderBatch;
49+
auto CurrentBatchName = BatchesAR[BatchIdx].Name;
50+
51+
if ((EnabledChecks & ChecksInCurrentBatchOnly) == ChecksInCurrentBatchOnly)
52+
continue; // All checks in batch are enabled. Nothing to diagnose.
53+
54+
// Diagnose disabled bounds checks
55+
56+
if ((EnabledChecks & ChecksInCurrentBatchOnly) == 0) {
57+
// None of the checks in the current batch are enabled. Diagnose this
58+
// once for all the checks in the batch.
59+
if ((DiagnosedDisabledChecks & ChecksInCurrentBatchOnly) !=
60+
ChecksInCurrentBatchOnly) {
61+
Diags->Report(diag::warn_bounds_safety_new_checks_none)
62+
<< CurrentBatchName;
63+
DiagnosedDisabledChecks |= ChecksInCurrentBatchOnly;
64+
}
65+
continue;
66+
}
67+
68+
// Some (but not all) checks are disabled in the current batch. Iterate over
69+
// each check in the batch and emit a diagnostic for each disabled check
70+
// in the batch.
71+
assert(ChecksInCurrentBatch > 0);
72+
auto FirstCheckInBatch = 1 << __builtin_ctz(ChecksInCurrentBatch);
73+
for (size_t CheckBit = FirstCheckInBatch;
74+
CheckBit <= LangOptions::BS_CHK_MaxMask; CheckBit <<= 1) {
75+
assert(CheckBit != 0);
76+
if ((CheckBit & ChecksInCurrentBatch) == 0)
77+
continue; // Check not in batch
78+
79+
if (EnabledChecks & CheckBit)
80+
continue; // Check is active
81+
82+
// Diagnose disabled check
83+
if (!(DiagnosedDisabledChecks & CheckBit)) {
84+
size_t CheckNumber = __builtin_ctz(CheckBit);
85+
// If we always suggested enabling the current batch that
86+
// could be confusing if the user passed something like
87+
// `-fbounds-safety-bringup-missing-checks=batch_0
88+
// -fno-bounds-safety-bringup-missing-checks=access_size`. To avoid
89+
// this we detect when the check corresponding to `CheckBit` has been
90+
// explicitly disabled on the command line and in that case we suggeset
91+
// removing the flag.
92+
bool SuggestRemovingFlag =
93+
CheckBit & IndividualChecksExplicitlyDisabled;
94+
Diags->Report(diag::warn_bounds_safety_new_checks_mixed)
95+
<< CheckNumber << SuggestRemovingFlag << CurrentBatchName;
96+
DiagnosedDisabledChecks |= CheckBit;
97+
}
98+
}
99+
}
100+
}
101+
19102
LangOptions::BoundsSafetyNewChecksMaskIntTy
20103
ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
21104
DiagnosticsEngine *Diags) {
105+
LangOptions::BoundsSafetyNewChecksMaskIntTy
106+
IndividualChecksExplicitlyDisabled = LangOptions::BS_CHK_None;
22107
auto FilteredArgs =
23108
Args.filtered(OPT_fbounds_safety_bringup_missing_checks_EQ,
24109
OPT_fno_bounds_safety_bringup_missing_checks_EQ);
25110
if (FilteredArgs.empty()) {
26111
// No flags present. Use the default
27-
return LangOptions::getDefaultBoundsSafetyNewChecksMask();
112+
auto Result = LangOptions::getDefaultBoundsSafetyNewChecksMask();
113+
DiagnoseDisabledBoundsSafetyChecks(Result, Diags,
114+
IndividualChecksExplicitlyDisabled);
115+
return Result;
28116
}
29117

30118
// If flags are present then start with BS_CHK_None as the initial mask and
@@ -35,6 +123,11 @@ ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
35123
// All the options will be applied as masks in the command line order, to make
36124
// it easier to enable all but certain checks (or disable all but certain
37125
// checks).
126+
const auto Batch0Checks =
127+
LangOptions::getBoundsSafetyNewChecksMaskForGroup("batch_0");
128+
const auto AllChecks =
129+
LangOptions::getBoundsSafetyNewChecksMaskForGroup("all");
130+
bool Errors = false;
38131
for (const Arg *A : FilteredArgs) {
39132
for (const char *Value : A->getValues()) {
40133
std::optional<LangOptions::BoundsSafetyNewChecksMaskIntTy> Mask =
@@ -51,17 +144,16 @@ ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
51144
.Case("libc_attributes", LangOptions::BS_CHK_LibCAttributes)
52145
.Case("array_subscript_agg",
53146
LangOptions::BS_CHK_ArraySubscriptAgg)
54-
.Case("all",
55-
LangOptions::getBoundsSafetyNewChecksMaskForGroup("all"))
56-
.Case(
57-
"batch_0",
58-
LangOptions::getBoundsSafetyNewChecksMaskForGroup("batch_0"))
147+
.Case("all", AllChecks)
148+
.Case("batch_0", Batch0Checks)
59149
.Case("none", LangOptions::BS_CHK_None)
60150
.Default(std::nullopt);
151+
61152
if (!Mask) {
62153
if (Diags)
63154
Diags->Report(diag::err_drv_invalid_value)
64155
<< A->getSpelling() << Value;
156+
Errors = true;
65157
break;
66158
}
67159

@@ -81,6 +173,7 @@ ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
81173
<< A->getSpelling() << Value
82174
<< (IsPosFlag ? "-fno-bounds-safety-bringup-missing-checks"
83175
: "-fbounds-safety-bringup-missing-checks");
176+
Errors = true;
84177
break;
85178
}
86179

@@ -91,8 +184,24 @@ ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
91184
OPT_fno_bounds_safety_bringup_missing_checks_EQ));
92185
Result &= ~(*Mask);
93186
}
187+
188+
// Update which checks have been explicitly disabled
189+
if (__builtin_popcount(*Mask) == 1) {
190+
// A single check was enabled/disabled
191+
if (IsPosFlag)
192+
IndividualChecksExplicitlyDisabled &= ~(*Mask);
193+
else
194+
IndividualChecksExplicitlyDisabled |= *Mask;
195+
} else {
196+
// A batch of checks were enabled/disabled. Any checks in that batch
197+
// are no longer explicitly set.
198+
IndividualChecksExplicitlyDisabled &= ~(*Mask);
199+
}
94200
}
95201
}
202+
if (Diags && !Errors)
203+
DiagnoseDisabledBoundsSafetyChecks(Result, Diags,
204+
IndividualChecksExplicitlyDisabled);
96205
return Result;
97206
}
98207

0 commit comments

Comments
 (0)