• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
registerMatchers(MatchFinder * Finder)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.
DiagnoseSlicedOverriddenMethods(const Expr & Call,const CXXRecordDecl & DerivedDecl,const CXXRecordDecl & BaseDecl)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 
check(const MatchFinder::MatchResult & Result)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