Skip to content

Commit 792dc18

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: llvm#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: llvm#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 llvm#52959
1 parent a636b7b commit 792dc18

File tree

7 files changed

+79
-13
lines changed

7 files changed

+79
-13
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
@@ -97,6 +97,10 @@ Improvements to clang-tidy
9797
now run checks in parallel by default using all available hardware threads.
9898
Both scripts display the number of threads being used in their output.
9999

100+
- :program:`clang-tidy` no longer attemps to match AST nodes from system headers
101+
by default, greatly improving performance. This behavior is disabled if the
102+
`SystemHeaders` option is enabled.
103+
100104
New checks
101105
^^^^^^^^^^
102106

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
@@ -207,6 +207,9 @@ AST Matchers
207207
- Ensure ``hasBitWidth`` doesn't crash on bit widths that are dependent on template
208208
parameters.
209209

210+
- Add a boolean member ``SkipSystemHeaders`` to ``MatchFinderOptions``. This
211+
allows it to skip nodes in system headers when traversing the AST.
212+
210213
clang-format
211214
------------
212215

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 & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,6 @@ class MatchChildASTVisitor
152152
// They are public only to allow CRTP to work. They are *not *part
153153
// of the public API of this class.
154154
bool TraverseDecl(Decl *DeclNode) {
155-
156155
if (DeclNode && DeclNode->isImplicit() &&
157156
Finder->isTraversalIgnoringImplicitNodes())
158157
return baseTraverse(*DeclNode);
@@ -1336,6 +1335,45 @@ class MatchASTVisitor : public RecursiveASTVisitor<MatchASTVisitor>,
13361335
return false;
13371336
}
13381337

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

14671505
bool MatchASTVisitor::TraverseDecl(Decl *DeclNode) {
1468-
if (!DeclNode) {
1506+
if (shouldSkipNode(DeclNode))
14691507
return true;
1470-
}
14711508

14721509
bool ScopedTraversal =
14731510
TraversingASTNodeNotSpelledInSource || DeclNode->isImplicit();
@@ -1495,9 +1532,9 @@ bool MatchASTVisitor::TraverseDecl(Decl *DeclNode) {
14951532
}
14961533

14971534
bool MatchASTVisitor::TraverseStmt(Stmt *StmtNode, DataRecursionQueue *Queue) {
1498-
if (!StmtNode) {
1535+
if (shouldSkipNode(StmtNode))
14991536
return true;
1500-
}
1537+
15011538
bool ScopedTraversal = TraversingASTNodeNotSpelledInSource ||
15021539
TraversingASTChildrenNotSpelledInSource;
15031540

@@ -1507,11 +1544,17 @@ bool MatchASTVisitor::TraverseStmt(Stmt *StmtNode, DataRecursionQueue *Queue) {
15071544
}
15081545

15091546
bool MatchASTVisitor::TraverseType(QualType TypeNode) {
1547+
if (shouldSkipNode(TypeNode))
1548+
return true;
1549+
15101550
match(TypeNode);
15111551
return RecursiveASTVisitor<MatchASTVisitor>::TraverseType(TypeNode);
15121552
}
15131553

15141554
bool MatchASTVisitor::TraverseTypeLoc(TypeLoc TypeLocNode) {
1555+
if (shouldSkipNode(TypeLocNode))
1556+
return true;
1557+
15151558
// The RecursiveASTVisitor only visits types if they're not within TypeLocs.
15161559
// We still want to find those types via matchers, so we match them here. Note
15171560
// that the TypeLocs are structurally a shadow-hierarchy to the expressed
@@ -1523,6 +1566,9 @@ bool MatchASTVisitor::TraverseTypeLoc(TypeLoc TypeLocNode) {
15231566
}
15241567

15251568
bool MatchASTVisitor::TraverseNestedNameSpecifier(NestedNameSpecifier *NNS) {
1569+
if (shouldSkipNode(NNS))
1570+
return true;
1571+
15261572
match(*NNS);
15271573
return RecursiveASTVisitor<MatchASTVisitor>::TraverseNestedNameSpecifier(NNS);
15281574
}
@@ -1532,6 +1578,9 @@ bool MatchASTVisitor::TraverseNestedNameSpecifierLoc(
15321578
if (!NNS)
15331579
return true;
15341580

1581+
if (shouldSkipNode(NNS))
1582+
return true;
1583+
15351584
match(NNS);
15361585

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

15451594
bool MatchASTVisitor::TraverseConstructorInitializer(
15461595
CXXCtorInitializer *CtorInit) {
1547-
if (!CtorInit)
1596+
if (shouldSkipNode(CtorInit))
15481597
return true;
15491598

15501599
bool ScopedTraversal = TraversingASTNodeNotSpelledInSource ||
@@ -1562,11 +1611,17 @@ bool MatchASTVisitor::TraverseConstructorInitializer(
15621611
}
15631612

15641613
bool MatchASTVisitor::TraverseTemplateArgumentLoc(TemplateArgumentLoc Loc) {
1614+
if (shouldSkipNode(Loc))
1615+
return true;
1616+
15651617
match(Loc);
15661618
return RecursiveASTVisitor<MatchASTVisitor>::TraverseTemplateArgumentLoc(Loc);
15671619
}
15681620

15691621
bool MatchASTVisitor::TraverseAttr(Attr *AttrNode) {
1622+
if (shouldSkipNode(AttrNode))
1623+
return true;
1624+
15701625
match(*AttrNode);
15711626
return RecursiveASTVisitor<MatchASTVisitor>::TraverseAttr(AttrNode);
15721627
}

0 commit comments

Comments
 (0)