DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Header

Mercurial (c68fe15a81fc)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
/* 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 "RefCountedInsideLambdaChecker.h"
#include "CustomMatchers.h"

RefCountedMap RefCountedClasses;

void RefCountedInsideLambdaChecker::registerMatchers(MatchFinder *AstMatcher) {
  // We want to reject any code which captures a pointer to an object of a
  // refcounted type, and then lets that value escape. As a primitive analysis,
  // we reject any occurances of the lambda as a template parameter to a class
  // (which could allow it to escape), as well as any presence of such a lambda
  // in a return value (either from lambdas, or in c++14, auto functions).
  //
  // We check these lambdas' capture lists for raw pointers to refcounted types.
  AstMatcher->addMatcher(functionDecl(returns(recordType(hasDeclaration(
                             cxxRecordDecl(isLambdaDecl()).bind("decl"))))),
                         this);
  AstMatcher->addMatcher(lambdaExpr().bind("lambdaExpr"), this);
  AstMatcher->addMatcher(
      classTemplateSpecializationDecl(
          hasAnyTemplateArgument(refersToType(recordType(
              hasDeclaration(cxxRecordDecl(isLambdaDecl()).bind("decl")))))),
      this);
}

void RefCountedInsideLambdaChecker::emitDiagnostics(SourceLocation Loc,
                                                    StringRef Name,
                                                    QualType Type) {
  diag(Loc,
       "Refcounted variable '%0' of type %1 cannot be captured by a lambda",
       DiagnosticIDs::Error)
      << Name << Type;
  diag(Loc, "Please consider using a smart pointer", DiagnosticIDs::Note);
}

void RefCountedInsideLambdaChecker::check(
    const MatchFinder::MatchResult &Result) {
  static DenseSet<const CXXRecordDecl *> CheckedDecls;

  const CXXRecordDecl *Lambda = Result.Nodes.getNodeAs<CXXRecordDecl>("decl");

  if (const LambdaExpr *OuterLambda =
          Result.Nodes.getNodeAs<LambdaExpr>("lambdaExpr")) {
    const CXXMethodDecl *OpCall = OuterLambda->getCallOperator();
    QualType ReturnTy = OpCall->getReturnType();
    if (const CXXRecordDecl *Record = ReturnTy->getAsCXXRecordDecl()) {
      Lambda = Record;
    }
  }

  if (!Lambda || !Lambda->isLambda()) {
    return;
  }

  // Don't report errors on the same declarations more than once.
  if (CheckedDecls.count(Lambda)) {
    return;
  }
  CheckedDecls.insert(Lambda);

  bool StrongRefToThisCaptured = false;

  for (const LambdaCapture &Capture : Lambda->captures()) {
    // Check if any of the captures are ByRef. If they are, we have nothing to
    // report, as it's OK to capture raw pointers to refcounted objects so long
    // as the Lambda doesn't escape the current scope, which is required by
    // ByRef captures already.
    if (Capture.getCaptureKind() == LCK_ByRef) {
      return;
    }

    // Check if this capture is byvalue, and captures a strong reference to
    // this.
    // XXX: Do we want to make sure that this type which we are capturing is a
    // "Smart Pointer" somehow?
    if (!StrongRefToThisCaptured && Capture.capturesVariable() &&
        Capture.getCaptureKind() == LCK_ByCopy) {
      const VarDecl *Var = Capture.getCapturedVar();
      if (Var->hasInit()) {
        const Stmt *Init = Var->getInit();

        // Ignore single argument constructors, and trivial nodes.
        while (true) {
          auto NewInit = IgnoreTrivials(Init);
          if (auto ConstructExpr = dyn_cast<CXXConstructExpr>(NewInit)) {
            if (ConstructExpr->getNumArgs() == 1) {
              NewInit = ConstructExpr->getArg(0);
            }
          }
          if (Init == NewInit) {
            break;
          }
          Init = NewInit;
        }

        if (isa<CXXThisExpr>(Init)) {
          StrongRefToThisCaptured = true;
        }
      }
    }
  }

  // Now we can go through and produce errors for any captured variables or this
  // pointers.
  for (const LambdaCapture &Capture : Lambda->captures()) {
    if (Capture.capturesVariable()) {
      QualType Pointee = Capture.getCapturedVar()->getType()->getPointeeType();

      if (!Pointee.isNull() && isClassRefCounted(Pointee)) {
        emitDiagnostics(Capture.getLocation(),
                        Capture.getCapturedVar()->getName(), Pointee);
        return;
      }
    }

    // The situation with captures of `this` is more complex. All captures of
    // `this` look the same-ish (they are LCK_This). We want to complain about
    // captures of `this` where `this` is a refcounted type, and the capture is
    // actually used in the body of the lambda (if the capture isn't used, then
    // we don't care, because it's only being captured in order to give access
    // to private methods).
    //
    // In addition, we don't complain about this, even if it is used, if it was
    // captured implicitly when the LambdaCaptureDefault was LCD_ByRef, as that
    // expresses the intent that the lambda won't leave the enclosing scope.
    bool ImplicitByRefDefaultedCapture =
        Capture.isImplicit() && Lambda->getLambdaCaptureDefault() == LCD_ByRef;
    if (Capture.capturesThis() && !ImplicitByRefDefaultedCapture &&
        !StrongRefToThisCaptured) {
      ThisVisitor V(*this);
      bool NotAborted = V.TraverseDecl(
          const_cast<CXXMethodDecl *>(Lambda->getLambdaCallOperator()));
      if (!NotAborted) {
        return;
      }
    }
  }
}

bool RefCountedInsideLambdaChecker::ThisVisitor::VisitCXXThisExpr(
    CXXThisExpr *This) {
  QualType Pointee = This->getType()->getPointeeType();
  if (!Pointee.isNull() && isClassRefCounted(Pointee)) {
    Checker.emitDiagnostics(This->getBeginLoc(), "this", Pointee);
    return false;
  }

  return true;
}