115 lines
4.4 KiB
C++
115 lines
4.4 KiB
C++
/* 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
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#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.
|
|
}
|