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 varDecl( 121 hasType(hasUnqualifiedDesugaredType( 122 recordType(hasDeclaration(cxxRecordDecl(IsAHandle))))), 123 unless(parmVarDecl()), 124 hasInitializer(exprWithCleanups(has(ignoringParenImpCasts(handleFrom( 125 IsAHandle, ConvertedHandle)))) 126 .bind("bad_stmt"))), 127 this); 128 // Find 'foo = ReturnsAValue(); // foo is Handle' 129 Finder->addMatcher( 130 cxxOperatorCallExpr(callee(cxxMethodDecl(ofClass(IsAHandle))), 131 hasOverloadedOperatorName("="), 132 hasArgument(1, ConvertedHandle)) 133 .bind("bad_stmt"), 134 this); 135 136 // Container insertions that will dangle. 137 Finder->addMatcher(makeContainerMatcher(IsAHandle).bind("bad_stmt"), this); 138 } 139 140 void DanglingHandleCheck::registerMatchersForReturn(MatchFinder *Finder) { 141 // Return a local. 142 Finder->addMatcher( 143 returnStmt( 144 // The AST contains two constructor calls: 145 // 1. Value to Handle conversion. 146 // 2. Handle copy construction. 147 // We have to match both. 148 has(ignoringImplicit(handleFrom( 149 IsAHandle, 150 handleFrom(IsAHandle, 151 declRefExpr(to(varDecl( 152 // Is function scope ... 153 hasAutomaticStorageDuration(), 154 // ... and it is a local array or Value. 155 anyOf(hasType(arrayType()), 156 hasType(hasUnqualifiedDesugaredType( 157 recordType(hasDeclaration(recordDecl( 158 unless(IsAHandle)))))))))))))), 159 // Temporary fix for false positives inside lambdas. 160 unless(hasAncestor(lambdaExpr()))) 161 .bind("bad_stmt"), 162 this); 163 164 // Return a temporary. 165 Finder->addMatcher( 166 returnStmt(has(exprWithCleanups(has(ignoringParenImpCasts(handleFrom( 167 IsAHandle, handleFromTemporaryValue(IsAHandle))))))) 168 .bind("bad_stmt"), 169 this); 170 } 171 172 void DanglingHandleCheck::registerMatchers(MatchFinder *Finder) { 173 registerMatchersForVariables(Finder); 174 registerMatchersForReturn(Finder); 175 } 176 177 void DanglingHandleCheck::check(const MatchFinder::MatchResult &Result) { 178 auto *Handle = Result.Nodes.getNodeAs<CXXRecordDecl>("handle"); 179 diag(Result.Nodes.getNodeAs<Stmt>("bad_stmt")->getBeginLoc(), 180 "%0 outlives its value") 181 << Handle->getQualifiedNameAsString(); 182 } 183 184 } // namespace bugprone 185 } // namespace tidy 186 } // namespace clang 187