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 }