-
Notifications
You must be signed in to change notification settings - Fork 13.5k
[clang-tidy][dataflow] Add bugprone-null-check-after-dereference
check
#84166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Discookie
wants to merge
21
commits into
llvm:main
Choose a base branch
from
Discookie:reverse-null
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
4a8e0df
[clang][dataflow] Add null-check after dereference checker
Discookie ba2f02d
Reverse-null surface-level fixes
Discookie 68a903e
Fix crash when analyzing anonymous lambda fns
Discookie 142c79c
Review 1
Discookie af54b31
Fix upstream fn signature change
Discookie 672223e
Formatting
Discookie 268e913
Aggressive Top skipping, first pass
Discookie 1c93b73
Add support for function calls that take a pointer-of-pointer argument
Discookie dd150a9
Remove leftover logs
Discookie 32cfd31
Fix crashes related to setting gl/prvalues mismatch
Discookie a5bb2d4
Remove reference to ControlFlowContext
Discookie 0bdaa40
Fix formatting
Discookie ca00002
Better top-bool- and generic-value handling
Discookie bf9f79a
Remove TK_IgnoreUnlessSpelledInSource
Discookie 32e516d
Use new caller interface for runDataflowAnalysis
Discookie 6020612
Merge branch 'main' into reverse-null
Discookie cc64a3a
Merge branch 'main' into reverse-null
Discookie eaaf9c1
Merge branch 'main' into reverse-null
Discookie 79075e0
Simplify join logic, add some proper unit tests
Discookie cb1242f
Merge branch 'main' into reverse-null
Discookie b2427cd
Minor fixes
Discookie File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
152 changes: 152 additions & 0 deletions
152
clang-tools-extra/clang-tidy/bugprone/NullCheckAfterDereferenceCheck.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
//===--- NullCheckAfterDereferenceCheck.cpp - clang-tidy-------------------===// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#include "NullCheckAfterDereferenceCheck.h" | ||
#include "clang/AST/ASTContext.h" | ||
#include "clang/AST/DeclCXX.h" | ||
#include "clang/AST/DeclTemplate.h" | ||
#include "clang/ASTMatchers/ASTMatchFinder.h" | ||
#include "clang/ASTMatchers/ASTMatchers.h" | ||
#include "clang/Analysis/CFG.h" | ||
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" | ||
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" | ||
#include "clang/Analysis/FlowSensitive/DataflowLattice.h" | ||
#include "clang/Analysis/FlowSensitive/Models/NullPointerAnalysisModel.h" | ||
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h" | ||
#include "clang/Basic/SourceLocation.h" | ||
#include "llvm/ADT/Any.h" | ||
#include "llvm/ADT/STLExtras.h" | ||
#include "llvm/Support/Error.h" | ||
#include <clang/Analysis/FlowSensitive/AdornedCFG.h> | ||
#include <memory> | ||
#include <vector> | ||
|
||
namespace clang::tidy::bugprone { | ||
|
||
using ast_matchers::MatchFinder; | ||
using dataflow::NullCheckAfterDereferenceDiagnoser; | ||
using dataflow::NullPointerAnalysisModel; | ||
using Diagnoser = NullCheckAfterDereferenceDiagnoser; | ||
|
||
static constexpr llvm::StringLiteral FuncID("fun"); | ||
|
||
struct ExpandedResult { | ||
Diagnoser::DiagnosticEntry Entry; | ||
std::optional<SourceLocation> DerefLoc; | ||
}; | ||
|
||
using ExpandedResultType = llvm::SmallVector<ExpandedResult>; | ||
|
||
static std::optional<ExpandedResultType> | ||
analyzeFunction(const FunctionDecl &FuncDecl) { | ||
using dataflow::AdornedCFG; | ||
using dataflow::DataflowAnalysisState; | ||
using llvm::Expected; | ||
|
||
ASTContext &ASTCtx = FuncDecl.getASTContext(); | ||
|
||
if (FuncDecl.getBody() == nullptr) { | ||
return std::nullopt; | ||
} | ||
|
||
Expected<AdornedCFG> Context = | ||
AdornedCFG::build(FuncDecl, *FuncDecl.getBody(), ASTCtx); | ||
if (!Context) | ||
return std::nullopt; | ||
|
||
dataflow::DataflowAnalysisContext AnalysisContext( | ||
std::make_unique<dataflow::WatchedLiteralsSolver>()); | ||
dataflow::Environment Env(AnalysisContext, FuncDecl); | ||
NullPointerAnalysisModel Analysis(ASTCtx); | ||
Diagnoser Diagnoser; | ||
|
||
Diagnoser::ResultType Diagnostics; | ||
|
||
if (llvm::Error E = dataflow::diagnoseFunction<NullPointerAnalysisModel, Diagnoser::DiagnosticEntry>( | ||
FuncDecl, ASTCtx, Diagnoser).moveInto(Diagnostics)) { | ||
llvm::dbgs() << "Dataflow analysis failed: " << llvm::toString(std::move(E)) | ||
<< ".\n"; | ||
return std::nullopt; | ||
} | ||
|
||
ExpandedResultType ExpandedDiagnostics; | ||
|
||
llvm::transform(Diagnostics, | ||
std::back_inserter(ExpandedDiagnostics), | ||
[&](Diagnoser::DiagnosticEntry Entry) -> ExpandedResult { | ||
if (auto Val = Diagnoser.WarningLocToVal[Entry.Location]; | ||
auto DerefExpr = Diagnoser.ValToDerefLoc[Val]) { | ||
return {Entry, DerefExpr->getBeginLoc()}; | ||
} | ||
|
||
return {Entry, std::nullopt}; | ||
}); | ||
|
||
return ExpandedDiagnostics; | ||
} | ||
|
||
void NullCheckAfterDereferenceCheck::registerMatchers(MatchFinder *Finder) { | ||
using namespace ast_matchers; | ||
|
||
auto containsPointerValue = | ||
hasDescendant(NullPointerAnalysisModel::ptrValueMatcher()); | ||
Finder->addMatcher( | ||
decl(anyOf(functionDecl(unless(isExpansionInSystemHeader()), | ||
// FIXME: Remove the filter below when lambdas are | ||
// well supported by the check. | ||
unless(hasDeclContext(cxxRecordDecl(isLambda()))), | ||
hasBody(containsPointerValue)), | ||
cxxConstructorDecl( | ||
unless(hasDeclContext(cxxRecordDecl(isLambda()))), | ||
hasAnyConstructorInitializer( | ||
withInitializer(containsPointerValue))))) | ||
.bind(FuncID), | ||
this); | ||
} | ||
|
||
void NullCheckAfterDereferenceCheck::check( | ||
const MatchFinder::MatchResult &Result) { | ||
if (Result.SourceManager->getDiagnostics().hasUncompilableErrorOccurred()) | ||
return; | ||
|
||
const auto *FuncDecl = Result.Nodes.getNodeAs<FunctionDecl>(FuncID); | ||
assert(FuncDecl && "invalid FuncDecl matcher"); | ||
if (FuncDecl->isTemplated()) | ||
return; | ||
|
||
if (const auto Diagnostics = analyzeFunction(*FuncDecl)) { | ||
for (const auto [Entry, DerefLoc] : *Diagnostics) { | ||
const auto [WarningLoc, Type] = Entry; | ||
|
||
switch (Type) { | ||
case Diagnoser::DiagnosticType::CheckAfterDeref: | ||
diag(WarningLoc, "pointer value is checked even though " | ||
"it cannot be null at this point"); | ||
|
||
if (DerefLoc) { | ||
diag(*DerefLoc, | ||
"one of the locations where the pointer's value cannot be null", | ||
DiagnosticIDs::Note); | ||
} | ||
break; | ||
case Diagnoser::DiagnosticType::CheckWhenNull: | ||
diag(WarningLoc, | ||
"pointer value is checked but it can only be null at this point"); | ||
|
||
if (DerefLoc) { | ||
diag(*DerefLoc, | ||
"one of the locations where the pointer's value can only be null", | ||
DiagnosticIDs::Note); | ||
} | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
|
||
} // namespace clang::tidy::bugprone |
37 changes: 37 additions & 0 deletions
37
clang-tools-extra/clang-tidy/bugprone/NullCheckAfterDereferenceCheck.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
//===--- NullCheckAfterDereferenceCheck.h - clang-tidy ----------*- C++ -*-===// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NULLCHECKAFTERDEREFERENCECHECK_H | ||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NULLCHECKAFTERDEREFERENCECHECK_H | ||
|
||
#include "../ClangTidyCheck.h" | ||
#include "clang/ASTMatchers/ASTMatchFinder.h" | ||
|
||
namespace clang::tidy::bugprone { | ||
|
||
/// Finds checks for pointer nullability after a pointer has already been | ||
/// dereferenced, using the data-flow framework. | ||
/// | ||
/// For the user-facing documentation see: | ||
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/null-check-after-dereference.html | ||
class NullCheckAfterDereferenceCheck : public ClangTidyCheck { | ||
public: | ||
NullCheckAfterDereferenceCheck(StringRef Name, ClangTidyContext *Context) | ||
: ClangTidyCheck(Name, Context) {} | ||
|
||
// The data-flow framework does not support C because of AST differences. | ||
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { | ||
return LangOpts.CPlusPlus; | ||
} | ||
void registerMatchers(ast_matchers::MatchFinder *Finder) override; | ||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override; | ||
}; | ||
|
||
} // namespace clang::tidy::bugprone | ||
|
||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NULLCHECKAFTERDEREFERENCECHECK_H |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.