7
7
// ===----------------------------------------------------------------------===//
8
8
#include " clang/Driver/BoundsSafetyArgs.h"
9
9
#include " clang/Basic/DiagnosticDriver.h"
10
+ #include " clang/Basic/DiagnosticFrontend.h"
10
11
#include " clang/Driver/Options.h"
12
+ #include " llvm/ADT/StringSet.h"
13
+ #include " llvm/ADT/bit.h"
14
+ #include < array>
11
15
12
16
using namespace llvm ::opt;
13
17
using namespace clang ::driver::options;
@@ -16,15 +20,103 @@ namespace clang {
16
20
17
21
namespace driver {
18
22
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
+
19
103
LangOptions::BoundsSafetyNewChecksMaskIntTy
20
104
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;
22
111
auto FilteredArgs =
23
112
Args.filtered (OPT_fbounds_safety_bringup_missing_checks_EQ,
24
113
OPT_fno_bounds_safety_bringup_missing_checks_EQ);
25
114
if (FilteredArgs.empty ()) {
26
115
// No flags present. Use the default
27
- return LangOptions::getDefaultBoundsSafetyNewChecksMask ();
116
+ auto Result = LangOptions::getDefaultBoundsSafetyNewChecksMask ();
117
+ DiagnoseDisabledBoundsSafetyChecks (Result, Diags,
118
+ IndividualChecksExplicitlyDisabled);
119
+ return Result;
28
120
}
29
121
30
122
// 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,
35
127
// All the options will be applied as masks in the command line order, to make
36
128
// it easier to enable all but certain checks (or disable all but certain
37
129
// checks).
130
+ const auto Batch0Checks =
131
+ LangOptions::getBoundsSafetyNewChecksMaskForGroup (" batch_0" );
132
+ const auto AllChecks =
133
+ LangOptions::getBoundsSafetyNewChecksMaskForGroup (" all" );
134
+ bool Errors = false ;
38
135
for (const Arg *A : FilteredArgs) {
39
136
for (const char *Value : A->getValues ()) {
40
137
std::optional<LangOptions::BoundsSafetyNewChecksMaskIntTy> Mask =
@@ -51,17 +148,16 @@ ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
51
148
.Case (" libc_attributes" , LangOptions::BS_CHK_LibCAttributes)
52
149
.Case (" array_subscript_agg" ,
53
150
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)
59
153
.Case (" none" , LangOptions::BS_CHK_None)
60
154
.Default (std::nullopt);
155
+
61
156
if (!Mask) {
62
157
if (Diags)
63
158
Diags->Report (diag::err_drv_invalid_value)
64
159
<< A->getSpelling () << Value;
160
+ Errors = true ;
65
161
break ;
66
162
}
67
163
@@ -81,6 +177,7 @@ ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
81
177
<< A->getSpelling () << Value
82
178
<< (IsPosFlag ? " -fno-bounds-safety-bringup-missing-checks"
83
179
: " -fbounds-safety-bringup-missing-checks" );
180
+ Errors = true ;
84
181
break ;
85
182
}
86
183
@@ -91,8 +188,25 @@ ParseBoundsSafetyNewChecksMaskFromArgs(const llvm::opt::ArgList &Args,
91
188
OPT_fno_bounds_safety_bringup_missing_checks_EQ));
92
189
Result &= ~(*Mask);
93
190
}
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
+ }
94
205
}
95
206
}
207
+ if (DiagnoseMissingChecks && Diags && !Errors)
208
+ DiagnoseDisabledBoundsSafetyChecks (Result, Diags,
209
+ IndividualChecksExplicitlyDisabled);
96
210
return Result;
97
211
}
98
212
0 commit comments