clang-tools 10.0.0

SlicingCheck.cpp
Go to the documentation of this file.
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 expr(anyOf(SlicesObjectInAssignment, SlicesObjectInCtor)).bind("Call"),
67 this);
68 }
69
70 /// Warns on methods overridden in DerivedDecl with respect to BaseDecl.
71 /// FIXME: this warns on all overrides outside of the sliced path in case of
72 /// multiple inheritance.
73 void SlicingCheck::DiagnoseSlicedOverriddenMethods(
74 const Expr &Call, const CXXRecordDecl &DerivedDecl,
75 const CXXRecordDecl &BaseDecl) {
76 if (DerivedDecl.getCanonicalDecl() == BaseDecl.getCanonicalDecl())
77 return;
78 for (const auto *Method : DerivedDecl.methods()) {
79 // Virtual destructors are OK. We're ignoring constructors since they are
80 // tagged as overrides.
81 if (isa<CXXConstructorDecl>(Method) || isa<CXXDestructorDecl>(Method))
82 continue;
83 if (Method->size_overridden_methods() > 0) {
84 diag(Call.getExprLoc(),
85 "slicing object from type %0 to %1 discards override %2")
86 << &DerivedDecl << &BaseDecl << Method;
87 }
88 }
89 // Recursively process bases.
90 for (const auto &Base : DerivedDecl.bases()) {
91 if (const auto *BaseRecordType = Base.getType()->getAs<RecordType>()) {
92 if (const auto *BaseRecord = cast_or_null<CXXRecordDecl>(
93 BaseRecordType->getDecl()->getDefinition()))
94 DiagnoseSlicedOverriddenMethods(Call, *BaseRecord, BaseDecl);
95 }
96 }
97 }
98
99 void SlicingCheck::check(const MatchFinder::MatchResult &Result) {
100 const auto *BaseDecl = Result.Nodes.getNodeAs<CXXRecordDecl>("BaseDecl");
101 const auto *DerivedDecl =
102 Result.Nodes.getNodeAs<CXXRecordDecl>("DerivedDecl");
103 const auto *Call = Result.Nodes.getNodeAs<Expr>("Call");
104 assert(BaseDecl != nullptr);
105 assert(DerivedDecl != nullptr);
106 assert(Call != nullptr);
107
108 // Warn when slicing the vtable.
109 // We're looking through all the methods in the derived class and see if they
110 // override some methods in the base class.
111 // It's not enough to just test whether the class is polymorphic because we
112 // would be fine slicing B to A if no method in B (or its bases) overrides
113 // anything in A:
114 // class A { virtual void f(); };
115 // class B : public A {};
116 // because in that case calling A::f is the same as calling B::f.
117 DiagnoseSlicedOverriddenMethods(*Call, *DerivedDecl, *BaseDecl);
118
119 // Warn when slicing member variables.
120 const auto &BaseLayout =
121 BaseDecl->getASTContext().getASTRecordLayout(BaseDecl);
122 const auto &DerivedLayout =
123 DerivedDecl->getASTContext().getASTRecordLayout(DerivedDecl);
124 const CharUnits StateSize =
125 DerivedLayout.getDataSize() - BaseLayout.getDataSize();
126 if (StateSize.isPositive()) {
127 diag(Call->getExprLoc(), "slicing object from type %0 to %1 discards "
128 "%2 bytes of state")
129 << DerivedDecl << BaseDecl << static_cast<int>(StateSize.getQuantity());
130 }
131 }
132
133 } // namespace cppcoreguidelines
134 } // namespace tidy
135 } // namespace clang
clang::ast_matchers
Definition: AbseilMatcher.h:14
SlicingCheck.h
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
Base
std::unique_ptr< GlobalCompilationDatabase > Base
Definition: GlobalCompilationDatabaseTests.cpp:83