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 { 18 namespace tidy { 19 namespace cppcoreguidelines { 20 21 void SlicingCheck::registerMatchers(MatchFinder *Finder) { 22 // When we see: 23 // class B : public A { ... }; 24 // A a; 25 // B b; 26 // a = b; 27 // The assignment is OK if: 28 // - the assignment operator is defined as taking a B as second parameter, 29 // or 30 // - B does not define any additional members (either variables or 31 // overrides) wrt A. 32 // 33 // The same holds for copy ctor calls. This also captures stuff like: 34 // void f(A a); 35 // f(b); 36 37 // Helpers. 38 const auto OfBaseClass = ofClass(cxxRecordDecl().bind("BaseDecl")); 39 const auto IsDerivedFromBaseDecl = 40 cxxRecordDecl(isDerivedFrom(equalsBoundNode("BaseDecl"))) 41 .bind("DerivedDecl"); 42 const auto HasTypeDerivedFromBaseDecl = 43 anyOf(hasType(IsDerivedFromBaseDecl), 44 hasType(references(IsDerivedFromBaseDecl))); 45 const auto IsWithinDerivedCtor = 46 hasParent(cxxConstructorDecl(ofClass(equalsBoundNode("DerivedDecl")))); 47 48 // Assignment slicing: "a = b;" and "a = std::move(b);" variants. 49 const auto SlicesObjectInAssignment = 50 callExpr(callee(cxxMethodDecl(anyOf(isCopyAssignmentOperator(), 51 isMoveAssignmentOperator()), 52 OfBaseClass)), 53 hasArgument(1, HasTypeDerivedFromBaseDecl)); 54 55 // Construction slicing: "A a{b};" and "f(b);" variants. Note that in case of 56 // slicing the letter will create a temporary and therefore call a ctor. 57 const auto SlicesObjectInCtor = cxxConstructExpr( 58 hasDeclaration(cxxConstructorDecl( 59 anyOf(isCopyConstructor(), isMoveConstructor()), OfBaseClass)), 60 hasArgument(0, HasTypeDerivedFromBaseDecl), 61 // We need to disable matching on the call to the base copy/move 62 // constructor in DerivedDecl's constructors. 63 unless(IsWithinDerivedCtor)); 64 65 Finder->addMatcher( 66 traverse(ast_type_traits::TK_AsIs, 67 expr(anyOf(SlicesObjectInAssignment, SlicesObjectInCtor)) 68 .bind("Call")), 69 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>(Method) || isa<CXXDestructorDecl>(Method)) 84 continue; 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; 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 BaseRecordType->getDecl()->getDefinition())) 96 DiagnoseSlicedOverriddenMethods(Call, *BaseRecord, BaseDecl); 97 } 98 } 99 } 100 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); 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, *DerivedDecl, *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(Call->getExprLoc(), "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 cppcoreguidelines 136 } // namespace tidy 137 } // namespace clang 138