Skip to content

Commit 4c8f6e7

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 implementation will either suggest enabling a batch of checks (e.g. `-fbounds-safety-bringup-missing-checks=batch_0`) or will suggest removing a flag that is explicitly disabling a check (e.g. `-fno-bounds-safety-bringup-missing-checks=access_size`). The current implementation supports there being multple batches of checks. However, there is currently only one batch (`batch_0`). I originally tried to emit these warnings in the frontend. Unfortunately it turns out warning suppression (i.e. `-Wno-bounds-safety-legacy-checks-enabled`) and `-Werror` don't work correctly if warnings are emitted from the frontend (rdar://152730261). To workaround this the `-fbounds-safety-bringup-missing-checks=` flags are now also parsed in the Driver and at this point (and only this point) diagnostics for missing checks are emitted. The intention is to make these warnings be errors eventually. rdar://150805550
1 parent f0bb107 commit 4c8f6e7

File tree

9 files changed

+450
-11
lines changed

9 files changed

+450
-11
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/include/clang/Driver/BoundsSafetyArgs.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ namespace driver {
1616

1717
LangOptions::BoundsSafetyNewChecksMaskIntTy
1818
ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
19-
DiagnosticsEngine *Diags);
19+
DiagnosticsEngine *Diags,
20+
bool DiagnoseMissingChecks);
2021
} // namespace driver
2122
} // namespace clang
2223

clang/lib/Driver/BoundsSafetyArgs.cpp

Lines changed: 121 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
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"
13+
#include "llvm/ADT/bit.h"
14+
#include <array>
1115

1216
using namespace llvm::opt;
1317
using namespace clang::driver::options;
@@ -16,15 +20,103 @@ namespace clang {
1620

1721
namespace driver {
1822

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

30122
// If flags are present then start with BS_CHK_None as the initial mask and
@@ -35,6 +127,11 @@ ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
35127
// All the options will be applied as masks in the command line order, to make
36128
// it easier to enable all but certain checks (or disable all but certain
37129
// checks).
130+
const auto Batch0Checks =
131+
LangOptions::getBoundsSafetyNewChecksMaskForGroup("batch_0");
132+
const auto AllChecks =
133+
LangOptions::getBoundsSafetyNewChecksMaskForGroup("all");
134+
bool Errors = false;
38135
for (const Arg *A : FilteredArgs) {
39136
for (const char *Value : A->getValues()) {
40137
std::optional<LangOptions::BoundsSafetyNewChecksMaskIntTy> Mask =
@@ -51,17 +148,16 @@ ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
51148
.Case("libc_attributes", LangOptions::BS_CHK_LibCAttributes)
52149
.Case("array_subscript_agg",
53150
LangOptions::BS_CHK_ArraySubscriptAgg)
54-
.Case("all",
55-
LangOptions::getBoundsSafetyNewChecksMaskForGroup("all"))
56-
.Case(
57-
"batch_0",
58-
LangOptions::getBoundsSafetyNewChecksMaskForGroup("batch_0"))
151+
.Case("all", AllChecks)
152+
.Case("batch_0", Batch0Checks)
59153
.Case("none", LangOptions::BS_CHK_None)
60154
.Default(std::nullopt);
155+
61156
if (!Mask) {
62157
if (Diags)
63158
Diags->Report(diag::err_drv_invalid_value)
64159
<< A->getSpelling() << Value;
160+
Errors = true;
65161
break;
66162
}
67163

@@ -81,6 +177,7 @@ ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
81177
<< A->getSpelling() << Value
82178
<< (IsPosFlag ? "-fno-bounds-safety-bringup-missing-checks"
83179
: "-fbounds-safety-bringup-missing-checks");
180+
Errors = true;
84181
break;
85182
}
86183

@@ -91,8 +188,25 @@ ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
91188
OPT_fno_bounds_safety_bringup_missing_checks_EQ));
92189
Result &= ~(*Mask);
93190
}
191+
192+
// Update which checks have been explicitly disabled. E.g.
193+
// `-fno-bounds-safety-bringup-missing-checks=access_size`.
194+
if (llvm::has_single_bit(*Mask)) {
195+
// A single check was enabled/disabled
196+
if (IsPosFlag)
197+
IndividualChecksExplicitlyDisabled &= ~(*Mask);
198+
else
199+
IndividualChecksExplicitlyDisabled |= *Mask;
200+
} else {
201+
// A batch of checks were enabled/disabled. Any checks in that batch
202+
// are no longer explicitly set.
203+
IndividualChecksExplicitlyDisabled &= ~(*Mask);
204+
}
94205
}
95206
}
207+
if (DiagnoseMissingChecks && Diags && !Errors)
208+
DiagnoseDisabledBoundsSafetyChecks(Result, Diags,
209+
IndividualChecksExplicitlyDisabled);
96210
return Result;
97211
}
98212

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "clang/Basic/Version.h"
2626
#include "clang/Config/config.h"
2727
#include "clang/Driver/Action.h"
28+
#include "clang/Driver/BoundsSafetyArgs.h" // TO_UPSTREAM(BoundsSafety)
2829
#include "clang/Driver/CommonArgs.h"
2930
#include "clang/Driver/Distro.h"
3031
#include "clang/Driver/InputInfo.h"
@@ -7603,6 +7604,15 @@ void Clang::ConstructJob(Compilation &C, const JobAction &Job,
76037604
Args.addAllArgs(
76047605
CmdArgs, {options::OPT_fbounds_safety_bringup_missing_checks_EQ,
76057606
options::OPT_fno_bounds_safety_bringup_missing_checks_EQ});
7607+
7608+
// Validate the `-fbounds-safety-bringup-missing-checks` flags and emit
7609+
// warnings for disabled checks. We do this here because if we emit
7610+
// warnings in CC1 (where `ParseBoundsSafetyNewChecksMaskFromArgs` is also
7611+
// called) they cannot be suppressed and ignore `-Werror`
7612+
// (rdar://152730261).
7613+
(void)ParseBoundsSafetyNewChecksMaskFromArgs(
7614+
Args, &D.getDiags(),
7615+
/*DiagnoseMissingChecks=*/true);
76067616
}
76077617
}
76087618

clang/lib/Driver/ToolChains/Darwin.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1336,7 +1336,8 @@ void DarwinClang::addClangTargetOptions(
13361336
options::OPT_fno_bounds_safety, false)) {
13371337
LangOptions::BoundsSafetyNewChecksMaskIntTy NewChecks =
13381338
ParseBoundsSafetyNewChecksMaskFromArgs(DriverArgs,
1339-
/*DiagnosticsEngine=*/nullptr);
1339+
/*DiagnosticsEngine=*/nullptr,
1340+
/*DiagnoseMissingChecks=*/false);
13401341
if (NewChecks & LangOptions::BS_CHK_LibCAttributes) {
13411342
bool TargetWithoutUserspaceLibc = false;
13421343
if (getTriple().isOSDarwin() && !TargetWithoutUserspaceLibc) {

clang/lib/Frontend/CompilerInvocation.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4575,9 +4575,13 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args,
45754575
Args.hasFlag(OPT_ftrigraphs, OPT_fno_trigraphs, Opts.Trigraphs);
45764576

45774577
/*TO_UPSTREAM(BoundsSafety) ON*/
4578-
// Parse the enabled checks and emit any necessary diagnostics
4578+
// Parse the enabled checks and emit any necessary diagnostics.
4579+
// We do not diagnose missing checks here because warnings emitted in this
4580+
// context cannot be surpressed and don't respect `-Werror`
4581+
// (rdar://152730261). Instead we warn about missing checks in the driver.
45794582
Opts.BoundsSafetyBringUpMissingChecks =
4580-
ParseBoundsSafetyNewChecksMaskFromArgs(Args, &Diags);
4583+
ParseBoundsSafetyNewChecksMaskFromArgs(Args, &Diags,
4584+
/*DiagnoseMissingChecks=*/false);
45814585

45824586
// -fbounds-safety should be automatically marshalled into `Opts` in
45834587
// GenerateFrontendArgs() via `LangOpts<"BoundsSafety">` on

0 commit comments

Comments
 (0)