Skip to content

Commit 638b35e

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 09a6a25 commit 638b35e

File tree

7 files changed

+75
-12
lines changed

7 files changed

+75
-12
lines changed

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

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

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

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ Improvements to clang-query
101101
Improvements to clang-tidy
102102
--------------------------
103103

104+
- :program:`clang-tidy` no longer attemps to analyze code from system headers
105+
by default, greatly improving performance. This behavior is disabled if the
106+
`SystemHeaders` option is enabled.
107+
104108
- The :program:`run-clang-tidy.py` and :program:`clang-tidy-diff.py` scripts
105109
now run checks in parallel by default using all available hardware threads.
106110
Both scripts display the number of threads being used in their output.

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
@@ -230,6 +230,9 @@ AST Matchers
230230
- Ensure ``hasBitWidth`` doesn't crash on bit widths that are dependent on template
231231
parameters.
232232

233+
- Add a boolean member ``IgnoreSystemHeaders`` to ``MatchFinderOptions``. This
234+
allows it to ignore nodes in system headers when traversing the AST.
235+
233236
clang-format
234237
------------
235238

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: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,6 +1336,41 @@ class MatchASTVisitor : public RecursiveASTVisitor<MatchASTVisitor>,
13361336
return false;
13371337
}
13381338

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

14671502
bool MatchASTVisitor::TraverseDecl(Decl *DeclNode) {
1468-
if (!DeclNode) {
1503+
if (shouldSkipNode(DeclNode))
14691504
return true;
1470-
}
14711505

14721506
bool ScopedTraversal =
14731507
TraversingASTNodeNotSpelledInSource || DeclNode->isImplicit();
@@ -1495,9 +1529,9 @@ bool MatchASTVisitor::TraverseDecl(Decl *DeclNode) {
14951529
}
14961530

14971531
bool MatchASTVisitor::TraverseStmt(Stmt *StmtNode, DataRecursionQueue *Queue) {
1498-
if (!StmtNode) {
1532+
if (shouldSkipNode(StmtNode))
14991533
return true;
1500-
}
1534+
15011535
bool ScopedTraversal = TraversingASTNodeNotSpelledInSource ||
15021536
TraversingASTChildrenNotSpelledInSource;
15031537

@@ -1507,11 +1541,17 @@ bool MatchASTVisitor::TraverseStmt(Stmt *StmtNode, DataRecursionQueue *Queue) {
15071541
}
15081542

15091543
bool MatchASTVisitor::TraverseType(QualType TypeNode) {
1544+
if (shouldSkipNode(TypeNode))
1545+
return true;
1546+
15101547
match(TypeNode);
15111548
return RecursiveASTVisitor<MatchASTVisitor>::TraverseType(TypeNode);
15121549
}
15131550

15141551
bool MatchASTVisitor::TraverseTypeLoc(TypeLoc TypeLocNode) {
1552+
if (shouldSkipNode(TypeLocNode))
1553+
return true;
1554+
15151555
// The RecursiveASTVisitor only visits types if they're not within TypeLocs.
15161556
// We still want to find those types via matchers, so we match them here. Note
15171557
// that the TypeLocs are structurally a shadow-hierarchy to the expressed
@@ -1523,6 +1563,9 @@ bool MatchASTVisitor::TraverseTypeLoc(TypeLoc TypeLocNode) {
15231563
}
15241564

15251565
bool MatchASTVisitor::TraverseNestedNameSpecifier(NestedNameSpecifier *NNS) {
1566+
if (shouldSkipNode(NNS))
1567+
return true;
1568+
15261569
match(*NNS);
15271570
return RecursiveASTVisitor<MatchASTVisitor>::TraverseNestedNameSpecifier(NNS);
15281571
}
@@ -1532,6 +1575,9 @@ bool MatchASTVisitor::TraverseNestedNameSpecifierLoc(
15321575
if (!NNS)
15331576
return true;
15341577

1578+
if (shouldSkipNode(NNS))
1579+
return true;
1580+
15351581
match(NNS);
15361582

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

15451591
bool MatchASTVisitor::TraverseConstructorInitializer(
15461592
CXXCtorInitializer *CtorInit) {
1547-
if (!CtorInit)
1593+
if (shouldSkipNode(CtorInit))
15481594
return true;
15491595

15501596
bool ScopedTraversal = TraversingASTNodeNotSpelledInSource ||
@@ -1562,11 +1608,17 @@ bool MatchASTVisitor::TraverseConstructorInitializer(
15621608
}
15631609

15641610
bool MatchASTVisitor::TraverseTemplateArgumentLoc(TemplateArgumentLoc Loc) {
1611+
if (shouldSkipNode(Loc))
1612+
return true;
1613+
15651614
match(Loc);
15661615
return RecursiveASTVisitor<MatchASTVisitor>::TraverseTemplateArgumentLoc(Loc);
15671616
}
15681617

15691618
bool MatchASTVisitor::TraverseAttr(Attr *AttrNode) {
1619+
if (shouldSkipNode(AttrNode))
1620+
return true;
1621+
15701622
match(*AttrNode);
15711623
return RecursiveASTVisitor<MatchASTVisitor>::TraverseAttr(AttrNode);
15721624
}

0 commit comments

Comments
 (0)