clang-tools 6.0.0

SlicingCheck.cpp
Go to the documentation of this file.
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 CharUnits StateSize =
126 DerivedLayout.getDataSize() - BaseLayout.getDataSize();
127 if (StateSize.isPositive()) {
128 diag(Call->getExprLoc(), "slicing object from type %0 to %1 discards "
129 "%2 bytes of state")
130 << DerivedDecl << BaseDecl << static_cast<int>(StateSize.getQuantity());
131 }
132 }
133
134 } // namespace cppcoreguidelines
135 } // namespace tidy
136 } // namespace clang
clang::ast_matchers
Definition: ClangTidyDiagnosticConsumer.h:27
SlicingCheck.h
clang
Definition: AndroidTidyModule.cpp:28