summaryrefslogtreecommitdiffstats
path: root/build/clang-plugin/RefCountedInsideLambdaChecker.cpp
blob: 3256aa8f04ceb7fa050592c0569ee4821594b1ff (plain)
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
153
154
155
156
157
158
159
160
161
162
163
164
/* 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);
}

static bool IsKnownLive(const VarDecl *Var) {
  const Stmt *Init = Var->getInit();
  if (!Init) {
    return false;
  }
  if (auto *Call = dyn_cast<CallExpr>(Init)) {
    const FunctionDecl *Callee = Call->getDirectCallee();
    return Callee && Callee->getName() == "MOZ_KnownLive";
  }
  return false;
}

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 = dyn_cast<VarDecl>(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()) {
      const VarDecl *Var = dyn_cast<VarDecl>(Capture.getCapturedVar());
      QualType Pointee = Var->getType()->getPointeeType();
      if (!Pointee.isNull() && isClassRefCounted(Pointee) &&
          !IsKnownLive(Var)) {
        emitDiagnostics(Capture.getLocation(), Var->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;
}