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
15using namespace clang::ast_matchers;
16
17namespace clang::tidy::cppcoreguidelines {
18
19void SlicingCheck::registerMatchers(MatchFinder *Finder) {
20 // When we see:
21 // class B : public A { ... };
22 // A a;
23 // B b;
24 // a = b;
25 // The assignment is OK if:
26 // - the assignment operator is defined as taking a B as second parameter,
27 // or
28 // - B does not define any additional members (either variables or
29 // overrides) wrt A.
30 //
31 // The same holds for copy ctor calls. This also captures stuff like:
32 // void f(A a);
33 // f(b);
34
35 // Helpers.
36 const auto OfBaseClass = ofClass(InnerMatcher: cxxRecordDecl().bind(ID: "BaseDecl"));
37 const auto IsDerivedFromBaseDecl =
38 cxxRecordDecl(isDerivedFrom(Base: equalsBoundNode(ID: "BaseDecl")))
39 .bind(ID: "DerivedDecl");
40 const auto HasTypeDerivedFromBaseDecl =
41 anyOf(hasType(InnerMatcher: IsDerivedFromBaseDecl),
42 hasType(InnerMatcher: references(InnerMatcher: IsDerivedFromBaseDecl)));
43 const auto IsCallToBaseClass = hasParent(cxxConstructorDecl(
44 ofClass(InnerMatcher: isSameOrDerivedFrom(Base: equalsBoundNode(ID: "DerivedDecl"))),
45 hasAnyConstructorInitializer(InnerMatcher: allOf(
46 isBaseInitializer(), withInitializer(InnerMatcher: equalsBoundNode(ID: "Call"))))));
47
48 // Assignment slicing: "a = b;" and "a = std::move(b);" variants.
49 const auto SlicesObjectInAssignment =
50 callExpr(expr().bind(ID: "Call"),
51 callee(InnerMatcher: cxxMethodDecl(anyOf(isCopyAssignmentOperator(),
52 isMoveAssignmentOperator()),
53 OfBaseClass)),
54 hasArgument(N: 1, InnerMatcher: 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 expr().bind(ID: "Call"),
60 hasDeclaration(InnerMatcher: cxxConstructorDecl(
61 anyOf(isCopyConstructor(), isMoveConstructor()), OfBaseClass)),
62 hasArgument(N: 0, InnerMatcher: HasTypeDerivedFromBaseDecl),
63 // We need to disable matching on the call to the base copy/move
64 // constructor in DerivedDecl's constructors.
65 unless(IsCallToBaseClass));
66
67 Finder->addMatcher(
68 NodeMatch: traverse(TK: TK_AsIs, InnerMatcher: expr(SlicesObjectInAssignment).bind(ID: "Call")), Action: this);
69 Finder->addMatcher(NodeMatch: traverse(TK: TK_AsIs, InnerMatcher: SlicesObjectInCtor), Action: 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.
75void 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>(Val: Method) || isa<CXXDestructorDecl>(Val: Method))
84 continue;
85 if (Method->size_overridden_methods() > 0) {
86 diag(Loc: Call.getExprLoc(),
87 Description: "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 Val: BaseRecordType->getDecl()->getDefinition()))
96 diagnoseSlicedOverriddenMethods(Call, DerivedDecl: *BaseRecord, BaseDecl);
97 }
98 }
99}
100
101void SlicingCheck::check(const MatchFinder::MatchResult &Result) {
102 const auto *BaseDecl = Result.Nodes.getNodeAs<CXXRecordDecl>(ID: "BaseDecl");
103 const auto *DerivedDecl =
104 Result.Nodes.getNodeAs<CXXRecordDecl>(ID: "DerivedDecl");
105 const auto *Call = Result.Nodes.getNodeAs<Expr>(ID: "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: *Call, DerivedDecl: *DerivedDecl, BaseDecl: *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(Loc: Call->getExprLoc(), Description: "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 clang::tidy::cppcoreguidelines
136

source code of clang-tools-extra/clang-tidy/cppcoreguidelines/SlicingCheck.cpp