Skip to content

Commit d1a4b06

Browse files
committed
[analyzer] Emit an error for invalid -analyzer-config inputs
Differential Revision: https://reviews.llvm.org/D53280 llvm-svn: 348038
1 parent 81a1a8e commit d1a4b06

File tree

6 files changed

+175
-17
lines changed

6 files changed

+175
-17
lines changed

clang/include/clang/Basic/DiagnosticDriverKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,9 @@ def err_analyzer_config_no_value : Error<
299299
"analyzer-config option '%0' has a key but no value">;
300300
def err_analyzer_config_multiple_values : Error<
301301
"analyzer-config option '%0' should contain only one '='">;
302+
def err_analyzer_config_invalid_input : Error<
303+
"invalid input for analyzer-config option '%0', that expects %1 value">;
304+
def err_analyzer_config_unknown : Error<"unknown analyzer-config '%0'">;
302305

303306
def err_drv_invalid_hvx_length : Error<
304307
"-mhvx-length is not supported without a -mhvx/-mhvx= flag">;

clang/include/clang/Driver/CC1Options.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ def analyzer_list_enabled_checkers : Flag<["-"], "analyzer-list-enabled-checkers
138138
def analyzer_config : Separate<["-"], "analyzer-config">,
139139
HelpText<"Choose analyzer options to enable">;
140140

141+
def analyzer_config_compatibility_mode : Separate<["-"], "analyzer-config-compatibility-mode">,
142+
HelpText<"Don't emit errors on invalid analyzer-config inputs">;
143+
144+
def analyzer_config_compatibility_mode_EQ : Joined<["-"], "analyzer-config-compatibility-mode=">,
145+
Alias<analyzer_config_compatibility_mode>;
146+
141147
//===----------------------------------------------------------------------===//
142148
// Migrator Options
143149
//===----------------------------------------------------------------------===//

clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ class AnalyzerOptions : public RefCountedBase<AnalyzerOptions> {
200200
unsigned ShowCheckerHelp : 1;
201201
unsigned ShowEnabledCheckerList : 1;
202202
unsigned ShowConfigOptionsList : 1;
203+
unsigned ShouldEmitErrorsOnInvalidConfigValue : 1;
203204
unsigned AnalyzeAll : 1;
204205
unsigned AnalyzerDisplayProgress : 1;
205206
unsigned AnalyzeNestedBlocks : 1;
@@ -222,6 +223,7 @@ class AnalyzerOptions : public RefCountedBase<AnalyzerOptions> {
222223
/// The mode of function selection used during inlining.
223224
AnalysisInliningMode InliningMode = NoRedundancy;
224225

226+
// Create a field for each -analyzer-config option.
225227
#define ANALYZER_OPTION_DEPENDS_ON_USER_MODE(TYPE, NAME, CMDFLAG, DESC, \
226228
SHALLOW_VAL, DEEP_VAL) \
227229
ANALYZER_OPTION(TYPE, NAME, CMDFLAG, DESC, SHALLOW_VAL)
@@ -233,13 +235,39 @@ class AnalyzerOptions : public RefCountedBase<AnalyzerOptions> {
233235
#undef ANALYZER_OPTION
234236
#undef ANALYZER_OPTION_DEPENDS_ON_USER_MODE
235237

238+
// Create an array of all -analyzer-config command line options. Sort it in
239+
// the constructor.
240+
std::vector<StringRef> AnalyzerConfigCmdFlags = {
241+
#define ANALYZER_OPTION_DEPENDS_ON_USER_MODE(TYPE, NAME, CMDFLAG, DESC, \
242+
SHALLOW_VAL, DEEP_VAL) \
243+
ANALYZER_OPTION(TYPE, NAME, CMDFLAG, DESC, SHALLOW_VAL)
244+
245+
#define ANALYZER_OPTION(TYPE, NAME, CMDFLAG, DESC, DEFAULT_VAL) \
246+
CMDFLAG,
247+
248+
#include "clang/StaticAnalyzer/Core/AnalyzerOptions.def"
249+
#undef ANALYZER_OPTION
250+
#undef ANALYZER_OPTION_DEPENDS_ON_USER_MODE
251+
};
252+
253+
bool isUnknownAnalyzerConfig(StringRef Name) const {
254+
255+
assert(std::is_sorted(AnalyzerConfigCmdFlags.begin(),
256+
AnalyzerConfigCmdFlags.end()));
257+
258+
return !std::binary_search(AnalyzerConfigCmdFlags.begin(),
259+
AnalyzerConfigCmdFlags.end(), Name);
260+
}
261+
236262
AnalyzerOptions()
237263
: DisableAllChecks(false), ShowCheckerHelp(false),
238264
ShowEnabledCheckerList(false), ShowConfigOptionsList(false),
239265
AnalyzeAll(false), AnalyzerDisplayProgress(false),
240266
AnalyzeNestedBlocks(false), eagerlyAssumeBinOpBifurcation(false),
241267
TrimGraph(false), visualizeExplodedGraphWithGraphViz(false),
242-
UnoptimizedCFG(false), PrintStats(false), NoRetryExhausted(false) {}
268+
UnoptimizedCFG(false), PrintStats(false), NoRetryExhausted(false) {
269+
llvm::sort(AnalyzerConfigCmdFlags);
270+
}
243271

244272
/// Interprets an option's string value as a boolean. The "true" string is
245273
/// interpreted as true and the "false" string is interpreted as false.

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2368,6 +2368,9 @@ static void RenderAnalyzerOptions(const ArgList &Args, ArgStringList &CmdArgs,
23682368
// Treat blocks as analysis entry points.
23692369
CmdArgs.push_back("-analyzer-opt-analyze-nested-blocks");
23702370

2371+
// Enable compatilibily mode to avoid analyzer-config related errors.
2372+
CmdArgs.push_back("-analyzer-config-compatibility-mode=true");
2373+
23712374
// Add default argument set.
23722375
if (!Args.hasArg(options::OPT__analyzer_no_default_checks)) {
23732376
CmdArgs.push_back("-analyzer-checker=core");

clang/lib/Frontend/CompilerInvocation.cpp

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,10 @@ static void addDiagnosticArgs(ArgList &Args, OptSpecifier Group,
181181
}
182182
}
183183

184+
// Parse the Static Analyzer configuration. If \p Diags is set to nullptr,
185+
// it won't verify the input.
184186
static void parseAnalyzerConfigs(AnalyzerOptions &AnOpts,
185-
DiagnosticsEngine &Diags);
187+
DiagnosticsEngine *Diags);
186188

187189
static void getAllNoBuiltinFuncValues(ArgList &Args,
188190
std::vector<std::string> &Funcs) {
@@ -284,6 +286,12 @@ static bool ParseAnalyzerArgs(AnalyzerOptions &Opts, ArgList &Args,
284286
Opts.ShowCheckerHelp = Args.hasArg(OPT_analyzer_checker_help);
285287
Opts.ShowConfigOptionsList = Args.hasArg(OPT_analyzer_config_help);
286288
Opts.ShowEnabledCheckerList = Args.hasArg(OPT_analyzer_list_enabled_checkers);
289+
Opts.ShouldEmitErrorsOnInvalidConfigValue =
290+
/* negated */!llvm::StringSwitch<bool>(
291+
Args.getLastArgValue(OPT_analyzer_config_compatibility_mode))
292+
.Case("true", true)
293+
.Case("false", false)
294+
.Default(false);
287295
Opts.DisableAllChecks = Args.hasArg(OPT_analyzer_disable_all_checks);
288296

289297
Opts.visualizeExplodedGraphWithGraphViz =
@@ -320,7 +328,7 @@ static bool ParseAnalyzerArgs(AnalyzerOptions &Opts, ArgList &Args,
320328

321329
// Go through the analyzer configuration options.
322330
for (const auto *A : Args.filtered(OPT_analyzer_config)) {
323-
A->claim();
331+
324332
// We can have a list of comma separated config names, e.g:
325333
// '-analyzer-config key1=val1,key2=val2'
326334
StringRef configList = A->getValue();
@@ -342,11 +350,24 @@ static bool ParseAnalyzerArgs(AnalyzerOptions &Opts, ArgList &Args,
342350
Success = false;
343351
break;
344352
}
353+
354+
// TODO: Check checker options too, possibly in CheckerRegistry.
355+
// Leave unknown non-checker configs unclaimed.
356+
if (!key.contains(":") && Opts.isUnknownAnalyzerConfig(key)) {
357+
if (Opts.ShouldEmitErrorsOnInvalidConfigValue)
358+
Diags.Report(diag::err_analyzer_config_unknown) << key;
359+
continue;
360+
}
361+
362+
A->claim();
345363
Opts.Config[key] = val;
346364
}
347365
}
348366

349-
parseAnalyzerConfigs(Opts, Diags);
367+
if (Opts.ShouldEmitErrorsOnInvalidConfigValue)
368+
parseAnalyzerConfigs(Opts, &Diags);
369+
else
370+
parseAnalyzerConfigs(Opts, nullptr);
350371

351372
llvm::raw_string_ostream os(Opts.FullCompilerInvocation);
352373
for (unsigned i = 0; i < Args.getNumInputArgStrings(); ++i) {
@@ -365,56 +386,82 @@ static StringRef getStringOption(AnalyzerOptions::ConfigTable &Config,
365386
}
366387

367388
static void initOption(AnalyzerOptions::ConfigTable &Config,
389+
DiagnosticsEngine *Diags,
368390
StringRef &OptionField, StringRef Name,
369391
StringRef DefaultVal) {
392+
// String options may be known to invalid (e.g. if the expected string is a
393+
// file name, but the file does not exist), those will have to be checked in
394+
// parseConfigs.
370395
OptionField = getStringOption(Config, Name, DefaultVal);
371396
}
372397

373398
static void initOption(AnalyzerOptions::ConfigTable &Config,
399+
DiagnosticsEngine *Diags,
374400
bool &OptionField, StringRef Name, bool DefaultVal) {
375-
// FIXME: We should emit a warning here if the value is something other than
376-
// "true", "false", or the empty string (meaning the default value),
377-
// but the AnalyzerOptions doesn't have access to a diagnostic engine.
378-
OptionField = llvm::StringSwitch<bool>(getStringOption(Config, Name,
379-
(DefaultVal ? "true" : "false")))
401+
auto PossiblyInvalidVal = llvm::StringSwitch<Optional<bool>>(
402+
getStringOption(Config, Name, (DefaultVal ? "true" : "false")))
380403
.Case("true", true)
381404
.Case("false", false)
382-
.Default(DefaultVal);
405+
.Default(None);
406+
407+
if (!PossiblyInvalidVal) {
408+
if (Diags)
409+
Diags->Report(diag::err_analyzer_config_invalid_input)
410+
<< Name << "a boolean";
411+
else
412+
OptionField = DefaultVal;
413+
} else
414+
OptionField = PossiblyInvalidVal.getValue();
383415
}
384416

385417
static void initOption(AnalyzerOptions::ConfigTable &Config,
418+
DiagnosticsEngine *Diags,
386419
unsigned &OptionField, StringRef Name,
387420
unsigned DefaultVal) {
421+
388422
OptionField = DefaultVal;
389423
bool HasFailed = getStringOption(Config, Name, std::to_string(DefaultVal))
390424
.getAsInteger(10, OptionField);
391-
assert(!HasFailed && "analyzer-config option should be numeric");
392-
(void)HasFailed;
425+
if (Diags && HasFailed)
426+
Diags->Report(diag::err_analyzer_config_invalid_input)
427+
<< Name << "an unsigned";
393428
}
394429

395430
static void parseAnalyzerConfigs(AnalyzerOptions &AnOpts,
396-
DiagnosticsEngine &Diags) {
397-
// TODO: Emit warnings for incorrect options.
431+
DiagnosticsEngine *Diags) {
398432
// TODO: There's no need to store the entire configtable, it'd be plenty
399433
// enough tostore checker options.
400434

401435
#define ANALYZER_OPTION(TYPE, NAME, CMDFLAG, DESC, DEFAULT_VAL) \
402-
initOption(AnOpts.Config, AnOpts.NAME, CMDFLAG, DEFAULT_VAL); \
436+
initOption(AnOpts.Config, Diags, AnOpts.NAME, CMDFLAG, DEFAULT_VAL);
403437

404438
#define ANALYZER_OPTION_DEPENDS_ON_USER_MODE(TYPE, NAME, CMDFLAG, DESC, \
405439
SHALLOW_VAL, DEEP_VAL) \
406440
switch (AnOpts.getUserMode()) { \
407441
case UMK_Shallow: \
408-
initOption(AnOpts.Config, AnOpts.NAME, CMDFLAG, SHALLOW_VAL); \
442+
initOption(AnOpts.Config, Diags, AnOpts.NAME, CMDFLAG, SHALLOW_VAL); \
409443
break; \
410444
case UMK_Deep: \
411-
initOption(AnOpts.Config, AnOpts.NAME, CMDFLAG, DEEP_VAL); \
445+
initOption(AnOpts.Config, Diags, AnOpts.NAME, CMDFLAG, DEEP_VAL); \
412446
break; \
413447
} \
414448

415449
#include "clang/StaticAnalyzer/Core/AnalyzerOptions.def"
416450
#undef ANALYZER_OPTION
417451
#undef ANALYZER_OPTION_DEPENDS_ON_USER_MODE
452+
453+
// At this point, AnalyzerOptions is configured. Let's validate some options.
454+
455+
if (!Diags)
456+
return;
457+
458+
if (!AnOpts.CTUDir.empty() && !llvm::sys::fs::is_directory(AnOpts.CTUDir))
459+
Diags->Report(diag::err_analyzer_config_invalid_input)
460+
<< "ctu-dir" << "a filename";
461+
462+
if (!AnOpts.CTUDir.empty() && !llvm::sys::fs::is_directory(AnOpts.CTUDir))
463+
Diags->Report(diag::err_analyzer_config_invalid_input)
464+
<< "model-path" << "a filename";
418465
}
419466

420467
static bool ParseMigratorArgs(MigratorOptions &Opts, ArgList &Args) {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// RUN: not %clang_analyze_cc1 -verify %s \
2+
// RUN: -analyzer-checker=core \
3+
// RUN: -analyzer-config notes-as-events=yesplease \
4+
// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-BOOL-INPUT
5+
6+
// CHECK-BOOL-INPUT: (frontend): invalid input for analyzer-config option
7+
// CHECK-BOOL-INPUT-SAME: 'notes-as-events', that expects a boolean value
8+
9+
// RUN: %clang_analyze_cc1 -verify %s \
10+
// RUN: -analyzer-checker=core \
11+
// RUN: -analyzer-config-compatibility-mode=true \
12+
// RUN: -analyzer-config notes-as-events=yesplease
13+
14+
15+
// RUN: not %clang_analyze_cc1 -verify %s \
16+
// RUN: -analyzer-checker=core \
17+
// RUN: -analyzer-config max-inlinable-size=400km/h \
18+
// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-UINT-INPUT
19+
20+
// CHECK-UINT-INPUT: (frontend): invalid input for analyzer-config option
21+
// CHECK-UINT-INPUT-SAME: 'max-inlinable-size', that expects an unsigned
22+
// CHECK-UINT-INPUT-SAME: value
23+
24+
// RUN: %clang_analyze_cc1 -verify %s \
25+
// RUN: -analyzer-checker=core \
26+
// RUN: -analyzer-config-compatibility-mode=true \
27+
// RUN: -analyzer-config max-inlinable-size=400km/h
28+
29+
30+
// RUN: not %clang_analyze_cc1 -verify %s \
31+
// RUN: -analyzer-checker=core \
32+
// RUN: -analyzer-config ctu-dir=0123012301230123 \
33+
// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-FILENAME-INPUT
34+
35+
// CHECK-FILENAME-INPUT: (frontend): invalid input for analyzer-config option
36+
// CHECK-FILENAME-INPUT-SAME: 'ctu-dir', that expects a filename
37+
// CHECK-FILENAME-INPUT-SAME: value
38+
39+
// RUN: %clang_analyze_cc1 -verify %s \
40+
// RUN: -analyzer-checker=core \
41+
// RUN: -analyzer-config-compatibility-mode=true \
42+
// RUN: -analyzer-config ctu-dir=0123012301230123
43+
44+
45+
// RUN: not %clang_analyze_cc1 -verify %s \
46+
// RUN: -analyzer-checker=core \
47+
// RUN: -analyzer-config no-false-positives=true \
48+
// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-UNKNOWN-CFG
49+
50+
// CHECK-UNKNOWN-CFG: (frontend): unknown analyzer-config 'no-false-positives'
51+
52+
// RUN: %clang_analyze_cc1 -verify %s \
53+
// RUN: -analyzer-checker=core \
54+
// RUN: -analyzer-config-compatibility-mode=true \
55+
// RUN: -analyzer-config no-false-positives=true
56+
57+
58+
// Test the driver properly using "analyzer-config-compatibility-mode=true",
59+
// no longer causing an error on input error.
60+
// RUN: %clang --analyze %s
61+
62+
// RUN: not %clang --analyze %s \
63+
// RUN: -Xclang -analyzer-config -Xclang no-false-positives=true \
64+
// RUN: -Xclang -analyzer-config-compatibility-mode=false \
65+
// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-NO-COMPAT
66+
67+
// CHECK-NO-COMPAT: error: unknown analyzer-config 'no-false-positives'
68+
69+
// expected-no-diagnostics
70+
71+
int main() {}

0 commit comments

Comments
 (0)