xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
1*0fca6ea1SDimitry Andric //===- ObjCAutoreleaseWriteChecker.cpp ---------------------------*- C++ -*-==//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric //
90b57cec5SDimitry Andric // This file defines ObjCAutoreleaseWriteChecker which warns against writes
100b57cec5SDimitry Andric // into autoreleased out parameters which cause crashes.
11fe6060f1SDimitry Andric // An example of a problematic write is a write to @c error in the example
120b57cec5SDimitry Andric // below:
130b57cec5SDimitry Andric //
140b57cec5SDimitry Andric // - (BOOL) mymethod:(NSError *__autoreleasing *)error list:(NSArray*) list {
150b57cec5SDimitry Andric //     [list enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
160b57cec5SDimitry Andric //       NSString *myString = obj;
170b57cec5SDimitry Andric //       if ([myString isEqualToString:@"error"] && error)
180b57cec5SDimitry Andric //         *error = [NSError errorWithDomain:@"MyDomain" code:-1];
190b57cec5SDimitry Andric //     }];
200b57cec5SDimitry Andric //     return false;
210b57cec5SDimitry Andric // }
220b57cec5SDimitry Andric //
230b57cec5SDimitry Andric // Such code will crash on read from `*error` due to the autorelease pool
240b57cec5SDimitry Andric // in `enumerateObjectsUsingBlock` implementation freeing the error object
250b57cec5SDimitry Andric // on exit from the function.
260b57cec5SDimitry Andric //
270b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
280b57cec5SDimitry Andric 
290b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
300b57cec5SDimitry Andric #include "clang/ASTMatchers/ASTMatchFinder.h"
310b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
320b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
335ffd83dbSDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h"
340b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/Checker.h"
350b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
360b57cec5SDimitry Andric #include "llvm/ADT/Twine.h"
370b57cec5SDimitry Andric 
380b57cec5SDimitry Andric using namespace clang;
390b57cec5SDimitry Andric using namespace ento;
400b57cec5SDimitry Andric using namespace ast_matchers;
410b57cec5SDimitry Andric 
420b57cec5SDimitry Andric namespace {
430b57cec5SDimitry Andric 
440b57cec5SDimitry Andric const char *ProblematicWriteBind = "problematicwrite";
450b57cec5SDimitry Andric const char *CapturedBind = "capturedbind";
460b57cec5SDimitry Andric const char *ParamBind = "parambind";
470b57cec5SDimitry Andric const char *IsMethodBind = "ismethodbind";
485ffd83dbSDimitry Andric const char *IsARPBind = "isautoreleasepoolbind";
490b57cec5SDimitry Andric 
500b57cec5SDimitry Andric class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> {
510b57cec5SDimitry Andric public:
520b57cec5SDimitry Andric   void checkASTCodeBody(const Decl *D,
530b57cec5SDimitry Andric                         AnalysisManager &AM,
540b57cec5SDimitry Andric                         BugReporter &BR) const;
550b57cec5SDimitry Andric private:
560b57cec5SDimitry Andric   std::vector<std::string> SelectorsWithAutoreleasingPool = {
570b57cec5SDimitry Andric       // Common to NSArray,  NSSet, NSOrderedSet
580b57cec5SDimitry Andric       "enumerateObjectsUsingBlock:",
590b57cec5SDimitry Andric       "enumerateObjectsWithOptions:usingBlock:",
600b57cec5SDimitry Andric 
610b57cec5SDimitry Andric       // Common to NSArray and NSOrderedSet
620b57cec5SDimitry Andric       "enumerateObjectsAtIndexes:options:usingBlock:",
630b57cec5SDimitry Andric       "indexOfObjectAtIndexes:options:passingTest:",
640b57cec5SDimitry Andric       "indexesOfObjectsAtIndexes:options:passingTest:",
650b57cec5SDimitry Andric       "indexOfObjectPassingTest:",
660b57cec5SDimitry Andric       "indexOfObjectWithOptions:passingTest:",
670b57cec5SDimitry Andric       "indexesOfObjectsPassingTest:",
680b57cec5SDimitry Andric       "indexesOfObjectsWithOptions:passingTest:",
690b57cec5SDimitry Andric 
700b57cec5SDimitry Andric       // NSDictionary
710b57cec5SDimitry Andric       "enumerateKeysAndObjectsUsingBlock:",
720b57cec5SDimitry Andric       "enumerateKeysAndObjectsWithOptions:usingBlock:",
730b57cec5SDimitry Andric       "keysOfEntriesPassingTest:",
740b57cec5SDimitry Andric       "keysOfEntriesWithOptions:passingTest:",
750b57cec5SDimitry Andric 
760b57cec5SDimitry Andric       // NSSet
770b57cec5SDimitry Andric       "objectsPassingTest:",
780b57cec5SDimitry Andric       "objectsWithOptions:passingTest:",
790b57cec5SDimitry Andric       "enumerateIndexPathsWithOptions:usingBlock:",
800b57cec5SDimitry Andric 
810b57cec5SDimitry Andric       // NSIndexSet
820b57cec5SDimitry Andric       "enumerateIndexesWithOptions:usingBlock:",
830b57cec5SDimitry Andric       "enumerateIndexesUsingBlock:",
840b57cec5SDimitry Andric       "enumerateIndexesInRange:options:usingBlock:",
850b57cec5SDimitry Andric       "enumerateRangesUsingBlock:",
860b57cec5SDimitry Andric       "enumerateRangesWithOptions:usingBlock:",
870b57cec5SDimitry Andric       "enumerateRangesInRange:options:usingBlock:",
880b57cec5SDimitry Andric       "indexPassingTest:",
890b57cec5SDimitry Andric       "indexesPassingTest:",
900b57cec5SDimitry Andric       "indexWithOptions:passingTest:",
910b57cec5SDimitry Andric       "indexesWithOptions:passingTest:",
920b57cec5SDimitry Andric       "indexInRange:options:passingTest:",
930b57cec5SDimitry Andric       "indexesInRange:options:passingTest:"
940b57cec5SDimitry Andric   };
950b57cec5SDimitry Andric 
960b57cec5SDimitry Andric   std::vector<std::string> FunctionsWithAutoreleasingPool = {
970b57cec5SDimitry Andric       "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"};
980b57cec5SDimitry Andric };
990b57cec5SDimitry Andric }
1000b57cec5SDimitry Andric 
10181ad6265SDimitry Andric static inline std::vector<llvm::StringRef>
toRefs(const std::vector<std::string> & V)10281ad6265SDimitry Andric toRefs(const std::vector<std::string> &V) {
1030b57cec5SDimitry Andric   return std::vector<llvm::StringRef>(V.begin(), V.end());
1040b57cec5SDimitry Andric }
1050b57cec5SDimitry Andric 
10681ad6265SDimitry Andric static decltype(auto)
callsNames(const std::vector<std::string> & FunctionNames)10781ad6265SDimitry Andric callsNames(const std::vector<std::string> &FunctionNames) {
1080b57cec5SDimitry Andric   return callee(functionDecl(hasAnyName(toRefs(FunctionNames))));
1090b57cec5SDimitry Andric }
1100b57cec5SDimitry Andric 
emitDiagnostics(BoundNodes & Match,const Decl * D,BugReporter & BR,AnalysisManager & AM,const ObjCAutoreleaseWriteChecker * Checker)1110b57cec5SDimitry Andric static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR,
1120b57cec5SDimitry Andric                             AnalysisManager &AM,
1130b57cec5SDimitry Andric                             const ObjCAutoreleaseWriteChecker *Checker) {
1140b57cec5SDimitry Andric   AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
1150b57cec5SDimitry Andric 
1160b57cec5SDimitry Andric   const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind);
1170b57cec5SDimitry Andric   QualType Ty = PVD->getType();
1180b57cec5SDimitry Andric   if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing)
1190b57cec5SDimitry Andric     return;
1200b57cec5SDimitry Andric   const char *ActionMsg = "Write to";
1210b57cec5SDimitry Andric   const auto *MarkedStmt = Match.getNodeAs<Expr>(ProblematicWriteBind);
1220b57cec5SDimitry Andric   bool IsCapture = false;
1230b57cec5SDimitry Andric 
1240b57cec5SDimitry Andric   // Prefer to warn on write, but if not available, warn on capture.
1250b57cec5SDimitry Andric   if (!MarkedStmt) {
1260b57cec5SDimitry Andric     MarkedStmt = Match.getNodeAs<Expr>(CapturedBind);
1270b57cec5SDimitry Andric     assert(MarkedStmt);
1280b57cec5SDimitry Andric     ActionMsg = "Capture of";
1290b57cec5SDimitry Andric     IsCapture = true;
1300b57cec5SDimitry Andric   }
1310b57cec5SDimitry Andric 
1320b57cec5SDimitry Andric   SourceRange Range = MarkedStmt->getSourceRange();
1330b57cec5SDimitry Andric   PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin(
1340b57cec5SDimitry Andric       MarkedStmt, BR.getSourceManager(), ADC);
1350b57cec5SDimitry Andric 
1365ffd83dbSDimitry Andric   bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(IsMethodBind) != nullptr;
1375ffd83dbSDimitry Andric   const char *FunctionDescription = IsMethod ? "method" : "function";
1385ffd83dbSDimitry Andric   bool IsARP = Match.getNodeAs<ObjCAutoreleasePoolStmt>(IsARPBind) != nullptr;
1395ffd83dbSDimitry Andric 
1405ffd83dbSDimitry Andric   llvm::SmallString<128> BugNameBuf;
1415ffd83dbSDimitry Andric   llvm::raw_svector_ostream BugName(BugNameBuf);
1425ffd83dbSDimitry Andric   BugName << ActionMsg
1435ffd83dbSDimitry Andric           << " autoreleasing out parameter inside autorelease pool";
1445ffd83dbSDimitry Andric 
1455ffd83dbSDimitry Andric   llvm::SmallString<128> BugMessageBuf;
1465ffd83dbSDimitry Andric   llvm::raw_svector_ostream BugMessage(BugMessageBuf);
1475ffd83dbSDimitry Andric   BugMessage << ActionMsg << " autoreleasing out parameter ";
1485ffd83dbSDimitry Andric   if (IsCapture)
1495ffd83dbSDimitry Andric     BugMessage << "'" + PVD->getName() + "' ";
1505ffd83dbSDimitry Andric 
1515ffd83dbSDimitry Andric   BugMessage << "inside ";
1525ffd83dbSDimitry Andric   if (IsARP)
1535ffd83dbSDimitry Andric     BugMessage << "locally-scoped autorelease pool;";
1545ffd83dbSDimitry Andric   else
1555ffd83dbSDimitry Andric     BugMessage << "autorelease pool that may exit before "
1565ffd83dbSDimitry Andric                << FunctionDescription << " returns;";
1575ffd83dbSDimitry Andric 
1585ffd83dbSDimitry Andric   BugMessage << " consider writing first to a strong local variable"
1595ffd83dbSDimitry Andric                 " declared outside ";
1605ffd83dbSDimitry Andric   if (IsARP)
1615ffd83dbSDimitry Andric     BugMessage << "of the autorelease pool";
1625ffd83dbSDimitry Andric   else
1635ffd83dbSDimitry Andric     BugMessage << "of the block";
1645ffd83dbSDimitry Andric 
1655ffd83dbSDimitry Andric   BR.EmitBasicReport(ADC->getDecl(), Checker, BugName.str(),
1665ffd83dbSDimitry Andric                      categories::MemoryRefCount, BugMessage.str(), Location,
1670b57cec5SDimitry Andric                      Range);
1680b57cec5SDimitry Andric }
1690b57cec5SDimitry Andric 
checkASTCodeBody(const Decl * D,AnalysisManager & AM,BugReporter & BR) const1700b57cec5SDimitry Andric void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D,
1710b57cec5SDimitry Andric                                                   AnalysisManager &AM,
1720b57cec5SDimitry Andric                                                   BugReporter &BR) const {
1730b57cec5SDimitry Andric 
1740b57cec5SDimitry Andric   auto DoublePointerParamM =
1750b57cec5SDimitry Andric       parmVarDecl(hasType(hasCanonicalType(pointerType(
1760b57cec5SDimitry Andric                       pointee(hasCanonicalType(objcObjectPointerType()))))))
1770b57cec5SDimitry Andric           .bind(ParamBind);
1780b57cec5SDimitry Andric 
1790b57cec5SDimitry Andric   auto ReferencedParamM =
1800b57cec5SDimitry Andric       declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind);
1810b57cec5SDimitry Andric 
1820b57cec5SDimitry Andric   // Write into a binded object, e.g. *ParamBind = X.
1830b57cec5SDimitry Andric   auto WritesIntoM = binaryOperator(
1840b57cec5SDimitry Andric     hasLHS(unaryOperator(
1850b57cec5SDimitry Andric         hasOperatorName("*"),
1860b57cec5SDimitry Andric         hasUnaryOperand(
1870b57cec5SDimitry Andric           ignoringParenImpCasts(ReferencedParamM))
1880b57cec5SDimitry Andric     )),
1890b57cec5SDimitry Andric     hasOperatorName("=")
1900b57cec5SDimitry Andric   ).bind(ProblematicWriteBind);
1910b57cec5SDimitry Andric 
1920b57cec5SDimitry Andric   auto ArgumentCaptureM = hasAnyArgument(
1930b57cec5SDimitry Andric     ignoringParenImpCasts(ReferencedParamM));
1940b57cec5SDimitry Andric   auto CapturedInParamM = stmt(anyOf(
1950b57cec5SDimitry Andric       callExpr(ArgumentCaptureM),
1960b57cec5SDimitry Andric       objcMessageExpr(ArgumentCaptureM)));
1970b57cec5SDimitry Andric 
1980b57cec5SDimitry Andric   // WritesIntoM happens inside a block passed as an argument.
1990b57cec5SDimitry Andric   auto WritesOrCapturesInBlockM = hasAnyArgument(allOf(
2000b57cec5SDimitry Andric       hasType(hasCanonicalType(blockPointerType())),
2010b57cec5SDimitry Andric       forEachDescendant(
2020b57cec5SDimitry Andric         stmt(anyOf(WritesIntoM, CapturedInParamM))
2030b57cec5SDimitry Andric       )));
2040b57cec5SDimitry Andric 
2050b57cec5SDimitry Andric   auto BlockPassedToMarkedFuncM = stmt(anyOf(
2060b57cec5SDimitry Andric     callExpr(allOf(
2070b57cec5SDimitry Andric       callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)),
2080b57cec5SDimitry Andric     objcMessageExpr(allOf(
2090b57cec5SDimitry Andric        hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)),
2100b57cec5SDimitry Andric        WritesOrCapturesInBlockM))
2110b57cec5SDimitry Andric   ));
2120b57cec5SDimitry Andric 
2135ffd83dbSDimitry Andric   // WritesIntoM happens inside an explicit @autoreleasepool.
2145ffd83dbSDimitry Andric   auto WritesOrCapturesInPoolM =
2155ffd83dbSDimitry Andric       autoreleasePoolStmt(
2165ffd83dbSDimitry Andric           forEachDescendant(stmt(anyOf(WritesIntoM, CapturedInParamM))))
2175ffd83dbSDimitry Andric           .bind(IsARPBind);
2185ffd83dbSDimitry Andric 
2195ffd83dbSDimitry Andric   auto HasParamAndWritesInMarkedFuncM =
2205ffd83dbSDimitry Andric       allOf(hasAnyParameter(DoublePointerParamM),
2215ffd83dbSDimitry Andric             anyOf(forEachDescendant(BlockPassedToMarkedFuncM),
2225ffd83dbSDimitry Andric                   forEachDescendant(WritesOrCapturesInPoolM)));
2230b57cec5SDimitry Andric 
2240b57cec5SDimitry Andric   auto MatcherM = decl(anyOf(
2250b57cec5SDimitry Andric       objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind),
2260b57cec5SDimitry Andric       functionDecl(HasParamAndWritesInMarkedFuncM),
2270b57cec5SDimitry Andric       blockDecl(HasParamAndWritesInMarkedFuncM)));
2280b57cec5SDimitry Andric 
2290b57cec5SDimitry Andric   auto Matches = match(MatcherM, *D, AM.getASTContext());
2300b57cec5SDimitry Andric   for (BoundNodes Match : Matches)
2310b57cec5SDimitry Andric     emitDiagnostics(Match, D, BR, AM, this);
2320b57cec5SDimitry Andric }
2330b57cec5SDimitry Andric 
registerAutoreleaseWriteChecker(CheckerManager & Mgr)2340b57cec5SDimitry Andric void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) {
2350b57cec5SDimitry Andric   Mgr.registerChecker<ObjCAutoreleaseWriteChecker>();
2360b57cec5SDimitry Andric }
2370b57cec5SDimitry Andric 
shouldRegisterAutoreleaseWriteChecker(const CheckerManager & mgr)2385ffd83dbSDimitry Andric bool ento::shouldRegisterAutoreleaseWriteChecker(const CheckerManager &mgr) {
2390b57cec5SDimitry Andric   return true;
2400b57cec5SDimitry Andric }
241