Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#include "NonStdMoveChecker.h"
#include "CustomMatchers.h"
#include "clang/Lex/Lexer.h"
constexpr const char *kConstructExpr = "construct";
constexpr const char *kOperatorCallExpr = "operator-call";
constexpr const char *kSourceExpr = "source-expr";
constexpr const char *kMaterializeExpr = "materialize-expr";
void NonStdMoveChecker::registerMatchers(MatchFinder *AstMatcher) {
// Assignment through forget
AstMatcher->addMatcher(
cxxOperatorCallExpr(
hasOverloadedOperatorName("="),
hasAnyArgument(materializeTemporaryExpr(
has(cxxBindTemporaryExpr(has(cxxMemberCallExpr(
has(memberExpr(member(hasName("forget")))),
on(expr().bind(kSourceExpr)))))))
.bind(kMaterializeExpr)))
.bind(kOperatorCallExpr),
this);
// Construction through forget
AstMatcher->addMatcher(
cxxConstructExpr(has(materializeTemporaryExpr(
has(cxxBindTemporaryExpr(has(cxxMemberCallExpr(
has(memberExpr(member(hasName("forget")))),
on(expr().bind(kSourceExpr)))))))
.bind(kMaterializeExpr)))
.bind(kConstructExpr),
this);
}
#if CLANG_VERSION_FULL >= 1600
std::optional<FixItHint>
#else
Optional<FixItHint>
#endif
NonStdMoveChecker::makeFixItHint(const MatchFinder::MatchResult &Result,
const Expr *const TargetExpr) {
const auto *MaterializeExpr = Result.Nodes.getNodeAs<Expr>(kMaterializeExpr);
// TODO: In principle, we should check here if TargetExpr if
// assignable/constructible from std::move(SourceExpr). Not sure how to do
// this. Currently, we only filter out the case where the targetTypeTemplate
// is already_AddRefed, where this is known to fail.
const auto *targetTypeTemplate = getNonTemplateSpecializedCXXRecordDecl(
TargetExpr->getType().getCanonicalType());
const auto *sourceTypeTemplate = getNonTemplateSpecializedCXXRecordDecl(
MaterializeExpr->getType().getCanonicalType());
if (targetTypeTemplate && sourceTypeTemplate) {
// TODO is there a better way to check this than by name? otherwise, the
// names probably are necessarily unique in the scope
if (targetTypeTemplate->getName() == sourceTypeTemplate->getName() &&
targetTypeTemplate->getName() == "already_AddRefed") {
return {};
}
}
const auto *SourceExpr = Result.Nodes.getNodeAs<Expr>(kSourceExpr);
const auto sourceText = Lexer::getSourceText(
CharSourceRange::getTokenRange(SourceExpr->getSourceRange()),
Result.Context->getSourceManager(), Result.Context->getLangOpts());
return FixItHint::CreateReplacement(MaterializeExpr->getSourceRange(),
("std::move(" + sourceText + ")").str());
}
void NonStdMoveChecker::check(const MatchFinder::MatchResult &Result) {
// TODO: Include source and target type name in messages.
const auto *OCE =
Result.Nodes.getNodeAs<CXXOperatorCallExpr>(kOperatorCallExpr);
if (OCE) {
const auto *refPtrDecl =
dyn_cast<const CXXRecordDecl>(OCE->getCalleeDecl()->getDeclContext());
const auto XFixItHint = makeFixItHint(Result, OCE);
// TODO: produce diagnostic but no FixItHint in this case?
if (XFixItHint) {
diag(OCE->getBeginLoc(), "non-standard move assignment to %0 obscures "
"move, use std::move instead")
<< refPtrDecl << *XFixItHint;
}
}
const auto *CoE = Result.Nodes.getNodeAs<CXXConstructExpr>(kConstructExpr);
if (CoE) {
const auto *refPtrDecl =
dyn_cast<const CXXRecordDecl>(CoE->getConstructor()->getDeclContext());
const auto XFixItHint = makeFixItHint(Result, CoE);
// TODO: produce diagnostic but no FixItHint in this case?
if (XFixItHint) {
diag(CoE->getBeginLoc(), "non-standard move construction of %0 obscures "
"move, use std::move instead")
<< refPtrDecl << *XFixItHint;
}
}
// TODO: What about swap calls immediately after default-construction? These
// can also be replaced by move-construction, but this may require
// control-flow analysis.
}