DanglingOnTemporaryChecker.cpp (10210B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "DanglingOnTemporaryChecker.h" 6 #include "CustomMatchers.h" 7 #include "VariableUsageHelpers.h" 8 9 void DanglingOnTemporaryChecker::registerMatchers(MatchFinder *AstMatcher) { 10 //////////////////////////////////////// 11 // Quick annotation conflict checkers // 12 //////////////////////////////////////// 13 14 AstMatcher->addMatcher( 15 // This is a matcher on a method declaration, 16 cxxMethodDecl( 17 // which is marked as no dangling on temporaries, 18 noDanglingOnTemporaries(), 19 20 // and which is && ref-qualified. 21 isRValueRefQualified(), 22 23 decl().bind("invalidMethodRefQualified")), 24 this); 25 26 AstMatcher->addMatcher( 27 // This is a matcher on a method declaration, 28 cxxMethodDecl( 29 // which is marked as no dangling on temporaries, 30 noDanglingOnTemporaries(), 31 32 // which returns a primitive type, 33 returns(builtinType()), 34 35 // and which doesn't return a pointer. 36 unless(returns(pointerType())), 37 38 decl().bind("invalidMethodPointer")), 39 this); 40 41 ////////////////// 42 // Main checker // 43 ////////////////// 44 45 auto hasParentCall = hasParent( 46 expr(anyOf(cxxOperatorCallExpr( 47 // If we're in a lamda, we may have an operator call 48 // expression ancestor in the AST, but the temporary we're 49 // matching against is not going to have the same lifetime 50 // as the constructor call. 51 unless(has(expr(ignoreTrivials(lambdaExpr())))), 52 expr().bind("parentOperatorCallExpr")), 53 callExpr( 54 // If we're in a lamda, we may have a call expression 55 // ancestor in the AST, but the temporary we're matching 56 // against is not going to have the same lifetime as the 57 // function call. 58 unless(has(expr(ignoreTrivials(lambdaExpr())))), 59 expr().bind("parentCallExpr")), 60 objcMessageExpr( 61 // If we're in a lamda, we may have an objc message 62 // expression ancestor in the AST, but the temporary we're 63 // matching against is not going to have the same lifetime 64 // as the function call. 65 unless(has(expr(ignoreTrivials(lambdaExpr())))), 66 expr().bind("parentObjCMessageExpr")), 67 cxxConstructExpr( 68 // If we're in a lamda, we may have a construct expression 69 // ancestor in the AST, but the temporary we're matching 70 // against is not going to have the same lifetime as the 71 // constructor call. 72 unless(has(expr(ignoreTrivials(lambdaExpr())))), 73 expr().bind("parentConstructExpr"))))); 74 75 AstMatcher->addMatcher( 76 // This is a matcher on a method call, 77 cxxMemberCallExpr( 78 // which is in first party code, 79 isFirstParty(), 80 81 // and which is performed on a temporary, 82 on(allOf(unless(hasType(pointerType())), isTemporary(), 83 // but which is not `this`. 84 unless(cxxThisExpr()))), 85 86 // and which is marked as no dangling on temporaries. 87 callee(cxxMethodDecl(noDanglingOnTemporaries())), 88 89 expr().bind("memberCallExpr"), 90 91 // We optionally match a parent call expression or a parent construct 92 // expression because using a temporary inside a call is fine as long 93 // as the pointer doesn't escape the function call. 94 anyOf( 95 // This is the case where the call is the direct parent, so we 96 // know that the member call expression is the argument. 97 allOf(hasParentCall, expr().bind("parentCallArg")), 98 99 // This is the case where the call is not the direct parent, so we 100 // get its child to know in which argument tree we are. 101 hasAncestor(expr(hasParentCall, expr().bind("parentCallArg"))), 102 // To make it optional. 103 anything())), 104 this); 105 } 106 107 void DanglingOnTemporaryChecker::check(const MatchFinder::MatchResult &Result) { 108 /////////////////////////////////////// 109 // Quick annotation conflict checker // 110 /////////////////////////////////////// 111 112 const char *ErrorInvalidRefQualified = "methods annotated with " 113 "MOZ_NO_DANGLING_ON_TEMPORARIES " 114 "cannot be && ref-qualified"; 115 116 const char *ErrorInvalidPointer = "methods annotated with " 117 "MOZ_NO_DANGLING_ON_TEMPORARIES must " 118 "return a pointer"; 119 120 if (auto InvalidRefQualified = 121 Result.Nodes.getNodeAs<CXXMethodDecl>("invalidMethodRefQualified")) { 122 diag(InvalidRefQualified->getLocation(), ErrorInvalidRefQualified, 123 DiagnosticIDs::Error); 124 return; 125 } 126 127 if (auto InvalidPointer = 128 Result.Nodes.getNodeAs<CXXMethodDecl>("invalidMethodPointer")) { 129 diag(InvalidPointer->getLocation(), ErrorInvalidPointer, 130 DiagnosticIDs::Error); 131 return; 132 } 133 134 ////////////////// 135 // Main checker // 136 ////////////////// 137 138 const char *Error = "calling `%0` on a temporary, potentially allowing use " 139 "after free of the raw pointer"; 140 141 const char *EscapeStmtNote = 142 "the raw pointer escapes the function scope here"; 143 144 const ObjCMessageExpr *ParentObjCMessageExpr = 145 Result.Nodes.getNodeAs<ObjCMessageExpr>("parentObjCMessageExpr"); 146 147 // We don't care about cases in ObjC message expressions. 148 if (ParentObjCMessageExpr) { 149 return; 150 } 151 152 const CXXMemberCallExpr *MemberCall = 153 Result.Nodes.getNodeAs<CXXMemberCallExpr>("memberCallExpr"); 154 155 const CallExpr *ParentCallExpr = 156 Result.Nodes.getNodeAs<CallExpr>("parentCallExpr"); 157 const CXXConstructExpr *ParentConstructExpr = 158 Result.Nodes.getNodeAs<CXXConstructExpr>("parentConstructExpr"); 159 const CXXOperatorCallExpr *ParentOperatorCallExpr = 160 Result.Nodes.getNodeAs<CXXOperatorCallExpr>("parentOperatorCallExpr"); 161 const Expr *ParentCallArg = Result.Nodes.getNodeAs<Expr>("parentCallArg"); 162 163 // Just in case. 164 if (!MemberCall) { 165 return; 166 } 167 168 // If we have a parent call, we check whether or not we escape the function 169 // being called. 170 if (ParentOperatorCallExpr || ParentCallExpr || ParentConstructExpr) { 171 // Just in case. 172 if (!ParentCallArg) { 173 return; 174 } 175 176 // No default constructor so we can't construct it using if/else. 177 auto FunctionEscapeData = 178 ParentOperatorCallExpr 179 ? escapesFunction(ParentCallArg, ParentOperatorCallExpr) 180 : ParentCallExpr 181 ? escapesFunction(ParentCallArg, ParentCallExpr) 182 : escapesFunction(ParentCallArg, ParentConstructExpr); 183 184 // If there was an error in the escapesFunction call. 185 if (std::error_code ec = FunctionEscapeData.getError()) { 186 // FIXME: For now we ignore the variadic case and just consider that the 187 // argument doesn't escape the function. Same for the case where we can't 188 // find the function declaration or if the function is builtin. 189 if (static_cast<EscapesFunctionError>(ec.value()) == 190 EscapesFunctionError::FunctionIsVariadic || 191 static_cast<EscapesFunctionError>(ec.value()) == 192 EscapesFunctionError::FunctionDeclNotFound || 193 static_cast<EscapesFunctionError>(ec.value()) == 194 EscapesFunctionError::FunctionIsBuiltin) { 195 return; 196 } 197 198 // We emit the internal checker error and return. 199 diag(MemberCall->getExprLoc(), 200 std::string(ec.category().name()) + " error: " + ec.message(), 201 DiagnosticIDs::Error); 202 return; 203 } 204 205 // We deconstruct the function escape data. 206 const Stmt *EscapeStmt; 207 const Decl *EscapeDecl; 208 std::tie(EscapeStmt, EscapeDecl) = *FunctionEscapeData; 209 210 // If we didn't escape a parent function, we're done: we don't emit any 211 // diagnostic. 212 if (!EscapeStmt || !EscapeDecl) { 213 return; 214 } 215 216 // We emit the error diagnostic indicating that we are calling the method 217 // temporary. 218 diag(MemberCall->getExprLoc(), Error, DiagnosticIDs::Error) 219 << MemberCall->getMethodDecl()->getName() 220 << MemberCall->getSourceRange(); 221 222 // We indicate the escape statement. 223 diag(EscapeStmt->getBeginLoc(), EscapeStmtNote, DiagnosticIDs::Note) 224 << EscapeStmt->getSourceRange(); 225 226 // We build the escape note along with its source range. 227 StringRef EscapeDeclNote; 228 SourceRange EscapeDeclRange; 229 if (isa<ParmVarDecl>(EscapeDecl)) { 230 EscapeDeclNote = "through the parameter declared here"; 231 EscapeDeclRange = EscapeDecl->getSourceRange(); 232 } else if (isa<VarDecl>(EscapeDecl)) { 233 EscapeDeclNote = "through the variable declared here"; 234 EscapeDeclRange = EscapeDecl->getSourceRange(); 235 } else if (isa<FieldDecl>(EscapeDecl)) { 236 EscapeDeclNote = "through the field declared here"; 237 EscapeDeclRange = EscapeDecl->getSourceRange(); 238 } else if (auto FuncDecl = dyn_cast<FunctionDecl>(EscapeDecl)) { 239 EscapeDeclNote = "through the return value of the function declared here"; 240 EscapeDeclRange = FuncDecl->getReturnTypeSourceRange(); 241 } else { 242 return; 243 } 244 245 // We emit the declaration note indicating through which decl the argument 246 // escapes. 247 diag(EscapeDecl->getLocation(), EscapeDeclNote, DiagnosticIDs::Note) 248 << EscapeDeclRange; 249 } else { 250 // We emit the error diagnostic indicating that we are calling the method 251 // temporary. 252 diag(MemberCall->getExprLoc(), Error, DiagnosticIDs::Error) 253 << MemberCall->getMethodDecl()->getName() 254 << MemberCall->getSourceRange(); 255 } 256 }