Blob Blame History Raw
/* 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();
  }
}