Skip to content

Commit 49093c4

Browse files
IgnitionChuanqiXu9
andauthored
[clang-tidy] Fix performance-trivially-destructible with C++20 modules (llvm#178471)
When a class definition is seen through both a header include and a C++20 module import, destructors may appear multiple times in the AST's redeclaration chain. The original matcher used `isFirstDecl()` which fails in this scenario because the same declaration can appear as both first and non-first depending on the view. Replace `unless(isFirstDecl())` with `isOutOfLine()` which correctly identifies out-of-line definitions by checking whether the lexical context differs from the semantic context. Also update clang-tools-extra's lit.cfg.py to call `use_clang()` instead of `clang_setup()` to make the `%clang` substitution available for tests. Fixes llvm#178102 Co-authored-by: Chuanqi Xu <yedeng.yd@linux.alibaba.com>
1 parent 613c5b4 commit 49093c4

File tree

5 files changed

+78
-5
lines changed

5 files changed

+78
-5
lines changed

clang-tools-extra/clang-tidy/performance/TriviallyDestructibleCheck.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ namespace clang::tidy::performance {
2020

2121
namespace {
2222

23-
AST_MATCHER(Decl, isFirstDecl) { return Node.isFirstDecl(); }
23+
// We use isOutOfLine() to find out-of-line defaulted destructor definitions.
24+
// This is more robust than !isFirstDecl() because with C++20 modules, when a
25+
// class is visible through both a header include and a module import, its
26+
// declarations may appear multiple times in the redeclaration chain.
27+
AST_MATCHER(CXXMethodDecl, isOutOfLine) { return Node.isOutOfLine(); }
2428

2529
AST_MATCHER_P(CXXRecordDecl, hasBase, Matcher<QualType>, InnerMatcher) {
2630
return llvm::any_of(Node.bases(), [&](const CXXBaseSpecifier &BaseSpec) {
@@ -33,8 +37,8 @@ AST_MATCHER_P(CXXRecordDecl, hasBase, Matcher<QualType>, InnerMatcher) {
3337
void TriviallyDestructibleCheck::registerMatchers(MatchFinder *Finder) {
3438
Finder->addMatcher(
3539
cxxDestructorDecl(
36-
isDefaulted(),
37-
unless(anyOf(isFirstDecl(), isVirtual(),
40+
isDefaulted(), isOutOfLine(),
41+
unless(anyOf(isVirtual(),
3842
ofClass(cxxRecordDecl(
3943
anyOf(hasBase(unless(isTriviallyDestructible())),
4044
has(fieldDecl(unless(

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,11 @@ Changes in existing checks
404404

405405
- Fixes false negatives when using ``std::set`` from ``libstdc++``.
406406

407+
- Improved :doc:`performance-trivially-destructible
408+
<clang-tidy/checks/performance/trivially-destructible>` check by fixing
409+
false positives when a class is seen through both a header include and
410+
a C++20 module import.
411+
407412
- Improved :doc:`readability-container-size-empty
408413
<clang-tidy/checks/readability/container-size-empty>` check:
409414

clang-tools-extra/test/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ set(CLANG_TOOLS_TEST_DEPS
4444
clang-resource-headers
4545

4646
clang-tidy
47+
48+
# Some tests invoke clang directly (e.g., to precompile modules).
49+
clang
4750
)
4851

4952
# Add lit test dependencies.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir -p %t
3+
// RUN: split-file %s %t
4+
// RUN: %clang -std=c++20 -x c++-module --precompile %t/mymodule.cppm -o %t/mymodule.pcm -I%t
5+
// RUN: %check_clang_tidy -std=c++20 %t/main.cpp performance-trivially-destructible %t/out \
6+
// RUN: -- -- -std=c++20 -I%t -fmodule-file=mymodule=%t/mymodule.pcm
7+
8+
// Test: C++20 modules - class visible through both #include and import.
9+
//
10+
// When a class is defined in a header that is both #include'd directly and
11+
// included in a module's global module fragment, the class's destructor may
12+
// appear multiple times in the redeclaration chain.
13+
//
14+
// This test verifies:
15+
// 1. No false positive on in-line defaulted destructor (struct A)
16+
// 2. No false positive on implicit destructor (struct X)
17+
// 3. Correct warning on out-of-line defaulted destructor (struct B)
18+
//
19+
// We expect exactly 1 warning (for struct B's out-of-line destructor).
20+
21+
//--- header.h
22+
#pragma once
23+
24+
// Negative cases: with modules, the destructor's redeclaration chain may
25+
// contain multiple entries, which can trigger false positives if not handled
26+
// correctly.
27+
28+
// In-line defaulted destructor should NOT warn
29+
struct A {
30+
~A() = default;
31+
};
32+
33+
// Implicit destructor should NOT warn
34+
struct X {
35+
A a;
36+
};
37+
38+
// Positive case: out-of-line defaulted destructor SHOULD warn
39+
struct B {
40+
~B();
41+
int x;
42+
};
43+
44+
//--- mymodule.cppm
45+
module;
46+
#include "header.h"
47+
export module mymodule;
48+
49+
//--- main.cpp
50+
#include "header.h"
51+
import mymodule;
52+
53+
// CHECK-MESSAGES: header.h:19:5: warning: class 'B' can be made trivially destructible
54+
B::~B() = default; // to-be-removed
55+
// CHECK-MESSAGES: :[[@LINE-1]]:4: note: destructor definition is here
56+
// CHECK-FIXES: // to-be-removed
57+
58+
int main() {
59+
X x;
60+
B b;
61+
}

clang-tools-extra/test/lit.cfg.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@
4949
# test_exec_root: The root path where tests should be run.
5050
config.test_exec_root = os.path.join(config.clang_tools_binary_dir, "test")
5151

52-
# Tools need the same environment setup as clang (we don't need clang itself).
53-
llvm_config.clang_setup()
52+
# Set up clang for use in tests. Makes %clang substitution available.
53+
llvm_config.use_clang()
5454

5555
if config.clang_tidy_staticanalyzer:
5656
config.available_features.add("static-analyzer")

0 commit comments

Comments
 (0)