Skip to content

Commit 0aab1a8

Browse files
committed
[BoundsSafety][Attempt 2] Add warning diagnostics for uses of legacy bounds checks
** This is the second attempt landing this patch. This first (#10800 4c8f6e7) failed because there was a bug. ** The bug was in `ParseBoundsSafetyNewChecksMaskFromArgs` where the call to `DiagnoseDisabledBoundsSafetyChecks` was not guarded with `DiagnoseMissingChecks`. This missing check caused diagnostics to appear when they shouldn't and caused crashes in cases where the `Diags` pointer was nullptr. To fix this the missing guard has been added and an assert that `Diags` is not null has been added. Unfortunately it isn't possible to write a regression test for this because the behavior depends on having at least some bounds checks disabled by default, which is not desirable behavior. While techinically we could add a hidden flag to change the default for the purposes of testing the added complexity of doing this doesn't really justify the added test coverage. This version of the patch also guards assigning to `Opts.BoundsSafetyBringUpMissingChecks` in `CompilerInvocation::ParseLangArgs` so that it is only assigned to when `-fbounds-safety` is enabled. This is done for several reasons * It avoids unnecessarily parsing the `-fbounds-safety-bringup-missing-checks=` flags when `-fbounds-safety` is disabled. * Isolates code built without `-fbounds-safety` from bugs in `ParseBoundsSafetyNewChecksMaskFromArgs`. * It is more consistent with `CompilerInvocationBase::GenerateLangArgs` which only emits `-fbounds-safety-bring-checks=` when `-fbounds-safety` is enabled. --- 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 08b69a8 commit 0aab1a8

File tree

10 files changed

+471
-17
lines changed

10 files changed

+471
-17
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
@@ -1679,6 +1679,8 @@ def BoundsSafetyStrictTerminatedByCast
16791679
: DiagGroup<"bounds-safety-strict-terminated-by-cast">;
16801680
def BoundsSafetyCountAttrArithConstantCount :
16811681
DiagGroup<"bounds-safety-externally-counted-ptr-arith-constant-count">;
1682+
def BoundsSafetyLegacyChecksEnabled :
1683+
DiagGroup<"bounds-safety-legacy-checks-enabled">;
16821684
// TO_UPSTREAM(BoundsSafety) OFF
16831685

16841686
// -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
@@ -471,6 +471,8 @@ class LangOptionsBase {
471471
BS_CHK_CompoundLiteralInit = 1 << 4, // rdar://110871666
472472
BS_CHK_LibCAttributes = 1 << 5, // rdar://84733153
473473
BS_CHK_ArraySubscriptAgg = 1 << 6, // rdar://145020583
474+
475+
BS_CHK_MaxMask = BS_CHK_ArraySubscriptAgg,
474476
};
475477
using BoundsSafetyNewChecksMaskIntTy =
476478
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: 123 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,105 @@ namespace clang {
1620

1721
namespace driver {
1822

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

30124
// If flags are present then start with BS_CHK_None as the initial mask and
@@ -35,6 +129,11 @@ ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
35129
// All the options will be applied as masks in the command line order, to make
36130
// it easier to enable all but certain checks (or disable all but certain
37131
// checks).
132+
const auto Batch0Checks =
133+
LangOptions::getBoundsSafetyNewChecksMaskForGroup("batch_0");
134+
const auto AllChecks =
135+
LangOptions::getBoundsSafetyNewChecksMaskForGroup("all");
136+
bool Errors = false;
38137
for (const Arg *A : FilteredArgs) {
39138
for (const char *Value : A->getValues()) {
40139
std::optional<LangOptions::BoundsSafetyNewChecksMaskIntTy> Mask =
@@ -51,17 +150,16 @@ ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
51150
.Case("libc_attributes", LangOptions::BS_CHK_LibCAttributes)
52151
.Case("array_subscript_agg",
53152
LangOptions::BS_CHK_ArraySubscriptAgg)
54-
.Case("all",
55-
LangOptions::getBoundsSafetyNewChecksMaskForGroup("all"))
56-
.Case(
57-
"batch_0",
58-
LangOptions::getBoundsSafetyNewChecksMaskForGroup("batch_0"))
153+
.Case("all", AllChecks)
154+
.Case("batch_0", Batch0Checks)
59155
.Case("none", LangOptions::BS_CHK_None)
60156
.Default(std::nullopt);
157+
61158
if (!Mask) {
62159
if (Diags)
63160
Diags->Report(diag::err_drv_invalid_value)
64161
<< A->getSpelling() << Value;
162+
Errors = true;
65163
break;
66164
}
67165

@@ -81,6 +179,7 @@ ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
81179
<< A->getSpelling() << Value
82180
<< (IsPosFlag ? "-fno-bounds-safety-bringup-missing-checks"
83181
: "-fbounds-safety-bringup-missing-checks");
182+
Errors = true;
84183
break;
85184
}
86185

@@ -91,8 +190,25 @@ ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
91190
OPT_fno_bounds_safety_bringup_missing_checks_EQ));
92191
Result &= ~(*Mask);
93192
}
193+
194+
// Update which checks have been explicitly disabled. E.g.
195+
// `-fno-bounds-safety-bringup-missing-checks=access_size`.
196+
if (llvm::has_single_bit(*Mask)) {
197+
// A single check was enabled/disabled
198+
if (IsPosFlag)
199+
IndividualChecksExplicitlyDisabled &= ~(*Mask);
200+
else
201+
IndividualChecksExplicitlyDisabled |= *Mask;
202+
} else {
203+
// A batch of checks were enabled/disabled. Any checks in that batch
204+
// are no longer explicitly set.
205+
IndividualChecksExplicitlyDisabled &= ~(*Mask);
206+
}
94207
}
95208
}
209+
if (DiagnoseMissingChecks && Diags && !Errors)
210+
DiagnoseDisabledBoundsSafetyChecks(Result, Diags,
211+
IndividualChecksExplicitlyDisabled);
96212
return Result;
97213
}
98214

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"
@@ -7641,6 +7642,15 @@ void Clang::ConstructJob(Compilation &C, const JobAction &Job,
76417642
Args.addAllArgs(
76427643
CmdArgs, {options::OPT_fbounds_safety_bringup_missing_checks_EQ,
76437644
options::OPT_fno_bounds_safety_bringup_missing_checks_EQ});
7645+
7646+
// Validate the `-fbounds-safety-bringup-missing-checks` flags and emit
7647+
// warnings for disabled checks. We do this here because if we emit
7648+
// warnings in CC1 (where `ParseBoundsSafetyNewChecksMaskFromArgs` is also
7649+
// called) they cannot be suppressed and ignore `-Werror`
7650+
// (rdar://152730261).
7651+
(void)ParseBoundsSafetyNewChecksMaskFromArgs(
7652+
Args, &D.getDiags(),
7653+
/*DiagnoseMissingChecks=*/true);
76447654
}
76457655
}
76467656

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: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4635,9 +4635,15 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args,
46354635
Args.hasFlag(OPT_ftrigraphs, OPT_fno_trigraphs, Opts.Trigraphs);
46364636

46374637
/*TO_UPSTREAM(BoundsSafety) ON*/
4638-
// Parse the enabled checks and emit any necessary diagnostics
4639-
Opts.BoundsSafetyBringUpMissingChecks =
4640-
ParseBoundsSafetyNewChecksMaskFromArgs(Args, &Diags);
4638+
if (Opts.BoundsSafety) {
4639+
// Parse the enabled checks and emit any necessary diagnostics.
4640+
// We do not diagnose missing checks here because warnings emitted in this
4641+
// context cannot be surpressed and don't respect `-Werror`
4642+
// (rdar://152730261). Instead we warn about missing checks in the driver.
4643+
Opts.BoundsSafetyBringUpMissingChecks =
4644+
ParseBoundsSafetyNewChecksMaskFromArgs(Args, &Diags,
4645+
/*DiagnoseMissingChecks=*/false);
4646+
}
46414647

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

0 commit comments

Comments
 (0)