forked from mirrors/gecko-dev
		
	 e67b41e3c7
			
		
	
	
		e67b41e3c7
		
	
	
	
	
		
			
			Differential Revision: https://phabricator.services.mozilla.com/D63000 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			256 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			256 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "DanglingOnTemporaryChecker.h"
 | |
| #include "CustomMatchers.h"
 | |
| #include "VariableUsageHelpers.h"
 | |
| 
 | |
| void DanglingOnTemporaryChecker::registerMatchers(MatchFinder *AstMatcher) {
 | |
|   ////////////////////////////////////////
 | |
|   // Quick annotation conflict checkers //
 | |
|   ////////////////////////////////////////
 | |
| 
 | |
|   AstMatcher->addMatcher(
 | |
|       // This is a matcher on a method declaration,
 | |
|       cxxMethodDecl(
 | |
|           // which is marked as no dangling on temporaries,
 | |
|           noDanglingOnTemporaries(),
 | |
| 
 | |
|           // and which is && ref-qualified.
 | |
|           isRValueRefQualified(),
 | |
| 
 | |
|           decl().bind("invalidMethodRefQualified")),
 | |
|       this);
 | |
| 
 | |
|   AstMatcher->addMatcher(
 | |
|       // This is a matcher on a method declaration,
 | |
|       cxxMethodDecl(
 | |
|           // which is marked as no dangling on temporaries,
 | |
|           noDanglingOnTemporaries(),
 | |
| 
 | |
|           // which returns a primitive type,
 | |
|           returns(builtinType()),
 | |
| 
 | |
|           // and which doesn't return a pointer.
 | |
|           unless(returns(pointerType())),
 | |
| 
 | |
|           decl().bind("invalidMethodPointer")),
 | |
|       this);
 | |
| 
 | |
|   //////////////////
 | |
|   // Main checker //
 | |
|   //////////////////
 | |
| 
 | |
|   auto hasParentCall = hasParent(
 | |
|       expr(anyOf(cxxOperatorCallExpr(
 | |
|                      // If we're in a lamda, we may have an operator call
 | |
|                      // expression ancestor in the AST, but the temporary we're
 | |
|                      // matching against is not going to have the same lifetime
 | |
|                      // as the constructor call.
 | |
|                      unless(has(expr(ignoreTrivials(lambdaExpr())))),
 | |
|                      expr().bind("parentOperatorCallExpr")),
 | |
|                  callExpr(
 | |
|                      // If we're in a lamda, we may have a call expression
 | |
|                      // ancestor in the AST, but the temporary we're matching
 | |
|                      // against is not going to have the same lifetime as the
 | |
|                      // function call.
 | |
|                      unless(has(expr(ignoreTrivials(lambdaExpr())))),
 | |
|                      expr().bind("parentCallExpr")),
 | |
|                  objcMessageExpr(
 | |
|                      // If we're in a lamda, we may have an objc message
 | |
|                      // expression ancestor in the AST, but the temporary we're
 | |
|                      // matching against is not going to have the same lifetime
 | |
|                      // as the function call.
 | |
|                      unless(has(expr(ignoreTrivials(lambdaExpr())))),
 | |
|                      expr().bind("parentObjCMessageExpr")),
 | |
|                  cxxConstructExpr(
 | |
|                      // If we're in a lamda, we may have a construct expression
 | |
|                      // ancestor in the AST, but the temporary we're matching
 | |
|                      // against is not going to have the same lifetime as the
 | |
|                      // constructor call.
 | |
|                      unless(has(expr(ignoreTrivials(lambdaExpr())))),
 | |
|                      expr().bind("parentConstructExpr")))));
 | |
| 
 | |
|   AstMatcher->addMatcher(
 | |
|       // This is a matcher on a method call,
 | |
|       cxxMemberCallExpr(
 | |
|           // which is in first party code,
 | |
|           isFirstParty(),
 | |
| 
 | |
|           // and which is performed on a temporary,
 | |
|           on(allOf(unless(hasType(pointerType())), isTemporary(),
 | |
|                    // but which is not `this`.
 | |
|                    unless(cxxThisExpr()))),
 | |
| 
 | |
|           // and which is marked as no dangling on temporaries.
 | |
|           callee(cxxMethodDecl(noDanglingOnTemporaries())),
 | |
| 
 | |
|           expr().bind("memberCallExpr"),
 | |
| 
 | |
|           // We optionally match a parent call expression or a parent construct
 | |
|           // expression because using a temporary inside a call is fine as long
 | |
|           // as the pointer doesn't escape the function call.
 | |
|           anyOf(
 | |
|               // This is the case where the call is the direct parent, so we
 | |
|               // know that the member call expression is the argument.
 | |
|               allOf(hasParentCall, expr().bind("parentCallArg")),
 | |
| 
 | |
|               // This is the case where the call is not the direct parent, so we
 | |
|               // get its child to know in which argument tree we are.
 | |
|               hasAncestor(expr(hasParentCall, expr().bind("parentCallArg"))),
 | |
|               // To make it optional.
 | |
|               anything())),
 | |
|       this);
 | |
| }
 | |
| 
 | |
| void DanglingOnTemporaryChecker::check(const MatchFinder::MatchResult &Result) {
 | |
|   ///////////////////////////////////////
 | |
|   // Quick annotation conflict checker //
 | |
|   ///////////////////////////////////////
 | |
| 
 | |
|   const char *ErrorInvalidRefQualified = "methods annotated with "
 | |
|                                          "MOZ_NO_DANGLING_ON_TEMPORARIES "
 | |
|                                          "cannot be && ref-qualified";
 | |
| 
 | |
|   const char *ErrorInvalidPointer = "methods annotated with "
 | |
|                                     "MOZ_NO_DANGLING_ON_TEMPORARIES must "
 | |
|                                     "return a pointer";
 | |
| 
 | |
|   if (auto InvalidRefQualified =
 | |
|           Result.Nodes.getNodeAs<CXXMethodDecl>("invalidMethodRefQualified")) {
 | |
|     diag(InvalidRefQualified->getLocation(), ErrorInvalidRefQualified,
 | |
|          DiagnosticIDs::Error);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (auto InvalidPointer =
 | |
|           Result.Nodes.getNodeAs<CXXMethodDecl>("invalidMethodPointer")) {
 | |
|     diag(InvalidPointer->getLocation(), ErrorInvalidPointer,
 | |
|          DiagnosticIDs::Error);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   //////////////////
 | |
|   // Main checker //
 | |
|   //////////////////
 | |
| 
 | |
|   const char *Error = "calling `%0` on a temporary, potentially allowing use "
 | |
|                       "after free of the raw pointer";
 | |
| 
 | |
|   const char *EscapeStmtNote =
 | |
|       "the raw pointer escapes the function scope here";
 | |
| 
 | |
|   const ObjCMessageExpr *ParentObjCMessageExpr =
 | |
|       Result.Nodes.getNodeAs<ObjCMessageExpr>("parentObjCMessageExpr");
 | |
| 
 | |
|   // We don't care about cases in ObjC message expressions.
 | |
|   if (ParentObjCMessageExpr) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const CXXMemberCallExpr *MemberCall =
 | |
|       Result.Nodes.getNodeAs<CXXMemberCallExpr>("memberCallExpr");
 | |
| 
 | |
|   const CallExpr *ParentCallExpr =
 | |
|       Result.Nodes.getNodeAs<CallExpr>("parentCallExpr");
 | |
|   const CXXConstructExpr *ParentConstructExpr =
 | |
|       Result.Nodes.getNodeAs<CXXConstructExpr>("parentConstructExpr");
 | |
|   const CXXOperatorCallExpr *ParentOperatorCallExpr =
 | |
|       Result.Nodes.getNodeAs<CXXOperatorCallExpr>("parentOperatorCallExpr");
 | |
|   const Expr *ParentCallArg = Result.Nodes.getNodeAs<Expr>("parentCallArg");
 | |
| 
 | |
|   // Just in case.
 | |
|   if (!MemberCall) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If we have a parent call, we check whether or not we escape the function
 | |
|   // being called.
 | |
|   if (ParentOperatorCallExpr || ParentCallExpr || ParentConstructExpr) {
 | |
|     // Just in case.
 | |
|     if (!ParentCallArg) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // No default constructor so we can't construct it using if/else.
 | |
|     auto FunctionEscapeData =
 | |
|         ParentOperatorCallExpr
 | |
|             ? escapesFunction(ParentCallArg, ParentOperatorCallExpr)
 | |
|             : ParentCallExpr
 | |
|                   ? escapesFunction(ParentCallArg, ParentCallExpr)
 | |
|                   : escapesFunction(ParentCallArg, ParentConstructExpr);
 | |
| 
 | |
|     // If there was an error in the escapesFunction call.
 | |
|     if (std::error_code ec = FunctionEscapeData.getError()) {
 | |
|       // FIXME: For now we ignore the variadic case and just consider that the
 | |
|       // argument doesn't escape the function. Same for the case where we can't
 | |
|       // find the function declaration or if the function is builtin.
 | |
|       if (static_cast<EscapesFunctionError>(ec.value()) ==
 | |
|               EscapesFunctionError::FunctionIsVariadic ||
 | |
|           static_cast<EscapesFunctionError>(ec.value()) ==
 | |
|               EscapesFunctionError::FunctionDeclNotFound ||
 | |
|           static_cast<EscapesFunctionError>(ec.value()) ==
 | |
|               EscapesFunctionError::FunctionIsBuiltin) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       // We emit the internal checker error and return.
 | |
|       diag(MemberCall->getExprLoc(),
 | |
|            std::string(ec.category().name()) + " error: " + ec.message(),
 | |
|            DiagnosticIDs::Error);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // We deconstruct the function escape data.
 | |
|     const Stmt *EscapeStmt;
 | |
|     const Decl *EscapeDecl;
 | |
|     std::tie(EscapeStmt, EscapeDecl) = *FunctionEscapeData;
 | |
| 
 | |
|     // If we didn't escape a parent function, we're done: we don't emit any
 | |
|     // diagnostic.
 | |
|     if (!EscapeStmt || !EscapeDecl) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // We emit the error diagnostic indicating that we are calling the method
 | |
|     // temporary.
 | |
|     diag(MemberCall->getExprLoc(), Error, DiagnosticIDs::Error)
 | |
|         << MemberCall->getMethodDecl()->getName()
 | |
|         << MemberCall->getSourceRange();
 | |
| 
 | |
|     // We indicate the escape statement.
 | |
|     diag(EscapeStmt->getBeginLoc(), EscapeStmtNote, DiagnosticIDs::Note)
 | |
|         << EscapeStmt->getSourceRange();
 | |
| 
 | |
|     // We build the escape note along with its source range.
 | |
|     StringRef EscapeDeclNote;
 | |
|     SourceRange EscapeDeclRange;
 | |
|     if (isa<ParmVarDecl>(EscapeDecl)) {
 | |
|       EscapeDeclNote = "through the parameter declared here";
 | |
|       EscapeDeclRange = EscapeDecl->getSourceRange();
 | |
|     } else if (isa<VarDecl>(EscapeDecl)) {
 | |
|       EscapeDeclNote = "through the variable declared here";
 | |
|       EscapeDeclRange = EscapeDecl->getSourceRange();
 | |
|     } else if (isa<FieldDecl>(EscapeDecl)) {
 | |
|       EscapeDeclNote = "through the field declared here";
 | |
|       EscapeDeclRange = EscapeDecl->getSourceRange();
 | |
|     } else if (auto FuncDecl = dyn_cast<FunctionDecl>(EscapeDecl)) {
 | |
|       EscapeDeclNote = "through the return value of the function declared here";
 | |
|       EscapeDeclRange = FuncDecl->getReturnTypeSourceRange();
 | |
|     } else {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // We emit the declaration note indicating through which decl the argument
 | |
|     // escapes.
 | |
|     diag(EscapeDecl->getLocation(), EscapeDeclNote, DiagnosticIDs::Note)
 | |
|         << EscapeDeclRange;
 | |
|   } else {
 | |
|     // We emit the error diagnostic indicating that we are calling the method
 | |
|     // temporary.
 | |
|     diag(MemberCall->getExprLoc(), Error, DiagnosticIDs::Error)
 | |
|         << MemberCall->getMethodDecl()->getName()
 | |
|         << MemberCall->getSourceRange();
 | |
|   }
 | |
| }
 |