Skip to content

Commit 18a676b

Browse files
author
Carlos Gálvez
committed
[clang-tidy] Avoid processing declarations in system headers
Currently, clang-tidy processes the entire TranslationUnit, including declarations in system headers. However, the work done in system headers is discarded at the very end when presenting results, unless the SystemHeaders option is active. This is a lot of wasted work, and makes clang-tidy very slow. In comparison, clangd only processes declarations in the main file, and it's claimed to be 10x faster than clang-tidy: https://github.com/lljbash/clangd-tidy To solve this problem, we can apply a similar solution done in clangd into clang-tidy. We do this by changing the traversal scope from the default TranslationUnitDecl, to only contain the top-level declarations that are _not_ part of system headers. We do this by prepending a new ASTConsumer to the list of consumers: this new consumer sets the traversal scope in the ASTContext, which is later used by the MatchASTConsumer. Note: this behavior is not active if the user requests warnings from system headers via the SystemHeaders option. Note2: out of all the unit tests, only one of them fails: readability/identifier-naming-anon-record-fields.cpp This is because the limited traversal scope no longer includes the "IndirectFieldDecl" that appears in the AST when having a global scope anonymous union. I have not found a way to make this one work. However, it does seem like a very niche use case, and the benefits of a 10x faster clang-tidy largely outweigh the false negative now introduced by this patch. This use case is therefore removed from the unit test to make it pass. Note3: I have purposely decided to make this new feature enabled by default, instead of adding a new "opt-in/opt-out" flag. Having a new flag would mean duplicating all our tests to ensure they work in both modes, which would be infeasible. Having it enabled by default allow people to get the benefits immediately. Given that all unit tests pass, the risk for regressions is low. Even if that's the case, the only issue would be false negatives (fewer things are detected), which are much more tolerable than false positives. Credits: original implementation by @njames93, here: https://reviews.llvm.org/D150126 This implementation is simpler in the sense that it does not consider HeaderFilterRegex to filter even further. A follow-up patch could include the functionality if wanted. Fixes #52959
1 parent 97ed201 commit 18a676b

File tree

6 files changed

+66
-32
lines changed

6 files changed

+66
-32
lines changed

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,35 @@ class ClangTidyASTConsumer : public MultiplexConsumer {
339339
void anchor() override {};
340340
};
341341

342+
/// ASTConsumer that filters top-level declarations that are in system headers,
343+
/// and sets the AST traversal scope to only cover the declarations in user
344+
/// headers. This makes all clang-tidy checks avoid spending time processing
345+
/// declarations in system headers. The results are discarded anyway when
346+
/// presenting the results.
347+
class IgnoreSystemHeadersConsumer : public ASTConsumer {
348+
public:
349+
bool HandleTopLevelDecl(DeclGroupRef DG) override {
350+
for (Decl *D : DG) {
351+
if (!isInSystemHeader(D))
352+
Decls.push_back(D);
353+
}
354+
return true;
355+
}
356+
357+
void HandleTranslationUnit(ASTContext &Ctx) override {
358+
Ctx.setTraversalScope(Decls);
359+
}
360+
361+
private:
362+
std::vector<Decl *> Decls;
363+
364+
bool isInSystemHeader(Decl *D) {
365+
SourceManager &SM = D->getASTContext().getSourceManager();
366+
SourceLocation Loc = SM.getExpansionLoc(D->getBeginLoc());
367+
return SM.isInSystemHeader(Loc);
368+
}
369+
};
370+
342371
} // namespace
343372

344373
ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory(
@@ -449,6 +478,10 @@ ClangTidyASTConsumerFactory::createASTConsumer(
449478
}
450479

451480
std::vector<std::unique_ptr<ASTConsumer>> Consumers;
481+
482+
if (!Context.getOptions().SystemHeaders.value_or(false))
483+
Consumers.push_back(std::make_unique<IgnoreSystemHeadersConsumer>());
484+
452485
if (!Checks.empty())
453486
Consumers.push_back(Finder->newASTConsumer());
454487

clang-tools-extra/clang-tidy/cert/DontModifyStdNamespaceCheck.cpp

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,41 @@ AST_POLYMORPHIC_MATCHER_P(
3535
Builder) != Args.end();
3636
}
3737

38+
bool isStdOrPosixImpl(const DeclContext *Ctx) {
39+
if (!Ctx->isNamespace())
40+
return false;
41+
42+
const auto *ND = cast<NamespaceDecl>(Ctx);
43+
if (ND->isInline()) {
44+
return isStdOrPosixImpl(ND->getParent());
45+
}
46+
47+
if (!ND->getParent()->getRedeclContext()->isTranslationUnit())
48+
return false;
49+
50+
const IdentifierInfo *II = ND->getIdentifier();
51+
return II && (II->isStr("std") || II->isStr("posix"));
52+
}
53+
54+
AST_MATCHER(Decl, isInStdOrPosixNS) {
55+
for (const auto *Ctx = Node.getDeclContext(); Ctx; Ctx = Ctx->getParent()) {
56+
if (isStdOrPosixImpl(Ctx))
57+
return true;
58+
}
59+
return false;
60+
}
61+
3862
} // namespace
3963

4064
namespace clang::tidy::cert {
4165

4266
void DontModifyStdNamespaceCheck::registerMatchers(MatchFinder *Finder) {
4367
auto HasStdParent =
4468
hasDeclContext(namespaceDecl(hasAnyName("std", "posix"),
45-
unless(hasParent(namespaceDecl())))
69+
unless(hasDeclContext(namespaceDecl())))
4670
.bind("nmspc"));
47-
auto UserDefinedType = qualType(
48-
hasUnqualifiedDesugaredType(tagType(unless(hasDeclaration(tagDecl(
49-
hasAncestor(namespaceDecl(hasAnyName("std", "posix"),
50-
unless(hasParent(namespaceDecl()))))))))));
71+
auto UserDefinedType = qualType(hasUnqualifiedDesugaredType(
72+
tagType(unless(hasDeclaration(tagDecl(isInStdOrPosixNS()))))));
5173
auto HasNoProgramDefinedTemplateArgument = unless(
5274
hasAnyTemplateArgumentIncludingPack(refersToType(UserDefinedType)));
5375
auto InsideStdClassOrClassTemplateSpecialization = hasDeclContext(

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ Improvements to clang-query
8888
Improvements to clang-tidy
8989
--------------------------
9090

91+
- It no longer processes declarations from system headers by default, greatly
92+
improving performance (up to 10x speed-up). This behavior is disabled if the
93+
`SystemHeaders` option is enabled.
94+
9195
New checks
9296
^^^^^^^^^^
9397

clang-tools-extra/test/clang-tidy/checkers/readability/identifier-naming-anon-record-fields.cpp

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,24 +33,6 @@
3333
// RUN: readability-identifier-naming.LocalConstantPointerPrefix: 'lc_', \
3434
// RUN: }}'
3535

36-
static union {
37-
int global;
38-
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: invalid case style for global variable 'global'
39-
// CHECK-FIXES: {{^}} int g_global;{{$}}
40-
41-
const int global_const;
42-
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: invalid case style for global constant 'global_const'
43-
// CHECK-FIXES: {{^}} const int GLOBAL_CONST;{{$}}
44-
45-
int *global_ptr;
46-
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: invalid case style for global pointer 'global_ptr'
47-
// CHECK-FIXES: {{^}} int *GlobalPtr_Ptr;{{$}}
48-
49-
int *const global_const_ptr;
50-
// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: invalid case style for global constant pointer 'global_const_ptr'
51-
// CHECK-FIXES: {{^}} int *const GLOBAL_CONST_PTR_Ptr;{{$}}
52-
};
53-
5436
namespace ns {
5537

5638
static union {

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

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,12 @@ 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)
76-
// CHECK3: Use -header-filter=.* {{.*}}
7772
// CHECK3-QUIET-NOT: Suppressed
7873
// CHECK4-NOT: Suppressed {{.*}} warnings
79-
// CHECK4-NOT: Use -header-filter=.* {{.*}}
8074
// CHECK4-QUIET-NOT: Suppressed
81-
// CHECK6: Suppressed 2 warnings (2 in non-user code)
8275
// CHECK6: Use -header-filter=.* {{.*}}
8376

8477
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

0 commit comments

Comments
 (0)