diff options
Diffstat (limited to 'contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp')
-rw-r--r-- | contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp new file mode 100644 index 000000000000..e7fd14d4558b --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp @@ -0,0 +1,240 @@ +//===- ObjCAutoreleaseWriteChecker.cpp ---------------------------*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines ObjCAutoreleaseWriteChecker which warns against writes +// into autoreleased out parameters which cause crashes. +// An example of a problematic write is a write to @c error in the example +// below: +// +// - (BOOL) mymethod:(NSError *__autoreleasing *)error list:(NSArray*) list { +// [list enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { +// NSString *myString = obj; +// if ([myString isEqualToString:@"error"] && error) +// *error = [NSError errorWithDomain:@"MyDomain" code:-1]; +// }]; +// return false; +// } +// +// Such code will crash on read from `*error` due to the autorelease pool +// in `enumerateObjectsUsingBlock` implementation freeing the error object +// on exit from the function. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "llvm/ADT/Twine.h" + +using namespace clang; +using namespace ento; +using namespace ast_matchers; + +namespace { + +const char *ProblematicWriteBind = "problematicwrite"; +const char *CapturedBind = "capturedbind"; +const char *ParamBind = "parambind"; +const char *IsMethodBind = "ismethodbind"; +const char *IsARPBind = "isautoreleasepoolbind"; + +class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> { +public: + void checkASTCodeBody(const Decl *D, + AnalysisManager &AM, + BugReporter &BR) const; +private: + std::vector<std::string> SelectorsWithAutoreleasingPool = { + // Common to NSArray, NSSet, NSOrderedSet + "enumerateObjectsUsingBlock:", + "enumerateObjectsWithOptions:usingBlock:", + + // Common to NSArray and NSOrderedSet + "enumerateObjectsAtIndexes:options:usingBlock:", + "indexOfObjectAtIndexes:options:passingTest:", + "indexesOfObjectsAtIndexes:options:passingTest:", + "indexOfObjectPassingTest:", + "indexOfObjectWithOptions:passingTest:", + "indexesOfObjectsPassingTest:", + "indexesOfObjectsWithOptions:passingTest:", + + // NSDictionary + "enumerateKeysAndObjectsUsingBlock:", + "enumerateKeysAndObjectsWithOptions:usingBlock:", + "keysOfEntriesPassingTest:", + "keysOfEntriesWithOptions:passingTest:", + + // NSSet + "objectsPassingTest:", + "objectsWithOptions:passingTest:", + "enumerateIndexPathsWithOptions:usingBlock:", + + // NSIndexSet + "enumerateIndexesWithOptions:usingBlock:", + "enumerateIndexesUsingBlock:", + "enumerateIndexesInRange:options:usingBlock:", + "enumerateRangesUsingBlock:", + "enumerateRangesWithOptions:usingBlock:", + "enumerateRangesInRange:options:usingBlock:", + "indexPassingTest:", + "indexesPassingTest:", + "indexWithOptions:passingTest:", + "indexesWithOptions:passingTest:", + "indexInRange:options:passingTest:", + "indexesInRange:options:passingTest:" + }; + + std::vector<std::string> FunctionsWithAutoreleasingPool = { + "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"}; +}; +} + +static inline std::vector<llvm::StringRef> +toRefs(const std::vector<std::string> &V) { + return std::vector<llvm::StringRef>(V.begin(), V.end()); +} + +static decltype(auto) +callsNames(const std::vector<std::string> &FunctionNames) { + return callee(functionDecl(hasAnyName(toRefs(FunctionNames)))); +} + +static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR, + AnalysisManager &AM, + const ObjCAutoreleaseWriteChecker *Checker) { + AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D); + + const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind); + QualType Ty = PVD->getType(); + if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing) + return; + const char *ActionMsg = "Write to"; + const auto *MarkedStmt = Match.getNodeAs<Expr>(ProblematicWriteBind); + bool IsCapture = false; + + // Prefer to warn on write, but if not available, warn on capture. + if (!MarkedStmt) { + MarkedStmt = Match.getNodeAs<Expr>(CapturedBind); + assert(MarkedStmt); + ActionMsg = "Capture of"; + IsCapture = true; + } + + SourceRange Range = MarkedStmt->getSourceRange(); + PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin( + MarkedStmt, BR.getSourceManager(), ADC); + + bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(IsMethodBind) != nullptr; + const char *FunctionDescription = IsMethod ? "method" : "function"; + bool IsARP = Match.getNodeAs<ObjCAutoreleasePoolStmt>(IsARPBind) != nullptr; + + llvm::SmallString<128> BugNameBuf; + llvm::raw_svector_ostream BugName(BugNameBuf); + BugName << ActionMsg + << " autoreleasing out parameter inside autorelease pool"; + + llvm::SmallString<128> BugMessageBuf; + llvm::raw_svector_ostream BugMessage(BugMessageBuf); + BugMessage << ActionMsg << " autoreleasing out parameter "; + if (IsCapture) + BugMessage << "'" + PVD->getName() + "' "; + + BugMessage << "inside "; + if (IsARP) + BugMessage << "locally-scoped autorelease pool;"; + else + BugMessage << "autorelease pool that may exit before " + << FunctionDescription << " returns;"; + + BugMessage << " consider writing first to a strong local variable" + " declared outside "; + if (IsARP) + BugMessage << "of the autorelease pool"; + else + BugMessage << "of the block"; + + BR.EmitBasicReport(ADC->getDecl(), Checker, BugName.str(), + categories::MemoryRefCount, BugMessage.str(), Location, + Range); +} + +void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D, + AnalysisManager &AM, + BugReporter &BR) const { + + auto DoublePointerParamM = + parmVarDecl(hasType(hasCanonicalType(pointerType( + pointee(hasCanonicalType(objcObjectPointerType())))))) + .bind(ParamBind); + + auto ReferencedParamM = + declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind); + + // Write into a binded object, e.g. *ParamBind = X. + auto WritesIntoM = binaryOperator( + hasLHS(unaryOperator( + hasOperatorName("*"), + hasUnaryOperand( + ignoringParenImpCasts(ReferencedParamM)) + )), + hasOperatorName("=") + ).bind(ProblematicWriteBind); + + auto ArgumentCaptureM = hasAnyArgument( + ignoringParenImpCasts(ReferencedParamM)); + auto CapturedInParamM = stmt(anyOf( + callExpr(ArgumentCaptureM), + objcMessageExpr(ArgumentCaptureM))); + + // WritesIntoM happens inside a block passed as an argument. + auto WritesOrCapturesInBlockM = hasAnyArgument(allOf( + hasType(hasCanonicalType(blockPointerType())), + forEachDescendant( + stmt(anyOf(WritesIntoM, CapturedInParamM)) + ))); + + auto BlockPassedToMarkedFuncM = stmt(anyOf( + callExpr(allOf( + callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)), + objcMessageExpr(allOf( + hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)), + WritesOrCapturesInBlockM)) + )); + + // WritesIntoM happens inside an explicit @autoreleasepool. + auto WritesOrCapturesInPoolM = + autoreleasePoolStmt( + forEachDescendant(stmt(anyOf(WritesIntoM, CapturedInParamM)))) + .bind(IsARPBind); + + auto HasParamAndWritesInMarkedFuncM = + allOf(hasAnyParameter(DoublePointerParamM), + anyOf(forEachDescendant(BlockPassedToMarkedFuncM), + forEachDescendant(WritesOrCapturesInPoolM))); + + auto MatcherM = decl(anyOf( + objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind), + functionDecl(HasParamAndWritesInMarkedFuncM), + blockDecl(HasParamAndWritesInMarkedFuncM))); + + auto Matches = match(MatcherM, *D, AM.getASTContext()); + for (BoundNodes Match : Matches) + emitDiagnostics(Match, D, BR, AM, this); +} + +void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) { + Mgr.registerChecker<ObjCAutoreleaseWriteChecker>(); +} + +bool ento::shouldRegisterAutoreleaseWriteChecker(const CheckerManager &mgr) { + return true; +} |