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