Skip to content

Commit 6f659b0

Browse files
authored
[clang][dataflow] For bugprone-unchecked-optional-access report range (llvm#131055)
Report the range in diagnostics, in addition to the location in case the range helps disambiguate a little in chained `->` expressions. ``` b->a->f->x = 1; ^~~~~~~ ``` instead of just: ``` b->a->f->x = 1; ^ ``` As a followup we should probably also report the location/range of an `->` if that operator is used. Like: ``` b->a->f->x = 1; ^~ ```
1 parent d2e1e30 commit 6f659b0

File tree

4 files changed

+79
-22
lines changed

4 files changed

+79
-22
lines changed

clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
namespace clang::tidy::bugprone {
2020
using ast_matchers::MatchFinder;
2121
using dataflow::UncheckedOptionalAccessDiagnoser;
22+
using dataflow::UncheckedOptionalAccessDiagnostic;
2223
using dataflow::UncheckedOptionalAccessModel;
2324

2425
static constexpr llvm::StringLiteral FuncID("fun");
@@ -52,14 +53,16 @@ void UncheckedOptionalAccessCheck::check(
5253
UncheckedOptionalAccessDiagnoser Diagnoser(ModelOptions);
5354
// FIXME: Allow user to set the (defaulted) SAT iterations max for
5455
// `diagnoseFunction` with config options.
55-
if (llvm::Expected<llvm::SmallVector<SourceLocation>> Locs =
56-
dataflow::diagnoseFunction<UncheckedOptionalAccessModel,
57-
SourceLocation>(*FuncDecl, *Result.Context,
58-
Diagnoser))
59-
for (const SourceLocation &Loc : *Locs)
60-
diag(Loc, "unchecked access to optional value");
56+
if (llvm::Expected<llvm::SmallVector<UncheckedOptionalAccessDiagnostic>>
57+
Diags = dataflow::diagnoseFunction<UncheckedOptionalAccessModel,
58+
UncheckedOptionalAccessDiagnostic>(
59+
*FuncDecl, *Result.Context, Diagnoser))
60+
for (const UncheckedOptionalAccessDiagnostic &Diag : *Diags) {
61+
diag(Diag.Range.getBegin(), "unchecked access to optional value")
62+
<< Diag.Range;
63+
}
6164
else
62-
llvm::consumeError(Locs.takeError());
65+
llvm::consumeError(Diags.takeError());
6366
}
6467

6568
} // namespace clang::tidy::bugprone

clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "clang/Analysis/FlowSensitive/CachedConstAccessorsLattice.h"
2121
#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
2222
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
23+
#include "clang/Analysis/FlowSensitive/MatchSwitch.h"
2324
#include "clang/Analysis/FlowSensitive/NoopLattice.h"
2425
#include "clang/Basic/SourceLocation.h"
2526
#include "llvm/ADT/SmallVector.h"
@@ -71,20 +72,26 @@ class UncheckedOptionalAccessModel
7172
TransferMatchSwitch;
7273
};
7374

75+
/// Diagnostic information for an unchecked optional access.
76+
struct UncheckedOptionalAccessDiagnostic {
77+
CharSourceRange Range;
78+
};
79+
7480
class UncheckedOptionalAccessDiagnoser {
7581
public:
7682
UncheckedOptionalAccessDiagnoser(
7783
UncheckedOptionalAccessModelOptions Options = {});
7884

79-
llvm::SmallVector<SourceLocation>
85+
llvm::SmallVector<UncheckedOptionalAccessDiagnostic>
8086
operator()(const CFGElement &Elt, ASTContext &Ctx,
8187
const TransferStateForDiagnostics<UncheckedOptionalAccessLattice>
8288
&State) {
8389
return DiagnoseMatchSwitch(Elt, Ctx, State.Env);
8490
}
8591

8692
private:
87-
CFGMatchSwitch<const Environment, llvm::SmallVector<SourceLocation>>
93+
CFGMatchSwitch<const Environment,
94+
llvm::SmallVector<UncheckedOptionalAccessDiagnostic>>
8895
DiagnoseMatchSwitch;
8996
};
9097

clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,8 +1120,8 @@ auto buildTransferMatchSwitch() {
11201120
.Build();
11211121
}
11221122

1123-
llvm::SmallVector<SourceLocation> diagnoseUnwrapCall(const Expr *ObjectExpr,
1124-
const Environment &Env) {
1123+
llvm::SmallVector<UncheckedOptionalAccessDiagnostic>
1124+
diagnoseUnwrapCall(const Expr *ObjectExpr, const Environment &Env) {
11251125
if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(
11261126
getLocBehindPossiblePointer(*ObjectExpr, Env))) {
11271127
auto *Prop = Env.getValue(locForHasValue(*OptionalLoc));
@@ -1132,9 +1132,9 @@ llvm::SmallVector<SourceLocation> diagnoseUnwrapCall(const Expr *ObjectExpr,
11321132
}
11331133

11341134
// Record that this unwrap is *not* provably safe.
1135-
// FIXME: include either the name of the optional (if applicable) or a source
1136-
// range of the access for easier interpretation of the result.
1137-
return {ObjectExpr->getBeginLoc()};
1135+
// FIXME: include the name of the optional (if applicable).
1136+
auto Range = CharSourceRange::getTokenRange(ObjectExpr->getSourceRange());
1137+
return {UncheckedOptionalAccessDiagnostic{Range}};
11381138
}
11391139

11401140
auto buildDiagnoseMatchSwitch(
@@ -1143,8 +1143,9 @@ auto buildDiagnoseMatchSwitch(
11431143
// lot of duplicated work (e.g. string comparisons), consider providing APIs
11441144
// that avoid it through memoization.
11451145
auto IgnorableOptional = ignorableOptional(Options);
1146-
return CFGMatchSwitchBuilder<const Environment,
1147-
llvm::SmallVector<SourceLocation>>()
1146+
return CFGMatchSwitchBuilder<
1147+
const Environment,
1148+
llvm::SmallVector<UncheckedOptionalAccessDiagnostic>>()
11481149
// optional::value
11491150
.CaseOfCFGStmt<CXXMemberCallExpr>(
11501151
valueCall(IgnorableOptional),

clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "clang/Basic/SourceLocation.h"
1515
#include "clang/Frontend/TextDiagnostic.h"
1616
#include "clang/Tooling/Tooling.h"
17+
#include "llvm/ADT/DenseMap.h"
1718
#include "llvm/ADT/DenseSet.h"
1819
#include "llvm/ADT/STLExtras.h"
1920
#include "llvm/Support/Error.h"
@@ -1282,6 +1283,14 @@ static raw_ostream &operator<<(raw_ostream &OS,
12821283
class UncheckedOptionalAccessTest
12831284
: public ::testing::TestWithParam<OptionalTypeIdentifier> {
12841285
protected:
1286+
// Check that after running the analysis on SourceCode, it produces the
1287+
// expected diagnostics according to [[unsafe]] annotations.
1288+
// - No annotations => no diagnostics.
1289+
// - Given "// [[unsafe]]" annotations on a line, we expect a diagnostic on
1290+
// that line.
1291+
// - Given "// [[unsafe:range_text]]" annotations on a line, we expect a
1292+
// diagnostic on that line, and we expect the diagnostic Range (printed as
1293+
// a string) to match the "range_text".
12851294
void ExpectDiagnosticsFor(std::string SourceCode,
12861295
bool IgnoreSmartPointerDereference = true) {
12871296
ExpectDiagnosticsFor(SourceCode, ast_matchers::hasName("target"),
@@ -1336,7 +1345,7 @@ class UncheckedOptionalAccessTest
13361345
T Make();
13371346
)");
13381347
UncheckedOptionalAccessModelOptions Options{IgnoreSmartPointerDereference};
1339-
std::vector<SourceLocation> Diagnostics;
1348+
std::vector<UncheckedOptionalAccessDiagnostic> Diagnostics;
13401349
llvm::Error Error = checkDataflow<UncheckedOptionalAccessModel>(
13411350
AnalysisInputs<UncheckedOptionalAccessModel>(
13421351
SourceCode, std::move(FuncMatcher),
@@ -1364,22 +1373,34 @@ class UncheckedOptionalAccessTest
13641373
&Annotations,
13651374
const AnalysisOutputs &AO) {
13661375
llvm::DenseSet<unsigned> AnnotationLines;
1367-
for (const auto &[Line, _] : Annotations) {
1376+
llvm::DenseMap<unsigned, std::string> AnnotationRangesInLines;
1377+
for (const auto &[Line, AnnotationWithMaybeRange] : Annotations) {
13681378
AnnotationLines.insert(Line);
1379+
auto it = AnnotationWithMaybeRange.find(':');
1380+
if (it != std::string::npos) {
1381+
AnnotationRangesInLines[Line] =
1382+
AnnotationWithMaybeRange.substr(it + 1);
1383+
}
13691384
}
13701385
auto &SrcMgr = AO.ASTCtx.getSourceManager();
13711386
llvm::DenseSet<unsigned> DiagnosticLines;
1372-
for (SourceLocation &Loc : Diagnostics) {
1373-
unsigned Line = SrcMgr.getPresumedLineNumber(Loc);
1387+
for (const UncheckedOptionalAccessDiagnostic &Diag : Diagnostics) {
1388+
unsigned Line = SrcMgr.getPresumedLineNumber(Diag.Range.getBegin());
13741389
DiagnosticLines.insert(Line);
13751390
if (!AnnotationLines.contains(Line)) {
13761391
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(
13771392
new DiagnosticOptions());
13781393
TextDiagnostic TD(llvm::errs(), AO.ASTCtx.getLangOpts(),
13791394
DiagOpts.get());
1380-
TD.emitDiagnostic(FullSourceLoc(Loc, SrcMgr),
1395+
TD.emitDiagnostic(FullSourceLoc(Diag.Range.getBegin(), SrcMgr),
13811396
DiagnosticsEngine::Error,
1382-
"unexpected diagnostic", {}, {});
1397+
"unexpected diagnostic", {Diag.Range}, {});
1398+
} else {
1399+
auto it = AnnotationRangesInLines.find(Line);
1400+
if (it != AnnotationRangesInLines.end()) {
1401+
EXPECT_EQ(Diag.Range.getAsRange().printToString(SrcMgr),
1402+
it->second);
1403+
}
13831404
}
13841405
}
13851406

@@ -4085,6 +4106,31 @@ TEST_P(UncheckedOptionalAccessTest, ConstPointerRefAccessor) {
40854106
/*IgnoreSmartPointerDereference=*/false);
40864107
}
40874108

4109+
TEST_P(UncheckedOptionalAccessTest, DiagnosticsHaveRanges) {
4110+
ExpectDiagnosticsFor(R"cc(
4111+
#include "unchecked_optional_access_test.h"
4112+
4113+
struct A {
4114+
$ns::$optional<int> fi;
4115+
};
4116+
struct B {
4117+
$ns::$optional<A> fa;
4118+
};
4119+
4120+
void target($ns::$optional<B> opt) {
4121+
opt.value(); // [[unsafe:<input.cc:12:7>]]
4122+
if (opt) {
4123+
opt // [[unsafe:<input.cc:14:9, line:16:13>]]
4124+
->
4125+
fa.value();
4126+
if (opt->fa) {
4127+
opt->fa->fi.value(); // [[unsafe:<input.cc:18:11, col:20>]]
4128+
}
4129+
}
4130+
}
4131+
)cc");
4132+
}
4133+
40884134
// FIXME: Add support for:
40894135
// - constructors (copy, move)
40904136
// - assignment operators (default, copy, move)

0 commit comments

Comments
 (0)