summaryrefslogtreecommitdiffstats
path: root/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp
diff options
context:
space:
mode:
authorGeorge Karpenkov <ekarpenkov@apple.com>2018-03-29 20:55:34 +0000
committerGeorge Karpenkov <ekarpenkov@apple.com>2018-03-29 20:55:34 +0000
commitd676ba0f28ee263bc68f6992ca237cdf9395be21 (patch)
tree1921f3ffea8f7d5bbaa53fdccc07ea43abda63a3 /clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp
parent309e29d5327519551df54ad1dba88c0643eb6f09 (diff)
downloadbcm5719-llvm-d676ba0f28ee263bc68f6992ca237cdf9395be21.tar.gz
bcm5719-llvm-d676ba0f28ee263bc68f6992ca237cdf9395be21.zip
[analyzer] Path-insensitive checker for writes into an auto-releasing pointer
from the wrong auto-releasing pool, as such writes may crash. rdar://25301111 Differential Revision: https://reviews.llvm.org/D44722 llvm-svn: 328827
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp')
-rw-r--r--clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp157
1 files changed, 157 insertions, 0 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp
new file mode 100644
index 00000000000..0433a8aea37
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp
@@ -0,0 +1,157 @@
+//===- ObjCAutoreleaseWriteChecker.cpp ----------------------------*- C++ -*-==//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines ObjCAutoreleaseWriteChecker which warns against writes
+// into autoreleased out parameters which are likely to cause crashes.
+// An example of a problematic write is a write to {@code 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 is very likely to crash due to the other queue autorelease pool
+// begin able to free the error object.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangSACheckers.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.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 *ParamBind = "parambind";
+const char *MethodBind = "methodbind";
+
+class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> {
+public:
+ void checkASTCodeBody(const Decl *D,
+ AnalysisManager &AM,
+ BugReporter &BR) const;
+private:
+ std::vector<std::string> SelectorsWithAutoreleasingPool = {
+ "enumerateObjectsUsingBlock:",
+ "enumerateKeysAndObjectsUsingBlock:",
+ "enumerateKeysAndObjectsWithOptions:usingBlock:",
+ "enumerateObjectsWithOptions:usingBlock:",
+ "enumerateObjectsAtIndexes:options:usingBlock:",
+ "enumerateIndexesWithOptions:usingBlock:",
+ "enumerateIndexesUsingBlock:",
+ "enumerateIndexesInRange:options:usingBlock:",
+ "enumerateRangesUsingBlock:",
+ "enumerateRangesWithOptions:usingBlock:",
+ "enumerateRangesInRange:options:usingBlock:"
+ "objectWithOptions:passingTest:",
+ };
+
+ std::vector<std::string> FunctionsWithAutoreleasingPool = {
+ "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"};
+};
+}
+
+static inline std::vector<llvm::StringRef> toRefs(std::vector<std::string> V) {
+ return std::vector<llvm::StringRef>(V.begin(), V.end());
+}
+
+static auto callsNames(std::vector<std::string> FunctionNames)
+ -> decltype(callee(functionDecl())) {
+ 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);
+ assert(PVD);
+ QualType Ty = PVD->getType();
+ if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing)
+ return;
+ const auto *SW = Match.getNodeAs<Expr>(ProblematicWriteBind);
+ bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(MethodBind) != nullptr;
+ const char *Name = IsMethod ? "method" : "function";
+ assert(SW);
+ BR.EmitBasicReport(
+ ADC->getDecl(), Checker,
+ /*Name=*/"Writing into auto-releasing variable from a different queue",
+ /*Category=*/"Memory",
+ (llvm::Twine("Writing into an auto-releasing out parameter inside ") +
+ "autorelease pool that may exit before " + Name + " returns; consider "
+ "writing first to a strong local variable declared outside of the block")
+ .str(),
+ PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
+ SW->getSourceRange());
+}
+
+void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D,
+ AnalysisManager &AM,
+ BugReporter &BR) const {
+
+ // Write into a binded object, e.g. *ParamBind = X.
+ auto WritesIntoM = binaryOperator(
+ hasLHS(unaryOperator(
+ hasOperatorName("*"),
+ hasUnaryOperand(
+ ignoringParenImpCasts(
+ declRefExpr(to(parmVarDecl(equalsBoundNode(ParamBind))))))
+ )),
+ hasOperatorName("=")
+ ).bind(ProblematicWriteBind);
+
+ // WritesIntoM happens inside a block passed as an argument.
+ auto WritesInBlockM = hasAnyArgument(allOf(
+ hasType(hasCanonicalType(blockPointerType())),
+ forEachDescendant(WritesIntoM)
+ ));
+
+ auto CallsAsyncM = stmt(anyOf(
+ callExpr(allOf(
+ callsNames(FunctionsWithAutoreleasingPool), WritesInBlockM)),
+ objcMessageExpr(allOf(
+ hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)),
+ WritesInBlockM))
+ ));
+
+ auto DoublePointerParamM =
+ parmVarDecl(hasType(pointerType(
+ pointee(hasCanonicalType(objcObjectPointerType())))))
+ .bind(ParamBind);
+
+ auto HasParamAndWritesAsyncM = allOf(
+ hasAnyParameter(DoublePointerParamM),
+ forEachDescendant(CallsAsyncM));
+
+ auto MatcherM = decl(anyOf(
+ objcMethodDecl(HasParamAndWritesAsyncM).bind(MethodBind),
+ functionDecl(HasParamAndWritesAsyncM)));
+
+ 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>();
+}
OpenPOWER on IntegriCloud