Skip to content

Commit 704d175

Browse files
committed
[clang][dataflow] Add null-check after dereference checker
1 parent 7af4e8b commit 704d175

File tree

12 files changed

+1777
-1
lines changed

12 files changed

+1777
-1
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
#include "NoEscapeCheck.h"
4949
#include "NonZeroEnumToBoolConversionCheck.h"
5050
#include "NotNullTerminatedResultCheck.h"
51+
#include "NullCheckAfterDereferenceCheck.h"
5152
#include "OptionalValueConversionCheck.h"
5253
#include "ParentVirtualCallCheck.h"
5354
#include "PosixReturnCheck.h"
@@ -180,6 +181,8 @@ class BugproneModule : public ClangTidyModule {
180181
CheckFactories.registerCheck<PosixReturnCheck>("bugprone-posix-return");
181182
CheckFactories.registerCheck<ReservedIdentifierCheck>(
182183
"bugprone-reserved-identifier");
184+
CheckFactories.registerCheck<NullCheckAfterDereferenceCheck>(
185+
"bugprone-null-check-after-dereference");
183186
CheckFactories.registerCheck<SharedPtrArrayMismatchCheck>(
184187
"bugprone-shared-ptr-array-mismatch");
185188
CheckFactories.registerCheck<SignalHandlerCheck>("bugprone-signal-handler");

clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ add_clang_library(clangTidyBugproneModule
4444
NoEscapeCheck.cpp
4545
NonZeroEnumToBoolConversionCheck.cpp
4646
NotNullTerminatedResultCheck.cpp
47+
NullCheckAfterDereferenceCheck.cpp
4748
OptionalValueConversionCheck.cpp
4849
ParentVirtualCallCheck.cpp
4950
PosixReturnCheck.cpp
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
//===--- NullCheckAfterDereferenceCheck.cpp - clang-tidy-------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "NullCheckAfterDereferenceCheck.h"
10+
#include "clang/AST/ASTContext.h"
11+
#include "clang/AST/DeclCXX.h"
12+
#include "clang/AST/DeclTemplate.h"
13+
#include "clang/ASTMatchers/ASTMatchFinder.h"
14+
#include "clang/ASTMatchers/ASTMatchers.h"
15+
#include "clang/Analysis/CFG.h"
16+
#include "clang/Analysis/FlowSensitive/ControlFlowContext.h"
17+
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
18+
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
19+
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
20+
#include "clang/Analysis/FlowSensitive/Models/NullPointerAnalysisModel.h"
21+
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
22+
#include "clang/Basic/SourceLocation.h"
23+
#include "llvm/ADT/Any.h"
24+
#include "llvm/ADT/STLExtras.h"
25+
#include "llvm/Support/Error.h"
26+
#include <memory>
27+
#include <vector>
28+
29+
namespace clang::tidy::bugprone {
30+
31+
using ast_matchers::MatchFinder;
32+
using dataflow::NullCheckAfterDereferenceDiagnoser;
33+
using dataflow::NullPointerAnalysisModel;
34+
35+
static constexpr llvm::StringLiteral FuncID("fun");
36+
37+
struct ExpandedResult {
38+
SourceLocation WarningLoc;
39+
std::optional<SourceLocation> DerefLoc;
40+
};
41+
42+
using ExpandedResultType =
43+
std::pair<std::vector<ExpandedResult>, std::vector<ExpandedResult>>;
44+
45+
static std::optional<ExpandedResultType>
46+
analyzeFunction(const FunctionDecl &FuncDecl) {
47+
using dataflow::ControlFlowContext;
48+
using dataflow::DataflowAnalysisState;
49+
using llvm::Expected;
50+
51+
ASTContext &ASTCtx = FuncDecl.getASTContext();
52+
53+
if (FuncDecl.getBody() == nullptr) {
54+
return std::nullopt;
55+
}
56+
57+
Expected<ControlFlowContext> Context =
58+
ControlFlowContext::build(FuncDecl, *FuncDecl.getBody(), ASTCtx);
59+
if (!Context)
60+
return std::nullopt;
61+
62+
dataflow::DataflowAnalysisContext AnalysisContext(
63+
std::make_unique<dataflow::WatchedLiteralsSolver>());
64+
dataflow::Environment Env(AnalysisContext, FuncDecl);
65+
NullPointerAnalysisModel Analysis(ASTCtx);
66+
NullCheckAfterDereferenceDiagnoser Diagnoser;
67+
NullCheckAfterDereferenceDiagnoser::ResultType Diagnostics;
68+
69+
using LatticeState = DataflowAnalysisState<NullPointerAnalysisModel::Lattice>;
70+
using DetailMaybeLatticeStates = std::vector<std::optional<LatticeState>>;
71+
72+
auto DiagnoserImpl = [&ASTCtx, &Diagnoser,
73+
&Diagnostics](const CFGElement &Elt,
74+
const LatticeState &S) mutable -> void {
75+
auto EltDiagnostics = Diagnoser.diagnose(ASTCtx, &Elt, S.Env);
76+
llvm::move(EltDiagnostics.first, std::back_inserter(Diagnostics.first));
77+
llvm::move(EltDiagnostics.second, std::back_inserter(Diagnostics.second));
78+
};
79+
80+
Expected<DetailMaybeLatticeStates> BlockToOutputState =
81+
dataflow::runDataflowAnalysis(*Context, Analysis, Env, DiagnoserImpl);
82+
83+
if (llvm::Error E = BlockToOutputState.takeError()) {
84+
llvm::dbgs() << "Dataflow analysis failed: " << llvm::toString(std::move(E))
85+
<< ".\n";
86+
return std::nullopt;
87+
}
88+
89+
ExpandedResultType ExpandedDiagnostics;
90+
91+
llvm::transform(Diagnostics.first,
92+
std::back_inserter(ExpandedDiagnostics.first),
93+
[&](SourceLocation WarningLoc) -> ExpandedResult {
94+
if (auto Val = Diagnoser.WarningLocToVal[WarningLoc];
95+
auto DerefExpr = Diagnoser.ValToDerefLoc[Val]) {
96+
return {WarningLoc, DerefExpr->getBeginLoc()};
97+
}
98+
99+
return {WarningLoc, std::nullopt};
100+
});
101+
102+
llvm::transform(Diagnostics.second,
103+
std::back_inserter(ExpandedDiagnostics.second),
104+
[&](SourceLocation WarningLoc) -> ExpandedResult {
105+
if (auto Val = Diagnoser.WarningLocToVal[WarningLoc];
106+
auto DerefExpr = Diagnoser.ValToDerefLoc[Val]) {
107+
return {WarningLoc, DerefExpr->getBeginLoc()};
108+
}
109+
110+
return {WarningLoc, std::nullopt};
111+
});
112+
113+
return ExpandedDiagnostics;
114+
}
115+
116+
void NullCheckAfterDereferenceCheck::registerMatchers(MatchFinder *Finder) {
117+
using namespace ast_matchers;
118+
119+
auto hasPointerValue =
120+
hasDescendant(NullPointerAnalysisModel::ptrValueMatcher());
121+
Finder->addMatcher(
122+
decl(anyOf(functionDecl(unless(isExpansionInSystemHeader()),
123+
// FIXME: Remove the filter below when lambdas are
124+
// well supported by the check.
125+
unless(hasDeclContext(cxxRecordDecl(isLambda()))),
126+
hasBody(hasPointerValue)),
127+
cxxConstructorDecl(hasAnyConstructorInitializer(
128+
withInitializer(hasPointerValue)))))
129+
.bind(FuncID),
130+
this);
131+
}
132+
133+
void NullCheckAfterDereferenceCheck::check(
134+
const MatchFinder::MatchResult &Result) {
135+
if (Result.SourceManager->getDiagnostics().hasUncompilableErrorOccurred())
136+
return;
137+
138+
const auto *FuncDecl = Result.Nodes.getNodeAs<FunctionDecl>(FuncID);
139+
assert(FuncDecl && "invalid FuncDecl matcher");
140+
if (FuncDecl->isTemplated())
141+
return;
142+
143+
if (const auto Diagnostics = analyzeFunction(*FuncDecl)) {
144+
const auto &[CheckWhenNullLocations, CheckAfterDereferenceLocations] =
145+
*Diagnostics;
146+
147+
for (const auto [WarningLoc, DerefLoc] : CheckAfterDereferenceLocations) {
148+
diag(WarningLoc, "pointer value is checked even though "
149+
"it cannot be null at this point");
150+
151+
if (DerefLoc) {
152+
diag(*DerefLoc,
153+
"one of the locations where the pointer's value cannot be null",
154+
DiagnosticIDs::Note);
155+
}
156+
}
157+
158+
for (const auto [WarningLoc, DerefLoc] : CheckWhenNullLocations) {
159+
diag(WarningLoc,
160+
"pointer value is checked but it can only be null at this point");
161+
162+
if (DerefLoc) {
163+
diag(*DerefLoc,
164+
"one of the locations where the pointer's value can only be null",
165+
DiagnosticIDs::Note);
166+
}
167+
}
168+
}
169+
}
170+
171+
} // namespace clang::tidy::bugprone
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//===--- NullCheckAfterDereferenceCheck.h - clang-tidy ----------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NULLCHECKAFTERDEREFERENCECHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NULLCHECKAFTERDEREFERENCECHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
#include "clang/ASTMatchers/ASTMatchFinder.h"
14+
15+
namespace clang::tidy::bugprone {
16+
17+
/// Finds checks for pointer nullability after a pointer has already been
18+
/// dereferenced, using the data-flow framework.
19+
///
20+
/// For the user-facing documentation see:
21+
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/null-check-after-dereference.html
22+
class NullCheckAfterDereferenceCheck : public ClangTidyCheck {
23+
public:
24+
NullCheckAfterDereferenceCheck(StringRef Name, ClangTidyContext *Context)
25+
: ClangTidyCheck(Name, Context) {}
26+
27+
// The data-flow framework does not support C because of AST differences.
28+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
29+
return LangOpts.CPlusPlus;
30+
}
31+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
32+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
33+
};
34+
35+
} // namespace clang::tidy::bugprone
36+
37+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NULLCHECKAFTERDEREFERENCECHECK_H

clang-tools-extra/clangd/TidyProvider.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,10 @@ TidyProvider disableUnusableChecks(llvm::ArrayRef<std::string> ExtraBadChecks) {
219219
"-bugprone-use-after-move",
220220
// Alias for bugprone-use-after-move.
221221
"-hicpp-invalid-access-moved",
222-
// Check uses dataflow analysis, which might hang/crash unexpectedly on
222+
// Checks use dataflow analysis, which might hang/crash unexpectedly on
223223
// incomplete code.
224224
"-bugprone-unchecked-optional-access",
225+
"-bugprone-null-check-after-dereference",
225226

226227
// ----- Performance problems -----
227228

0 commit comments

Comments
 (0)