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