|  | //===--- DanglingHandleCheck.cpp - clang-tidy------------------------------===// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "DanglingHandleCheck.h" | 
|  | #include "../utils/Matchers.h" | 
|  | #include "../utils/OptionsUtils.h" | 
|  | #include "clang/AST/ASTContext.h" | 
|  | #include "clang/ASTMatchers/ASTMatchFinder.h" | 
|  |  | 
|  | using namespace clang::ast_matchers; | 
|  | using namespace clang::tidy::matchers; | 
|  |  | 
|  | namespace clang::tidy::bugprone { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | ast_matchers::internal::BindableMatcher<Stmt> | 
|  | handleFrom(const ast_matchers::internal::Matcher<RecordDecl> &IsAHandle, | 
|  | const ast_matchers::internal::Matcher<Expr> &Arg) { | 
|  | return expr( | 
|  | anyOf(cxxConstructExpr(hasDeclaration(cxxMethodDecl(ofClass(IsAHandle))), | 
|  | hasArgument(0, Arg)), | 
|  | cxxMemberCallExpr(hasType(hasUnqualifiedDesugaredType(recordType( | 
|  | hasDeclaration(cxxRecordDecl(IsAHandle))))), | 
|  | callee(memberExpr(member(cxxConversionDecl()))), | 
|  | on(Arg)))); | 
|  | } | 
|  |  | 
|  | ast_matchers::internal::Matcher<Stmt> handleFromTemporaryValue( | 
|  | const ast_matchers::internal::Matcher<RecordDecl> &IsAHandle) { | 
|  |  | 
|  | const auto TemporaryExpr = anyOf( | 
|  | cxxBindTemporaryExpr(), | 
|  | cxxFunctionalCastExpr( | 
|  | hasCastKind(CK_ConstructorConversion), | 
|  | hasSourceExpression(ignoringParenImpCasts(cxxBindTemporaryExpr())))); | 
|  | // If a ternary operator returns a temporary value, then both branches hold a | 
|  | // temporary value. If one of them is not a temporary then it must be copied | 
|  | // into one to satisfy the type of the operator. | 
|  | const auto TemporaryTernary = conditionalOperator( | 
|  | hasTrueExpression(ignoringParenImpCasts(TemporaryExpr)), | 
|  | hasFalseExpression(ignoringParenImpCasts(TemporaryExpr))); | 
|  |  | 
|  | return handleFrom(IsAHandle, anyOf(TemporaryExpr, TemporaryTernary)); | 
|  | } | 
|  |  | 
|  | ast_matchers::internal::Matcher<RecordDecl> isASequence() { | 
|  | return hasAnyName("::std::deque", "::std::forward_list", "::std::list", | 
|  | "::std::vector"); | 
|  | } | 
|  |  | 
|  | ast_matchers::internal::Matcher<RecordDecl> isASet() { | 
|  | return hasAnyName("::std::set", "::std::multiset", "::std::unordered_set", | 
|  | "::std::unordered_multiset"); | 
|  | } | 
|  |  | 
|  | ast_matchers::internal::Matcher<RecordDecl> isAMap() { | 
|  | return hasAnyName("::std::map", "::std::multimap", "::std::unordered_map", | 
|  | "::std::unordered_multimap"); | 
|  | } | 
|  |  | 
|  | ast_matchers::internal::BindableMatcher<Stmt> makeContainerMatcher( | 
|  | const ast_matchers::internal::Matcher<RecordDecl> &IsAHandle) { | 
|  | // This matcher could be expanded to detect: | 
|  | //  - Constructors: eg. vector<string_view>(3, string("A")); | 
|  | //  - emplace*(): This requires a different logic to determine that | 
|  | //                the conversion will happen inside the container. | 
|  | //  - map's insert: This requires detecting that the pair conversion triggers | 
|  | //                  the bug. A little more complicated than what we have now. | 
|  | return callExpr( | 
|  | hasAnyArgument( | 
|  | ignoringParenImpCasts(handleFromTemporaryValue(IsAHandle))), | 
|  | anyOf( | 
|  | // For sequences: assign, push_back, resize. | 
|  | cxxMemberCallExpr( | 
|  | callee(functionDecl(hasAnyName("assign", "push_back", "resize"))), | 
|  | on(expr(hasType(hasUnqualifiedDesugaredType( | 
|  | recordType(hasDeclaration(recordDecl(isASequence())))))))), | 
|  | // For sequences and sets: insert. | 
|  | cxxMemberCallExpr(callee(functionDecl(hasName("insert"))), | 
|  | on(expr(hasType(hasUnqualifiedDesugaredType( | 
|  | recordType(hasDeclaration(recordDecl( | 
|  | anyOf(isASequence(), isASet()))))))))), | 
|  | // For maps: operator[]. | 
|  | cxxOperatorCallExpr(callee(cxxMethodDecl(ofClass(isAMap()))), | 
|  | hasOverloadedOperatorName("[]")))); | 
|  | } | 
|  |  | 
|  | } // anonymous namespace | 
|  |  | 
|  | DanglingHandleCheck::DanglingHandleCheck(StringRef Name, | 
|  | ClangTidyContext *Context) | 
|  | : ClangTidyCheck(Name, Context), | 
|  | HandleClasses(utils::options::parseStringList(Options.get( | 
|  | "HandleClasses", | 
|  | "std::basic_string_view;std::experimental::basic_string_view"))), | 
|  | IsAHandle(cxxRecordDecl(hasAnyName(HandleClasses)).bind("handle")) {} | 
|  |  | 
|  | void DanglingHandleCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { | 
|  | Options.store(Opts, "HandleClasses", | 
|  | utils::options::serializeStringList(HandleClasses)); | 
|  | } | 
|  |  | 
|  | void DanglingHandleCheck::registerMatchersForVariables(MatchFinder *Finder) { | 
|  | const auto ConvertedHandle = handleFromTemporaryValue(IsAHandle); | 
|  |  | 
|  | // Find 'Handle foo(ReturnsAValue());', 'Handle foo = ReturnsAValue();' | 
|  | Finder->addMatcher( | 
|  | varDecl(hasType(hasUnqualifiedDesugaredType( | 
|  | recordType(hasDeclaration(cxxRecordDecl(IsAHandle))))), | 
|  | unless(parmVarDecl()), | 
|  | hasInitializer( | 
|  | exprWithCleanups(ignoringElidableConstructorCall(has( | 
|  | ignoringParenImpCasts(ConvertedHandle)))) | 
|  | .bind("bad_stmt"))), | 
|  | this); | 
|  |  | 
|  | // Find 'foo = ReturnsAValue();  // foo is Handle' | 
|  | Finder->addMatcher( | 
|  | traverse(TK_AsIs, | 
|  | cxxOperatorCallExpr(callee(cxxMethodDecl(ofClass(IsAHandle))), | 
|  | hasOverloadedOperatorName("="), | 
|  | hasArgument(1, ConvertedHandle)) | 
|  | .bind("bad_stmt")), | 
|  | this); | 
|  |  | 
|  | // Container insertions that will dangle. | 
|  | Finder->addMatcher( | 
|  | traverse(TK_AsIs, makeContainerMatcher(IsAHandle).bind("bad_stmt")), | 
|  | this); | 
|  | } | 
|  |  | 
|  | void DanglingHandleCheck::registerMatchersForReturn(MatchFinder *Finder) { | 
|  | // Return a local. | 
|  | Finder->addMatcher( | 
|  | traverse(TK_AsIs, | 
|  | returnStmt( | 
|  | // The AST contains two constructor calls: | 
|  | //   1. Value to Handle conversion. | 
|  | //   2. Handle copy construction (elided in C++17+). | 
|  | // We have to match both. | 
|  | has(ignoringImplicit(ignoringElidableConstructorCall( | 
|  | ignoringImplicit(handleFrom( | 
|  | IsAHandle, | 
|  | declRefExpr(to(varDecl( | 
|  | // Is function scope ... | 
|  | hasAutomaticStorageDuration(), | 
|  | // ... and it is a local array or Value. | 
|  | anyOf(hasType(arrayType()), | 
|  | hasType(hasUnqualifiedDesugaredType( | 
|  | recordType(hasDeclaration(recordDecl( | 
|  | unless(IsAHandle))))))))))))))), | 
|  | // Temporary fix for false positives inside lambdas. | 
|  | unless(hasAncestor(lambdaExpr()))) | 
|  | .bind("bad_stmt")), | 
|  | this); | 
|  |  | 
|  | // Return a temporary. | 
|  | Finder->addMatcher( | 
|  | traverse(TK_AsIs, | 
|  | returnStmt(has(exprWithCleanups(ignoringElidableConstructorCall( | 
|  | has(ignoringParenImpCasts( | 
|  | handleFromTemporaryValue(IsAHandle))))))) | 
|  | .bind("bad_stmt")), | 
|  | this); | 
|  | } | 
|  |  | 
|  | void DanglingHandleCheck::registerMatchers(MatchFinder *Finder) { | 
|  | registerMatchersForVariables(Finder); | 
|  | registerMatchersForReturn(Finder); | 
|  | } | 
|  |  | 
|  | void DanglingHandleCheck::check(const MatchFinder::MatchResult &Result) { | 
|  | auto *Handle = Result.Nodes.getNodeAs<CXXRecordDecl>("handle"); | 
|  | diag(Result.Nodes.getNodeAs<Stmt>("bad_stmt")->getBeginLoc(), | 
|  | "%0 outlives its value") | 
|  | << Handle->getQualifiedNameAsString(); | 
|  | } | 
|  |  | 
|  | } // namespace clang::tidy::bugprone |