1 //===--- SlicingCheck.cpp - clang-tidy-------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
15 using namespace clang::ast_matchers
;
17 namespace clang::tidy::cppcoreguidelines
{
19 void SlicingCheck::registerMatchers(MatchFinder
*Finder
) {
21 // class B : public A { ... };
25 // The assignment is OK if:
26 // - the assignment operator is defined as taking a B as second parameter,
28 // - B does not define any additional members (either variables or
31 // The same holds for copy ctor calls. This also captures stuff like:
36 const auto OfBaseClass
= ofClass(cxxRecordDecl().bind("BaseDecl"));
37 const auto IsDerivedFromBaseDecl
=
38 cxxRecordDecl(isDerivedFrom(equalsBoundNode("BaseDecl")))
40 const auto HasTypeDerivedFromBaseDecl
=
41 anyOf(hasType(IsDerivedFromBaseDecl
),
42 hasType(references(IsDerivedFromBaseDecl
)));
43 const auto IsCallToBaseClass
= hasParent(cxxConstructorDecl(
44 ofClass(isSameOrDerivedFrom(equalsBoundNode("DerivedDecl"))),
45 hasAnyConstructorInitializer(allOf(
46 isBaseInitializer(), withInitializer(equalsBoundNode("Call"))))));
48 // Assignment slicing: "a = b;" and "a = std::move(b);" variants.
49 const auto SlicesObjectInAssignment
=
50 callExpr(expr().bind("Call"),
51 callee(cxxMethodDecl(anyOf(isCopyAssignmentOperator(),
52 isMoveAssignmentOperator()),
54 hasArgument(1, HasTypeDerivedFromBaseDecl
));
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(
60 hasDeclaration(cxxConstructorDecl(
61 anyOf(isCopyConstructor(), isMoveConstructor()), OfBaseClass
)),
62 hasArgument(0, HasTypeDerivedFromBaseDecl
),
63 // We need to disable matching on the call to the base copy/move
64 // constructor in DerivedDecl's constructors.
65 unless(IsCallToBaseClass
));
68 traverse(TK_AsIs
, expr(SlicesObjectInAssignment
).bind("Call")), this);
69 Finder
->addMatcher(traverse(TK_AsIs
, SlicesObjectInCtor
), this);
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())
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
>(Method
) || isa
<CXXDestructorDecl
>(Method
))
85 if (Method
->size_overridden_methods() > 0) {
86 diag(Call
.getExprLoc(),
87 "slicing object from type %0 to %1 discards override %2")
88 << &DerivedDecl
<< &BaseDecl
<< Method
;
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 BaseRecordType
->getDecl()->getDefinition()))
96 diagnoseSlicedOverriddenMethods(Call
, *BaseRecord
, BaseDecl
);
101 void SlicingCheck::check(const MatchFinder::MatchResult
&Result
) {
102 const auto *BaseDecl
= Result
.Nodes
.getNodeAs
<CXXRecordDecl
>("BaseDecl");
103 const auto *DerivedDecl
=
104 Result
.Nodes
.getNodeAs
<CXXRecordDecl
>("DerivedDecl");
105 const auto *Call
= Result
.Nodes
.getNodeAs
<Expr
>("Call");
106 assert(BaseDecl
!= nullptr);
107 assert(DerivedDecl
!= nullptr);
108 assert(Call
!= nullptr);
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
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
, *DerivedDecl
, *BaseDecl
);
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(Call
->getExprLoc(), "slicing object from type %0 to %1 discards "
131 << DerivedDecl
<< BaseDecl
<< static_cast<int>(StateSize
.getQuantity());
135 } // namespace clang::tidy::cppcoreguidelines