tor-browser

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

RefCountedInsideLambdaChecker.cpp (6194B)


      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 "RefCountedInsideLambdaChecker.h"
      6 #include "CustomMatchers.h"
      7 
      8 RefCountedMap RefCountedClasses;
      9 
     10 void RefCountedInsideLambdaChecker::registerMatchers(MatchFinder *AstMatcher) {
     11  // We want to reject any code which captures a pointer to an object of a
     12  // refcounted type, and then lets that value escape. As a primitive analysis,
     13  // we reject any occurances of the lambda as a template parameter to a class
     14  // (which could allow it to escape), as well as any presence of such a lambda
     15  // in a return value (either from lambdas, or in c++14, auto functions).
     16  //
     17  // We check these lambdas' capture lists for raw pointers to refcounted types.
     18  AstMatcher->addMatcher(functionDecl(returns(recordType(hasDeclaration(
     19                             cxxRecordDecl(isLambdaDecl()).bind("decl"))))),
     20                         this);
     21  AstMatcher->addMatcher(lambdaExpr().bind("lambdaExpr"), this);
     22  AstMatcher->addMatcher(
     23      classTemplateSpecializationDecl(
     24          hasAnyTemplateArgument(refersToType(recordType(
     25              hasDeclaration(cxxRecordDecl(isLambdaDecl()).bind("decl")))))),
     26      this);
     27 }
     28 
     29 void RefCountedInsideLambdaChecker::emitDiagnostics(SourceLocation Loc,
     30                                                    StringRef Name,
     31                                                    QualType Type) {
     32  diag(Loc,
     33       "Refcounted variable '%0' of type %1 cannot be captured by a lambda",
     34       DiagnosticIDs::Error)
     35      << Name << Type;
     36  diag(Loc, "Please consider using a smart pointer", DiagnosticIDs::Note);
     37 }
     38 
     39 static bool IsKnownLive(const VarDecl *Var) {
     40  const Stmt *Init = Var->getInit();
     41  if (!Init) {
     42    return false;
     43  }
     44  if (auto *Call = dyn_cast<CallExpr>(Init)) {
     45    const FunctionDecl *Callee = Call->getDirectCallee();
     46    return Callee && Callee->getName() == "MOZ_KnownLive";
     47  }
     48  return false;
     49 }
     50 
     51 void RefCountedInsideLambdaChecker::check(
     52    const MatchFinder::MatchResult &Result) {
     53  static DenseSet<const CXXRecordDecl *> CheckedDecls;
     54 
     55  const CXXRecordDecl *Lambda = Result.Nodes.getNodeAs<CXXRecordDecl>("decl");
     56 
     57  if (const LambdaExpr *OuterLambda =
     58          Result.Nodes.getNodeAs<LambdaExpr>("lambdaExpr")) {
     59    const CXXMethodDecl *OpCall = OuterLambda->getCallOperator();
     60    QualType ReturnTy = OpCall->getReturnType();
     61    if (const CXXRecordDecl *Record = ReturnTy->getAsCXXRecordDecl()) {
     62      Lambda = Record;
     63    }
     64  }
     65 
     66  if (!Lambda || !Lambda->isLambda()) {
     67    return;
     68  }
     69 
     70  // Don't report errors on the same declarations more than once.
     71  if (CheckedDecls.count(Lambda)) {
     72    return;
     73  }
     74  CheckedDecls.insert(Lambda);
     75 
     76  bool StrongRefToThisCaptured = false;
     77 
     78  for (const LambdaCapture &Capture : Lambda->captures()) {
     79    // Check if any of the captures are ByRef. If they are, we have nothing to
     80    // report, as it's OK to capture raw pointers to refcounted objects so long
     81    // as the Lambda doesn't escape the current scope, which is required by
     82    // ByRef captures already.
     83    if (Capture.getCaptureKind() == LCK_ByRef) {
     84      return;
     85    }
     86 
     87    // Check if this capture is byvalue, and captures a strong reference to
     88    // this.
     89    // XXX: Do we want to make sure that this type which we are capturing is a
     90    // "Smart Pointer" somehow?
     91    if (!StrongRefToThisCaptured && Capture.capturesVariable() &&
     92        Capture.getCaptureKind() == LCK_ByCopy) {
     93      const VarDecl *Var = dyn_cast<VarDecl>(Capture.getCapturedVar());
     94      if (Var->hasInit()) {
     95        const Stmt *Init = Var->getInit();
     96 
     97        // Ignore single argument constructors, and trivial nodes.
     98        while (true) {
     99          auto NewInit = IgnoreTrivials(Init);
    100          if (auto ConstructExpr = dyn_cast<CXXConstructExpr>(NewInit)) {
    101            if (ConstructExpr->getNumArgs() == 1) {
    102              NewInit = ConstructExpr->getArg(0);
    103            }
    104          }
    105          if (Init == NewInit) {
    106            break;
    107          }
    108          Init = NewInit;
    109        }
    110 
    111        if (isa<CXXThisExpr>(Init)) {
    112          StrongRefToThisCaptured = true;
    113        }
    114      }
    115    }
    116  }
    117 
    118  // Now we can go through and produce errors for any captured variables or this
    119  // pointers.
    120  for (const LambdaCapture &Capture : Lambda->captures()) {
    121    if (Capture.capturesVariable()) {
    122      const VarDecl *Var = dyn_cast<VarDecl>(Capture.getCapturedVar());
    123      QualType Pointee = Var->getType()->getPointeeType();
    124      if (!Pointee.isNull() && isClassRefCounted(Pointee) &&
    125          !IsKnownLive(Var)) {
    126        emitDiagnostics(Capture.getLocation(), Var->getName(), Pointee);
    127        return;
    128      }
    129    }
    130 
    131    // The situation with captures of `this` is more complex. All captures of
    132    // `this` look the same-ish (they are LCK_This). We want to complain about
    133    // captures of `this` where `this` is a refcounted type, and the capture is
    134    // actually used in the body of the lambda (if the capture isn't used, then
    135    // we don't care, because it's only being captured in order to give access
    136    // to private methods).
    137    //
    138    // In addition, we don't complain about this, even if it is used, if it was
    139    // captured implicitly when the LambdaCaptureDefault was LCD_ByRef, as that
    140    // expresses the intent that the lambda won't leave the enclosing scope.
    141    bool ImplicitByRefDefaultedCapture =
    142        Capture.isImplicit() && Lambda->getLambdaCaptureDefault() == LCD_ByRef;
    143    if (Capture.capturesThis() && !ImplicitByRefDefaultedCapture &&
    144        !StrongRefToThisCaptured) {
    145      ThisVisitor V(*this);
    146      bool NotAborted = V.TraverseDecl(
    147          const_cast<CXXMethodDecl *>(Lambda->getLambdaCallOperator()));
    148      if (!NotAborted) {
    149        return;
    150      }
    151    }
    152  }
    153 }
    154 
    155 bool RefCountedInsideLambdaChecker::ThisVisitor::VisitCXXThisExpr(
    156    CXXThisExpr *This) {
    157  QualType Pointee = This->getType()->getPointeeType();
    158  if (!Pointee.isNull() && isClassRefCounted(Pointee)) {
    159    Checker.emitDiagnostics(This->getBeginLoc(), "this", Pointee);
    160    return false;
    161  }
    162 
    163  return true;
    164 }