#include "SlicingCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/RecordLayout.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
using namespace clang::ast_matchers;
namespace clang::tidy::cppcoreguidelines {
void SlicingCheck::registerMatchers(MatchFinder *Finder) {
const auto OfBaseClass = ofClass(cxxRecordDecl().bind("BaseDecl"));
const auto IsDerivedFromBaseDecl =
cxxRecordDecl(isDerivedFrom(equalsBoundNode("BaseDecl")))
.bind("DerivedDecl");
const auto HasTypeDerivedFromBaseDecl =
anyOf(hasType(IsDerivedFromBaseDecl),
hasType(references(IsDerivedFromBaseDecl)));
const auto IsCallToBaseClass = hasParent(cxxConstructorDecl(
ofClass(isSameOrDerivedFrom(equalsBoundNode("DerivedDecl"))),
hasAnyConstructorInitializer(allOf(
isBaseInitializer(), withInitializer(equalsBoundNode("Call"))))));
const auto SlicesObjectInAssignment =
callExpr(expr().bind("Call"),
callee(cxxMethodDecl(anyOf(isCopyAssignmentOperator(),
isMoveAssignmentOperator()),
OfBaseClass)),
hasArgument(1, HasTypeDerivedFromBaseDecl));
const auto SlicesObjectInCtor = cxxConstructExpr(
expr().bind("Call"),
hasDeclaration(cxxConstructorDecl(
anyOf(isCopyConstructor(), isMoveConstructor()), OfBaseClass)),
hasArgument(0, HasTypeDerivedFromBaseDecl),
unless(IsCallToBaseClass));
Finder->addMatcher(
traverse(TK_AsIs, expr(SlicesObjectInAssignment).bind("Call")), this);
Finder->addMatcher(traverse(TK_AsIs, SlicesObjectInCtor), this);
}
void SlicingCheck::diagnoseSlicedOverriddenMethods(
const Expr &Call, const CXXRecordDecl &DerivedDecl,
const CXXRecordDecl &BaseDecl) {
if (DerivedDecl.getCanonicalDecl() == BaseDecl.getCanonicalDecl())
return;
for (const auto *Method : DerivedDecl.methods()) {
if (isa<CXXConstructorDecl>(Method) || isa<CXXDestructorDecl>(Method))
continue;
if (Method->size_overridden_methods() > 0) {
diag(Call.getExprLoc(),
"slicing object from type %0 to %1 discards override %2")
<< &DerivedDecl << &BaseDecl << Method;
}
}
for (const auto &Base : DerivedDecl.bases()) {
if (const auto *BaseRecord = Base.getType()->getAsCXXRecordDecl()) {
if (BaseRecord->isCompleteDefinition())
diagnoseSlicedOverriddenMethods(Call, *BaseRecord, BaseDecl);
}
}
}
void SlicingCheck::check(const MatchFinder::MatchResult &Result) {
const auto *BaseDecl = Result.Nodes.getNodeAs<CXXRecordDecl>("BaseDecl");
const auto *DerivedDecl =
Result.Nodes.getNodeAs<CXXRecordDecl>("DerivedDecl");
const auto *Call = Result.Nodes.getNodeAs<Expr>("Call");
assert(BaseDecl != nullptr);
assert(DerivedDecl != nullptr);
assert(Call != nullptr);
diagnoseSlicedOverriddenMethods(*Call, *DerivedDecl, *BaseDecl);
const auto &BaseLayout =
BaseDecl->getASTContext().getASTRecordLayout(BaseDecl);
const auto &DerivedLayout =
DerivedDecl->getASTContext().getASTRecordLayout(DerivedDecl);
const CharUnits StateSize =
DerivedLayout.getDataSize() - BaseLayout.getDataSize();
if (StateSize.isPositive()) {
diag(Call->getExprLoc(), "slicing object from type %0 to %1 discards "
"%2 bytes of state")
<< DerivedDecl << BaseDecl << static_cast<int>(StateSize.getQuantity());
}
}
}