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