Skip to content

Commit d52db8c

Browse files
author
Carlos Gálvez
committed
[clang-tidy] Avoid matching nodes in system headers
This commit is a re-do of e4a8969, which got reverted, with the same goal: dramatically speed-up clang-tidy by avoiding doing work in system headers (which is wasteful as warnings are later discarded). This proposal was already discussed here with favorable feedback: #132725 The novelty of this patch is: - It's less aggressive: it does not fiddle with AST traversal. This solves the issue with the previous patch, which impacted the ability to inspect parents of a given node. - Instead, what we optimize for is exitting early in each Traverse* function of MatchASTVisitor if the node is in a system header, thus avoiding calling the match() function with its corresponding callback (when there is a match). - It does not cause any failing tests. - It does not move MatchFinderOptions outside - instead we add a user-defined default constructor which solves the same problem. - It introduces a function "shouldSkipNode" which can be extended for adding more conditions. For example there's a PR open about skipping modules in clang-tidy where this could come handy: #145630 As a benchmark, I ran clang-tidy with all checks activated, on a single .cpp file which #includes all the standard C++ headers, then measure the time as well as found warnings. On trunk: Suppressed 213314 warnings (213314 in non-user code). real 0m14.311s user 0m14.126s sys 0m0.185s With this patch: Suppressed 149399 warnings (149399 in non-user code). real 0m3.583s user 0m3.454s sys 0m0.128s With the original patch that got reverted: Suppressed 8050 warnings (8050 in non-user code). Runtime: around 1 second. A lot of warnings remain and the runtime is sligthly higher, but we still got a dramatic reduction with no change in functionality. Further investigation has shown that all of the remaining warnings are due to PPCallbacks - implementing a similar system-header exclusion mechanism there can lead to almost no warnings left in system headers. This does not bring the runtime down as much, though. However this may not be as straightforward or wanted, it may even need to be done on a per-check basis (there's about 10 checks or so that would need to explicitly ignore system headers). I will leave that for another patch, it's low priority and does not improve the runtime much (it just prints better statistics). Fixes #52959
1 parent 67e2faa commit d52db8c

File tree

7 files changed

+79
-12
lines changed

7 files changed

+79
-12
lines changed

clang-tools-extra/clang-tidy/ClangTidy.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,10 @@ ClangTidyASTConsumerFactory::createASTConsumer(
425425
FinderOptions.CheckProfiling.emplace(Profiling->Records);
426426
}
427427

428+
// Avoid processing system headers, unless the user explicitly requests it
429+
if (!Context.getOptions().SystemHeaders.value_or(false))
430+
FinderOptions.IgnoreSystemHeaders = true;
431+
428432
std::unique_ptr<ast_matchers::MatchFinder> Finder(
429433
new ast_matchers::MatchFinder(std::move(FinderOptions)));
430434

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ Improvements to clang-tidy
105105
now run checks in parallel by default using all available hardware threads.
106106
Both scripts display the number of threads being used in their output.
107107

108+
- :program:`clang-tidy` no longer attemps to match AST nodes from system headers
109+
by default, greatly improving performance. This behavior is disabled if the
110+
`SystemHeaders` option is enabled.
111+
108112
New checks
109113
^^^^^^^^^^
110114

clang-tools-extra/test/clang-tidy/infrastructure/file-filter.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,14 @@ class A { A(int); };
6666
// CHECK4-NOT: warning:
6767
// CHECK4-QUIET-NOT: warning:
6868

69-
// CHECK: Suppressed 3 warnings (3 in non-user code)
7069
// CHECK: Use -header-filter=.* to display errors from all non-system headers.
7170
// CHECK-QUIET-NOT: Suppressed
72-
// CHECK2: Suppressed 1 warnings (1 in non-user code)
73-
// CHECK2: Use -header-filter=.* {{.*}}
7471
// CHECK2-QUIET-NOT: Suppressed
75-
// CHECK3: Suppressed 2 warnings (2 in non-user code)
7672
// CHECK3: Use -header-filter=.* {{.*}}
7773
// CHECK3-QUIET-NOT: Suppressed
7874
// CHECK4-NOT: Suppressed {{.*}} warnings
7975
// CHECK4-NOT: Use -header-filter=.* {{.*}}
8076
// CHECK4-QUIET-NOT: Suppressed
81-
// CHECK6: Suppressed 2 warnings (2 in non-user code)
8277
// CHECK6: Use -header-filter=.* {{.*}}
8378

8479
int x = 123;

clang-tools-extra/test/clang-tidy/infrastructure/system-headers.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
// RUN: clang-tidy -help | FileCheck -check-prefix=CHECK-OPT-PRESENT %s
1212

1313
// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' -system-headers=true %s -- -isystem %S/Inputs/system-headers 2>&1 | FileCheck -check-prefix=CHECK-SYSTEM-HEADERS %s
14-
// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' -system-headers=false %s -- -isystem %S/Inputs/system-headers 2>&1 | FileCheck -check-prefix=CHECK-NO-SYSTEM-HEADERS %s
14+
// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' -system-headers=false %s -- -isystem %S/Inputs/system-headers 2>&1 | FileCheck -check-prefix=CHECK-NO-SYSTEM-HEADERS --allow-empty %s
1515
// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' -config='SystemHeaders: true' %s -- -isystem %S/Inputs/system-headers 2>&1 | FileCheck -check-prefix=CHECK-SYSTEM-HEADERS %s
16-
// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' -config='SystemHeaders: false' %s -- -isystem %S/Inputs/system-headers 2>&1 | FileCheck -check-prefix=CHECK-NO-SYSTEM-HEADERS %s
16+
// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' -config='SystemHeaders: false' %s -- -isystem %S/Inputs/system-headers 2>&1 | FileCheck -check-prefix=CHECK-NO-SYSTEM-HEADERS --allow-empty %s
1717

1818
#include <system_header.h>
1919
// CHECK-SYSTEM-HEADERS: system_header.h:1:13: warning: single-argument constructors must be marked explicit

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,9 @@ AST Matchers
221221
- Ensure ``hasBitWidth`` doesn't crash on bit widths that are dependent on template
222222
parameters.
223223

224+
- Add a boolean member ``IgnoreSystemHeaders`` to ``MatchFinderOptions``. This
225+
allows it to ignore nodes in system headers when traversing the AST.
226+
224227
clang-format
225228
------------
226229

clang/include/clang/ASTMatchers/ASTMatchFinder.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,15 @@ class MatchFinder {
135135
llvm::StringMap<llvm::TimeRecord> &Records;
136136
};
137137

138+
MatchFinderOptions() {}
139+
138140
/// Enables per-check timers.
139141
///
140142
/// It prints a report after match.
141143
std::optional<Profiling> CheckProfiling;
144+
145+
/// Avoids matching declarations in system headers.
146+
bool IgnoreSystemHeaders{false};
142147
};
143148

144149
MatchFinder(MatchFinderOptions Options = MatchFinderOptions());

clang/lib/ASTMatchers/ASTMatchFinder.cpp

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,6 +1336,45 @@ class MatchASTVisitor : public RecursiveASTVisitor<MatchASTVisitor>,
13361336
return false;
13371337
}
13381338

1339+
bool isInSystemHeader(const SourceLocation &Loc) {
1340+
const SourceManager &SM = getASTContext().getSourceManager();
1341+
return SM.isInSystemHeader(Loc);
1342+
}
1343+
1344+
template <typename T> SourceLocation getNodeLocation(T const &Node) {
1345+
return Node.getBeginLoc();
1346+
}
1347+
1348+
SourceLocation getNodeLocation(QualType const &Node) { return {}; }
1349+
1350+
SourceLocation getNodeLocation(NestedNameSpecifier const &Node) { return {}; }
1351+
1352+
SourceLocation getNodeLocation(CXXCtorInitializer const &Node) {
1353+
return Node.getSourceLocation();
1354+
}
1355+
1356+
SourceLocation getNodeLocation(TemplateArgumentLoc const &Node) {
1357+
return Node.getLocation();
1358+
}
1359+
1360+
SourceLocation getNodeLocation(Attr const &Node) {
1361+
return Node.getLocation();
1362+
}
1363+
1364+
template <typename T>
1365+
auto shouldSkipNode(T const &Node)
1366+
-> std::enable_if_t<std::is_pointer_v<T>, bool> {
1367+
return (Node == nullptr) || shouldSkipNode(*Node);
1368+
}
1369+
1370+
template <typename T>
1371+
auto shouldSkipNode(T const &Node)
1372+
-> std::enable_if_t<!std::is_pointer_v<T>, bool> {
1373+
if (Options.IgnoreSystemHeaders && isInSystemHeader(getNodeLocation(Node)))
1374+
return true;
1375+
return false;
1376+
}
1377+
13391378
/// Bucket to record map.
13401379
///
13411380
/// Used to get the appropriate bucket for each matcher.
@@ -1465,9 +1504,8 @@ bool MatchASTVisitor::objcClassIsDerivedFrom(
14651504
}
14661505

14671506
bool MatchASTVisitor::TraverseDecl(Decl *DeclNode) {
1468-
if (!DeclNode) {
1507+
if (shouldSkipNode(DeclNode))
14691508
return true;
1470-
}
14711509

14721510
bool ScopedTraversal =
14731511
TraversingASTNodeNotSpelledInSource || DeclNode->isImplicit();
@@ -1495,9 +1533,9 @@ bool MatchASTVisitor::TraverseDecl(Decl *DeclNode) {
14951533
}
14961534

14971535
bool MatchASTVisitor::TraverseStmt(Stmt *StmtNode, DataRecursionQueue *Queue) {
1498-
if (!StmtNode) {
1536+
if (shouldSkipNode(StmtNode))
14991537
return true;
1500-
}
1538+
15011539
bool ScopedTraversal = TraversingASTNodeNotSpelledInSource ||
15021540
TraversingASTChildrenNotSpelledInSource;
15031541

@@ -1507,11 +1545,17 @@ bool MatchASTVisitor::TraverseStmt(Stmt *StmtNode, DataRecursionQueue *Queue) {
15071545
}
15081546

15091547
bool MatchASTVisitor::TraverseType(QualType TypeNode) {
1548+
if (shouldSkipNode(TypeNode))
1549+
return true;
1550+
15101551
match(TypeNode);
15111552
return RecursiveASTVisitor<MatchASTVisitor>::TraverseType(TypeNode);
15121553
}
15131554

15141555
bool MatchASTVisitor::TraverseTypeLoc(TypeLoc TypeLocNode) {
1556+
if (shouldSkipNode(TypeLocNode))
1557+
return true;
1558+
15151559
// The RecursiveASTVisitor only visits types if they're not within TypeLocs.
15161560
// We still want to find those types via matchers, so we match them here. Note
15171561
// that the TypeLocs are structurally a shadow-hierarchy to the expressed
@@ -1523,6 +1567,9 @@ bool MatchASTVisitor::TraverseTypeLoc(TypeLoc TypeLocNode) {
15231567
}
15241568

15251569
bool MatchASTVisitor::TraverseNestedNameSpecifier(NestedNameSpecifier *NNS) {
1570+
if (shouldSkipNode(NNS))
1571+
return true;
1572+
15261573
match(*NNS);
15271574
return RecursiveASTVisitor<MatchASTVisitor>::TraverseNestedNameSpecifier(NNS);
15281575
}
@@ -1532,6 +1579,9 @@ bool MatchASTVisitor::TraverseNestedNameSpecifierLoc(
15321579
if (!NNS)
15331580
return true;
15341581

1582+
if (shouldSkipNode(NNS))
1583+
return true;
1584+
15351585
match(NNS);
15361586

15371587
// We only match the nested name specifier here (as opposed to traversing it)
@@ -1544,7 +1594,7 @@ bool MatchASTVisitor::TraverseNestedNameSpecifierLoc(
15441594

15451595
bool MatchASTVisitor::TraverseConstructorInitializer(
15461596
CXXCtorInitializer *CtorInit) {
1547-
if (!CtorInit)
1597+
if (shouldSkipNode(CtorInit))
15481598
return true;
15491599

15501600
bool ScopedTraversal = TraversingASTNodeNotSpelledInSource ||
@@ -1562,11 +1612,17 @@ bool MatchASTVisitor::TraverseConstructorInitializer(
15621612
}
15631613

15641614
bool MatchASTVisitor::TraverseTemplateArgumentLoc(TemplateArgumentLoc Loc) {
1615+
if (shouldSkipNode(Loc))
1616+
return true;
1617+
15651618
match(Loc);
15661619
return RecursiveASTVisitor<MatchASTVisitor>::TraverseTemplateArgumentLoc(Loc);
15671620
}
15681621

15691622
bool MatchASTVisitor::TraverseAttr(Attr *AttrNode) {
1623+
if (shouldSkipNode(AttrNode))
1624+
return true;
1625+
15701626
match(*AttrNode);
15711627
return RecursiveASTVisitor<MatchASTVisitor>::TraverseAttr(AttrNode);
15721628
}

0 commit comments

Comments
 (0)