Skip to content

Commit 7e3ea55

Browse files
author
Balazs Benics
committed
[clang-tidy] modernize-deprecated-headers check should respect extern "C" blocks
The check should not report includes wrapped by `extern "C" { ... }` blocks, such as: ```lang=C++ #ifdef __cplusplus extern "C" { #endif #include "assert.h" #ifdef __cplusplus } #endif ``` This pattern comes up sometimes in header files designed to be consumed by both C and C++ source files. The check now reports false reports when the header file is consumed by a C++ translation unit. In this change, I'm not emitting the reports immediately from the `PPCallback`, rather aggregating them for further processing. After all preprocessing is done, the matcher will be called on the `TranslationUnitDecl`, ensuring that the check callback is called only once. Within that callback, I'm recursively visiting each decls, looking for `LinkageSpecDecls` which represent the `extern "C"` specifier. After this, I'm dropping all the reports coming from inside of it. After the visitation is done, I'm emitting the reports I'm left with. For performance reasons, I'm sorting the `IncludeMarkers` by their corresponding locations. This makes the scan `O(log(N)` when looking up the `IncludeMarkers` affected by the given `extern "C"` block. For this, I'm using `lower_bound()` and `upper_bound()`. Reviewed By: whisperity Differential Revision: https://reviews.llvm.org/D125209
1 parent a2ac0bb commit 7e3ea55

File tree

5 files changed

+192
-22
lines changed

5 files changed

+192
-22
lines changed

clang-tools-extra/clang-tidy/modernize/DeprecatedHeadersCheck.cpp

Lines changed: 109 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,37 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "DeprecatedHeadersCheck.h"
10+
#include "clang/AST/RecursiveASTVisitor.h"
1011
#include "clang/Frontend/CompilerInstance.h"
1112
#include "clang/Lex/PPCallbacks.h"
1213
#include "clang/Lex/Preprocessor.h"
1314
#include "llvm/ADT/StringMap.h"
1415
#include "llvm/ADT/StringSet.h"
1516

17+
#include <algorithm>
1618
#include <vector>
1719

1820
namespace clang {
1921
namespace tidy {
2022
namespace modernize {
23+
namespace detail {
24+
bool operator<(const IncludeMarker &LHS, const IncludeMarker &RHS) {
25+
return LHS.DecomposedDiagLoc < RHS.DecomposedDiagLoc;
26+
}
27+
bool operator<(const IncludeMarker &LHS,
28+
const std::pair<FileID, unsigned> &RHS) {
29+
return LHS.DecomposedDiagLoc < RHS;
30+
}
31+
bool operator<(const std::pair<FileID, unsigned> &LHS,
32+
const IncludeMarker &RHS) {
33+
return LHS < RHS.DecomposedDiagLoc;
34+
}
2135

22-
namespace {
2336
class IncludeModernizePPCallbacks : public PPCallbacks {
2437
public:
25-
explicit IncludeModernizePPCallbacks(ClangTidyCheck &Check,
26-
LangOptions LangOpts);
38+
explicit IncludeModernizePPCallbacks(DeprecatedHeadersCheck &Check,
39+
LangOptions LangOpts,
40+
const SourceManager &SM);
2741

2842
void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
2943
StringRef FileName, bool IsAngled,
@@ -33,22 +47,98 @@ class IncludeModernizePPCallbacks : public PPCallbacks {
3347
SrcMgr::CharacteristicKind FileType) override;
3448

3549
private:
36-
ClangTidyCheck &Check;
50+
DeprecatedHeadersCheck &Check;
3751
LangOptions LangOpts;
3852
llvm::StringMap<std::string> CStyledHeaderToCxx;
3953
llvm::StringSet<> DeleteHeaders;
54+
const SourceManager &SM;
55+
};
56+
57+
class ExternCRefutationVisitor
58+
: public RecursiveASTVisitor<ExternCRefutationVisitor> {
59+
std::vector<IncludeMarker> &IncludesToBeProcessed;
60+
const SourceManager &SM;
61+
62+
public:
63+
ExternCRefutationVisitor(std::vector<IncludeMarker> &IncludesToBeProcessed,
64+
SourceManager &SM)
65+
: IncludesToBeProcessed(IncludesToBeProcessed), SM(SM) {}
66+
bool shouldWalkTypesOfTypeLocs() const { return false; }
67+
bool shouldVisitLambdaBody() const { return false; }
68+
69+
bool VisitLinkageSpecDecl(LinkageSpecDecl *LinkSpecDecl) const {
70+
if (LinkSpecDecl->getLanguage() != LinkageSpecDecl::lang_c ||
71+
!LinkSpecDecl->hasBraces())
72+
return true;
73+
74+
auto ExternCBlockBegin =
75+
SM.getDecomposedExpansionLoc(LinkSpecDecl->getBeginLoc());
76+
auto ExternCBlockEnd =
77+
SM.getDecomposedExpansionLoc(LinkSpecDecl->getEndLoc());
78+
79+
auto Begin = IncludesToBeProcessed.begin();
80+
auto End = IncludesToBeProcessed.end();
81+
auto Low = std::lower_bound(Begin, End, ExternCBlockBegin);
82+
auto Up = std::upper_bound(Low, End, ExternCBlockEnd);
83+
IncludesToBeProcessed.erase(Low, Up);
84+
return true;
85+
}
4086
};
41-
} // namespace
87+
} // namespace detail
4288

4389
void DeprecatedHeadersCheck::registerPPCallbacks(
4490
const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
45-
PP->addPPCallbacks(
46-
::std::make_unique<IncludeModernizePPCallbacks>(*this, getLangOpts()));
91+
PP->addPPCallbacks(::std::make_unique<detail::IncludeModernizePPCallbacks>(
92+
*this, getLangOpts(), PP->getSourceManager()));
93+
}
94+
void DeprecatedHeadersCheck::registerMatchers(
95+
ast_matchers::MatchFinder *Finder) {
96+
// Even though the checker operates on a "preprocessor" level, we still need
97+
// to act on a "TranslationUnit" to acquire the AST where we can walk each
98+
// Decl and look for `extern "C"` blocks where we will suppress the report we
99+
// collected during the preprocessing phase.
100+
// The `onStartOfTranslationUnit()` won't suffice, since we need some handle
101+
// to the `ASTContext`.
102+
Finder->addMatcher(ast_matchers::translationUnitDecl().bind("TU"), this);
103+
}
104+
105+
void DeprecatedHeadersCheck::onEndOfTranslationUnit() {
106+
IncludesToBeProcessed.clear();
107+
}
108+
109+
void DeprecatedHeadersCheck::check(
110+
const ast_matchers::MatchFinder::MatchResult &Result) {
111+
SourceManager &SM = Result.Context->getSourceManager();
112+
using detail::IncludeMarker;
113+
114+
llvm::sort(IncludesToBeProcessed);
115+
116+
// Suppress includes wrapped by `extern "C" { ... }` blocks.
117+
detail::ExternCRefutationVisitor Visitor(IncludesToBeProcessed, SM);
118+
Visitor.TraverseAST(*Result.Context);
119+
120+
// Emit all the remaining reports.
121+
for (const IncludeMarker &Entry : IncludesToBeProcessed) {
122+
SourceLocation DiagLoc = SM.getComposedLoc(Entry.DecomposedDiagLoc.first,
123+
Entry.DecomposedDiagLoc.second);
124+
if (Entry.Replacement.empty()) {
125+
diag(DiagLoc, "including '%0' has no effect in C++; consider removing it")
126+
<< Entry.FileName << FixItHint::CreateRemoval(Entry.ReplacementRange);
127+
} else {
128+
diag(DiagLoc, "inclusion of deprecated C++ header "
129+
"'%0'; consider using '%1' instead")
130+
<< Entry.FileName << Entry.Replacement
131+
<< FixItHint::CreateReplacement(
132+
Entry.ReplacementRange,
133+
(llvm::Twine("<") + Entry.Replacement + ">").str());
134+
}
135+
}
47136
}
48137

49-
IncludeModernizePPCallbacks::IncludeModernizePPCallbacks(ClangTidyCheck &Check,
50-
LangOptions LangOpts)
51-
: Check(Check), LangOpts(LangOpts) {
138+
detail::IncludeModernizePPCallbacks::IncludeModernizePPCallbacks(
139+
DeprecatedHeadersCheck &Check, LangOptions LangOpts,
140+
const SourceManager &SM)
141+
: Check(Check), LangOpts(LangOpts), SM(SM) {
52142
for (const auto &KeyValue :
53143
std::vector<std::pair<llvm::StringRef, std::string>>(
54144
{{"assert.h", "cassert"},
@@ -89,7 +179,7 @@ IncludeModernizePPCallbacks::IncludeModernizePPCallbacks(ClangTidyCheck &Check,
89179
}
90180
}
91181

92-
void IncludeModernizePPCallbacks::InclusionDirective(
182+
void detail::IncludeModernizePPCallbacks::InclusionDirective(
93183
SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName,
94184
bool IsAngled, CharSourceRange FilenameRange, Optional<FileEntryRef> File,
95185
StringRef SearchPath, StringRef RelativePath, const Module *Imported,
@@ -101,19 +191,16 @@ void IncludeModernizePPCallbacks::InclusionDirective(
101191
// 1. Insert std prefix for every such symbol occurrence.
102192
// 2. Insert `using namespace std;` to the beginning of TU.
103193
// 3. Do nothing and let the user deal with the migration himself.
194+
std::pair<FileID, unsigned> DiagLoc =
195+
SM.getDecomposedExpansionLoc(FilenameRange.getBegin());
104196
if (CStyledHeaderToCxx.count(FileName) != 0) {
105-
std::string Replacement =
106-
(llvm::Twine("<") + CStyledHeaderToCxx[FileName] + ">").str();
107-
Check.diag(FilenameRange.getBegin(), "inclusion of deprecated C++ header "
108-
"'%0'; consider using '%1' instead")
109-
<< FileName << CStyledHeaderToCxx[FileName]
110-
<< FixItHint::CreateReplacement(FilenameRange.getAsRange(),
111-
Replacement);
197+
Check.IncludesToBeProcessed.push_back(
198+
IncludeMarker{CStyledHeaderToCxx[FileName], FileName,
199+
FilenameRange.getAsRange(), DiagLoc});
112200
} else if (DeleteHeaders.count(FileName) != 0) {
113-
Check.diag(FilenameRange.getBegin(),
114-
"including '%0' has no effect in C++; consider removing it")
115-
<< FileName << FixItHint::CreateRemoval(
116-
SourceRange(HashLoc, FilenameRange.getEnd()));
201+
Check.IncludesToBeProcessed.push_back(
202+
IncludeMarker{std::string{}, FileName,
203+
SourceRange{HashLoc, FilenameRange.getEnd()}, DiagLoc});
117204
}
118205
}
119206

clang-tools-extra/clang-tidy/modernize/DeprecatedHeadersCheck.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@ namespace clang {
1515
namespace tidy {
1616
namespace modernize {
1717

18+
namespace detail {
19+
class IncludeModernizePPCallbacks;
20+
class ExternCRefutationVisitor;
21+
struct IncludeMarker {
22+
std::string Replacement;
23+
StringRef FileName;
24+
SourceRange ReplacementRange;
25+
std::pair<FileID, unsigned> DecomposedDiagLoc;
26+
};
27+
bool operator<(const IncludeMarker &LHS, const IncludeMarker &RHS);
28+
bool operator<(const IncludeMarker &LHS,
29+
const std::pair<FileID, unsigned> &RHS);
30+
bool operator<(const std::pair<FileID, unsigned> &LHS,
31+
const IncludeMarker &RHS);
32+
} // namespace detail
33+
1834
/// This check replaces deprecated C library headers with their C++ STL
1935
/// alternatives.
2036
///
@@ -41,6 +57,14 @@ class DeprecatedHeadersCheck : public ClangTidyCheck {
4157
}
4258
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
4359
Preprocessor *ModuleExpanderPP) override;
60+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
61+
void onEndOfTranslationUnit() override;
62+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
63+
64+
private:
65+
friend class detail::IncludeModernizePPCallbacks;
66+
friend class detail::ExternCRefutationVisitor;
67+
std::vector<detail::IncludeMarker> IncludesToBeProcessed;
4468
};
4569

4670
} // namespace modernize

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ Changes in existing checks
170170
<clang-tidy/checks/misc-redundant-expression>` involving assignments in
171171
conditions. This fixes `Issue 35853 <https://github.com/llvm/llvm-project/issues/35853>`_.
172172

173+
- Fixed a false positive in :doc:`modernize-deprecated-headers
174+
<clang-tidy/checks/modernize-deprecated-headers>` involving including
175+
C header files from C++ files wrapped by ``extern "C" { ... }`` blocks.
176+
Such includes will be ignored by now.
177+
173178
- Improved :doc:`performance-inefficient-vector-operation
174179
<clang-tidy/checks/performance-inefficient-vector-operation>` to work when
175180
the vector is a member of a structure.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#include "assert.h"
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// RUN: %check_clang_tidy -std=c++11-or-later %s modernize-deprecated-headers %t \
2+
// RUN: -- -header-filter='.*' -system-headers \
3+
// RUN: -extra-arg-before=-isystem%S/Inputs/modernize-deprecated-headers
4+
5+
#define EXTERN_C extern "C"
6+
7+
extern "C++" {
8+
// We should still have the warnings here.
9+
#include <stdbool.h>
10+
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: including 'stdbool.h' has no effect in C++; consider removing it [modernize-deprecated-headers]
11+
}
12+
13+
#include <assert.h>
14+
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: inclusion of deprecated C++ header 'assert.h'; consider using 'cassert' instead [modernize-deprecated-headers]
15+
// CHECK-FIXES: {{^}}#include <cassert>{{$}}
16+
17+
#include <stdbool.h>
18+
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: including 'stdbool.h' has no effect in C++; consider removing it [modernize-deprecated-headers]
19+
20+
#include <mylib.h>
21+
// CHECK-MESSAGES: mylib.h:1:10: warning: inclusion of deprecated C++ header 'assert.h'; consider using 'cassert' instead [modernize-deprecated-headers]
22+
23+
namespace wrapping {
24+
extern "C" {
25+
#include <assert.h> // no-warning
26+
#include <mylib.h> // no-warning
27+
#include <stdbool.h> // no-warning
28+
}
29+
} // namespace wrapping
30+
31+
extern "C" {
32+
namespace wrapped {
33+
#include <assert.h> // no-warning
34+
#include <mylib.h> // no-warning
35+
#include <stdbool.h> // no-warning
36+
} // namespace wrapped
37+
}
38+
39+
namespace wrapping {
40+
extern "C" {
41+
namespace wrapped {
42+
#include <assert.h> // no-warning
43+
#include <mylib.h> // no-warning
44+
#include <stdbool.h> // no-warning
45+
} // namespace wrapped
46+
}
47+
} // namespace wrapping
48+
49+
EXTERN_C {
50+
#include <assert.h> // no-warning
51+
#include <mylib.h> // no-warning
52+
#include <stdbool.h> // no-warning
53+
}

0 commit comments

Comments
 (0)