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