clang-tools  16.0.0git
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(traverse(TK_AsIs, expr(anyOf(SlicesObjectInAssignment,
66  SlicesObjectInCtor))
67  .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
Base
std::unique_ptr< GlobalCompilationDatabase > Base
Definition: GlobalCompilationDatabaseTests.cpp:85
clang::ast_matchers
Definition: AbseilMatcher.h:14
SlicingCheck.h
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::clangd::check
bool check(llvm::StringRef File, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts)
Definition: Check.cpp:418