1 | //===--- SlicingCheck.cpp - clang-tidy-------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "SlicingCheck.h" |
10 | #include "clang/AST/ASTContext.h" |
11 | #include "clang/AST/RecordLayout.h" |
12 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
13 | #include "clang/ASTMatchers/ASTMatchers.h" |
14 | |
15 | using namespace clang::ast_matchers; |
16 | |
17 | namespace clang::tidy::cppcoreguidelines { |
18 | |
19 | void SlicingCheck::registerMatchers(MatchFinder *Finder) { |
20 | // When we see: |
21 | // class B : public A { ... }; |
22 | // A a; |
23 | // B b; |
24 | // a = b; |
25 | // The assignment is OK if: |
26 | // - the assignment operator is defined as taking a B as second parameter, |
27 | // or |
28 | // - B does not define any additional members (either variables or |
29 | // overrides) wrt A. |
30 | // |
31 | // The same holds for copy ctor calls. This also captures stuff like: |
32 | // void f(A a); |
33 | // f(b); |
34 | |
35 | // Helpers. |
36 | const auto OfBaseClass = ofClass(InnerMatcher: cxxRecordDecl().bind(ID: "BaseDecl" )); |
37 | const auto IsDerivedFromBaseDecl = |
38 | cxxRecordDecl(isDerivedFrom(Base: equalsBoundNode(ID: "BaseDecl" ))) |
39 | .bind(ID: "DerivedDecl" ); |
40 | const auto HasTypeDerivedFromBaseDecl = |
41 | anyOf(hasType(InnerMatcher: IsDerivedFromBaseDecl), |
42 | hasType(InnerMatcher: references(InnerMatcher: IsDerivedFromBaseDecl))); |
43 | const auto IsCallToBaseClass = hasParent(cxxConstructorDecl( |
44 | ofClass(InnerMatcher: isSameOrDerivedFrom(Base: equalsBoundNode(ID: "DerivedDecl" ))), |
45 | hasAnyConstructorInitializer(InnerMatcher: allOf( |
46 | isBaseInitializer(), withInitializer(InnerMatcher: equalsBoundNode(ID: "Call" )))))); |
47 | |
48 | // Assignment slicing: "a = b;" and "a = std::move(b);" variants. |
49 | const auto SlicesObjectInAssignment = |
50 | callExpr(expr().bind(ID: "Call" ), |
51 | callee(InnerMatcher: cxxMethodDecl(anyOf(isCopyAssignmentOperator(), |
52 | isMoveAssignmentOperator()), |
53 | OfBaseClass)), |
54 | hasArgument(N: 1, InnerMatcher: HasTypeDerivedFromBaseDecl)); |
55 | |
56 | // Construction slicing: "A a{b};" and "f(b);" variants. Note that in case of |
57 | // slicing the letter will create a temporary and therefore call a ctor. |
58 | const auto SlicesObjectInCtor = cxxConstructExpr( |
59 | expr().bind(ID: "Call" ), |
60 | hasDeclaration(InnerMatcher: cxxConstructorDecl( |
61 | anyOf(isCopyConstructor(), isMoveConstructor()), OfBaseClass)), |
62 | hasArgument(N: 0, InnerMatcher: HasTypeDerivedFromBaseDecl), |
63 | // We need to disable matching on the call to the base copy/move |
64 | // constructor in DerivedDecl's constructors. |
65 | unless(IsCallToBaseClass)); |
66 | |
67 | Finder->addMatcher( |
68 | NodeMatch: traverse(TK: TK_AsIs, InnerMatcher: expr(SlicesObjectInAssignment).bind(ID: "Call" )), Action: this); |
69 | Finder->addMatcher(NodeMatch: traverse(TK: TK_AsIs, InnerMatcher: SlicesObjectInCtor), Action: this); |
70 | } |
71 | |
72 | /// Warns on methods overridden in DerivedDecl with respect to BaseDecl. |
73 | /// FIXME: this warns on all overrides outside of the sliced path in case of |
74 | /// multiple inheritance. |
75 | void SlicingCheck::diagnoseSlicedOverriddenMethods( |
76 | const Expr &Call, const CXXRecordDecl &DerivedDecl, |
77 | const CXXRecordDecl &BaseDecl) { |
78 | if (DerivedDecl.getCanonicalDecl() == BaseDecl.getCanonicalDecl()) |
79 | return; |
80 | for (const auto *Method : DerivedDecl.methods()) { |
81 | // Virtual destructors are OK. We're ignoring constructors since they are |
82 | // tagged as overrides. |
83 | if (isa<CXXConstructorDecl>(Val: Method) || isa<CXXDestructorDecl>(Val: Method)) |
84 | continue; |
85 | if (Method->size_overridden_methods() > 0) { |
86 | diag(Loc: Call.getExprLoc(), |
87 | Description: "slicing object from type %0 to %1 discards override %2" ) |
88 | << &DerivedDecl << &BaseDecl << Method; |
89 | } |
90 | } |
91 | // Recursively process bases. |
92 | for (const auto &Base : DerivedDecl.bases()) { |
93 | if (const auto *BaseRecordType = Base.getType()->getAs<RecordType>()) { |
94 | if (const auto *BaseRecord = cast_or_null<CXXRecordDecl>( |
95 | Val: BaseRecordType->getDecl()->getDefinition())) |
96 | diagnoseSlicedOverriddenMethods(Call, DerivedDecl: *BaseRecord, BaseDecl); |
97 | } |
98 | } |
99 | } |
100 | |
101 | void SlicingCheck::check(const MatchFinder::MatchResult &Result) { |
102 | const auto *BaseDecl = Result.Nodes.getNodeAs<CXXRecordDecl>(ID: "BaseDecl" ); |
103 | const auto *DerivedDecl = |
104 | Result.Nodes.getNodeAs<CXXRecordDecl>(ID: "DerivedDecl" ); |
105 | const auto *Call = Result.Nodes.getNodeAs<Expr>(ID: "Call" ); |
106 | assert(BaseDecl != nullptr); |
107 | assert(DerivedDecl != nullptr); |
108 | assert(Call != nullptr); |
109 | |
110 | // Warn when slicing the vtable. |
111 | // We're looking through all the methods in the derived class and see if they |
112 | // override some methods in the base class. |
113 | // It's not enough to just test whether the class is polymorphic because we |
114 | // would be fine slicing B to A if no method in B (or its bases) overrides |
115 | // anything in A: |
116 | // class A { virtual void f(); }; |
117 | // class B : public A {}; |
118 | // because in that case calling A::f is the same as calling B::f. |
119 | diagnoseSlicedOverriddenMethods(Call: *Call, DerivedDecl: *DerivedDecl, BaseDecl: *BaseDecl); |
120 | |
121 | // Warn when slicing member variables. |
122 | const auto &BaseLayout = |
123 | BaseDecl->getASTContext().getASTRecordLayout(BaseDecl); |
124 | const auto &DerivedLayout = |
125 | DerivedDecl->getASTContext().getASTRecordLayout(DerivedDecl); |
126 | const CharUnits StateSize = |
127 | DerivedLayout.getDataSize() - BaseLayout.getDataSize(); |
128 | if (StateSize.isPositive()) { |
129 | diag(Loc: Call->getExprLoc(), Description: "slicing object from type %0 to %1 discards " |
130 | "%2 bytes of state" ) |
131 | << DerivedDecl << BaseDecl << static_cast<int>(StateSize.getQuantity()); |
132 | } |
133 | } |
134 | |
135 | } // namespace clang::tidy::cppcoreguidelines |
136 | |