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