forked from mirrors/gecko-dev
		
	MozReview-Commit-ID: AXrQEjWzxvg --HG-- extra : rebase_source : bf972fbb22648af2edbb756eb899ebddf8444dbc
		
			
				
	
	
		
			256 lines
		
	
	
	
		
			9.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			256 lines
		
	
	
	
		
			9.8 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->getLocStart(), 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();
 | 
						|
  }
 | 
						|
}
 |