tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }