1 //===--- DanglingHandleCheck.cpp - clang-tidy------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "DanglingHandleCheck.h" 10 #include "../utils/Matchers.h" 11 #include "../utils/OptionsUtils.h" 12 #include "clang/AST/ASTContext.h" 13 #include "clang/ASTMatchers/ASTMatchFinder.h" 14 15 using namespace clang::ast_matchers; 16 using namespace clang::tidy::matchers; 17 18 namespace clang::tidy::bugprone { 19 20 namespace { 21 22 ast_matchers::internal::BindableMatcher<Stmt> 23 handleFrom(const ast_matchers::internal::Matcher<RecordDecl> &IsAHandle, 24 const ast_matchers::internal::Matcher<Expr> &Arg) { 25 return expr( 26 anyOf(cxxConstructExpr(hasDeclaration(cxxMethodDecl(ofClass(IsAHandle))), 27 hasArgument(0, Arg)), 28 cxxMemberCallExpr(hasType(hasUnqualifiedDesugaredType(recordType( 29 hasDeclaration(cxxRecordDecl(IsAHandle))))), 30 callee(memberExpr(member(cxxConversionDecl()))), 31 on(Arg)))); 32 } 33 34 ast_matchers::internal::Matcher<Stmt> handleFromTemporaryValue( 35 const ast_matchers::internal::Matcher<RecordDecl> &IsAHandle) { 36 37 const auto TemporaryExpr = anyOf( 38 cxxBindTemporaryExpr(), 39 cxxFunctionalCastExpr( 40 hasCastKind(CK_ConstructorConversion), 41 hasSourceExpression(ignoringParenImpCasts(cxxBindTemporaryExpr())))); 42 // If a ternary operator returns a temporary value, then both branches hold a 43 // temporary value. If one of them is not a temporary then it must be copied 44 // into one to satisfy the type of the operator. 45 const auto TemporaryTernary = conditionalOperator( 46 hasTrueExpression(ignoringParenImpCasts(TemporaryExpr)), 47 hasFalseExpression(ignoringParenImpCasts(TemporaryExpr))); 48 49 return handleFrom(IsAHandle, anyOf(TemporaryExpr, TemporaryTernary)); 50 } 51 52 ast_matchers::internal::Matcher<RecordDecl> isASequence() { 53 return hasAnyName("::std::deque", "::std::forward_list", "::std::list", 54 "::std::vector"); 55 } 56 57 ast_matchers::internal::Matcher<RecordDecl> isASet() { 58 return hasAnyName("::std::set", "::std::multiset", "::std::unordered_set", 59 "::std::unordered_multiset"); 60 } 61 62 ast_matchers::internal::Matcher<RecordDecl> isAMap() { 63 return hasAnyName("::std::map", "::std::multimap", "::std::unordered_map", 64 "::std::unordered_multimap"); 65 } 66 67 ast_matchers::internal::BindableMatcher<Stmt> makeContainerMatcher( 68 const ast_matchers::internal::Matcher<RecordDecl> &IsAHandle) { 69 // This matcher could be expanded to detect: 70 // - Constructors: eg. vector<string_view>(3, string("A")); 71 // - emplace*(): This requires a different logic to determine that 72 // the conversion will happen inside the container. 73 // - map's insert: This requires detecting that the pair conversion triggers 74 // the bug. A little more complicated than what we have now. 75 return callExpr( 76 hasAnyArgument( 77 ignoringParenImpCasts(handleFromTemporaryValue(IsAHandle))), 78 anyOf( 79 // For sequences: assign, push_back, resize. 80 cxxMemberCallExpr( 81 callee(functionDecl(hasAnyName("assign", "push_back", "resize"))), 82 on(expr(hasType(hasUnqualifiedDesugaredType( 83 recordType(hasDeclaration(recordDecl(isASequence())))))))), 84 // For sequences and sets: insert. 85 cxxMemberCallExpr(callee(functionDecl(hasName("insert"))), 86 on(expr(hasType(hasUnqualifiedDesugaredType( 87 recordType(hasDeclaration(recordDecl( 88 anyOf(isASequence(), isASet()))))))))), 89 // For maps: operator[]. 90 cxxOperatorCallExpr(callee(cxxMethodDecl(ofClass(isAMap()))), 91 hasOverloadedOperatorName("[]")))); 92 } 93 94 } // anonymous namespace 95 96 DanglingHandleCheck::DanglingHandleCheck(StringRef Name, 97 ClangTidyContext *Context) 98 : ClangTidyCheck(Name, Context), 99 HandleClasses(utils::options::parseStringList(Options.get( 100 "HandleClasses", "std::basic_string_view;std::experimental::basic_" 101 "string_view;std::span"))), 102 IsAHandle(cxxRecordDecl(hasAnyName(HandleClasses)).bind("handle")) {} 103 104 void DanglingHandleCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 105 Options.store(Opts, "HandleClasses", 106 utils::options::serializeStringList(HandleClasses)); 107 } 108 109 void DanglingHandleCheck::registerMatchersForVariables(MatchFinder *Finder) { 110 const auto ConvertedHandle = handleFromTemporaryValue(IsAHandle); 111 112 // Find 'Handle foo(ReturnsAValue());', 'Handle foo = ReturnsAValue();' 113 Finder->addMatcher( 114 varDecl(hasType(hasUnqualifiedDesugaredType( 115 recordType(hasDeclaration(cxxRecordDecl(IsAHandle))))), 116 unless(parmVarDecl()), 117 hasInitializer( 118 exprWithCleanups(ignoringElidableConstructorCall(has( 119 ignoringParenImpCasts(ConvertedHandle)))) 120 .bind("bad_stmt"))), 121 this); 122 123 // Find 'foo = ReturnsAValue(); // foo is Handle' 124 Finder->addMatcher( 125 traverse(TK_AsIs, 126 cxxOperatorCallExpr(callee(cxxMethodDecl(ofClass(IsAHandle))), 127 hasOverloadedOperatorName("="), 128 hasArgument(1, ConvertedHandle)) 129 .bind("bad_stmt")), 130 this); 131 132 // Container insertions that will dangle. 133 Finder->addMatcher( 134 traverse(TK_AsIs, makeContainerMatcher(IsAHandle).bind("bad_stmt")), 135 this); 136 } 137 138 void DanglingHandleCheck::registerMatchersForReturn(MatchFinder *Finder) { 139 // Return a local. 140 Finder->addMatcher( 141 traverse(TK_AsIs, 142 returnStmt( 143 // The AST contains two constructor calls: 144 // 1. Value to Handle conversion. 145 // 2. Handle copy construction (elided in C++17+). 146 // We have to match both. 147 has(ignoringImplicit(ignoringElidableConstructorCall( 148 ignoringImplicit(handleFrom( 149 IsAHandle, 150 declRefExpr(to(varDecl( 151 // Is function scope ... 152 hasAutomaticStorageDuration(), 153 // ... and it is a local array or Value. 154 anyOf(hasType(arrayType()), 155 hasType(hasUnqualifiedDesugaredType( 156 recordType(hasDeclaration(recordDecl( 157 unless(IsAHandle))))))))))))))), 158 // Temporary fix for false positives inside lambdas. 159 unless(hasAncestor(lambdaExpr()))) 160 .bind("bad_stmt")), 161 this); 162 163 // Return a temporary. 164 Finder->addMatcher( 165 traverse(TK_AsIs, 166 returnStmt(has(exprWithCleanups(ignoringElidableConstructorCall( 167 has(ignoringParenImpCasts( 168 handleFromTemporaryValue(IsAHandle))))))) 169 .bind("bad_stmt")), 170 this); 171 } 172 173 void DanglingHandleCheck::registerMatchers(MatchFinder *Finder) { 174 registerMatchersForVariables(Finder); 175 registerMatchersForReturn(Finder); 176 } 177 178 void DanglingHandleCheck::check(const MatchFinder::MatchResult &Result) { 179 auto *Handle = Result.Nodes.getNodeAs<CXXRecordDecl>("handle"); 180 diag(Result.Nodes.getNodeAs<Stmt>("bad_stmt")->getBeginLoc(), 181 "%0 outlives its value") 182 << Handle->getQualifiedNameAsString(); 183 } 184 185 } // namespace clang::tidy::bugprone 186