/* 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 #else Optional #endif NonStdMoveChecker::makeFixItHint(const MatchFinder::MatchResult &Result, const Expr *const TargetExpr) { const auto *MaterializeExpr = Result.Nodes.getNodeAs(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(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(kOperatorCallExpr); if (OCE) { const auto *refPtrDecl = dyn_cast(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(kConstructExpr); if (CoE) { const auto *refPtrDecl = dyn_cast(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. }