Index: include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- include/clang/StaticAnalyzer/Checkers/Checkers.td +++ include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -608,6 +608,9 @@ HelpText<"Warn about improper use of '[super dealloc]' in Objective-C">, DescFile<"ObjCSuperDeallocChecker.cpp">; +def AutoreleaseRaceChecker : Checker<"AutoreleaseRace">, + HelpText<"Warn about potential races from autoreleasing objects in Objective-C">, + DescFile<"ObjCAutoreleaseRaceChecker.cpp">; } // end "osx.cocoa" let ParentPackage = Performance in { Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -63,6 +63,7 @@ NullabilityChecker.cpp NumberObjectConversionChecker.cpp ObjCAtSyncChecker.cpp + ObjCAutoreleaseRaceChecker.cpp ObjCContainersASTChecker.cpp ObjCContainersChecker.cpp ObjCMissingSuperCallChecker.cpp Index: lib/StaticAnalyzer/Checkers/ObjCAutoreleaseRaceChecker.cpp =================================================================== --- /dev/null +++ lib/StaticAnalyzer/Checkers/ObjCAutoreleaseRaceChecker.cpp @@ -0,0 +1,143 @@ +//===- ObjCAutoreleaseRaceChecker.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 ObjCAutoreleaseRaceChecker which warns against writes +// into autoreleased objects from different queues which are likely to race +// and cause crashes. +// An example of a problematic write is a write to {@code error} in the example +// below: +// +// - (BOOL) doSomethingWithError:(NSError *__autoreleasing *)error { +// dispatch_semaphore_t sem = dispatch_semaphore_create(0l); +// dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0l), ^{ +// if (error) { +// *error = [NSError errorWithDomain:@"MyDomain" code:-1]; +// } +// dispatch_semaphore_signal(sem); +// }); +// +// dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); +// 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 +#include + +using namespace clang; +using namespace ento; +using namespace ast_matchers; + +namespace { + +const char *ProblematicWriteBind = "problematicwrite"; + +class ObjCAutoreleaseRaceChecker : public Checker { +public: + void checkASTCodeBody(const Decl *D, + AnalysisManager &AM, + BugReporter &BR) const; +}; + +} + +static auto callsName(const char *FunctionName) + -> decltype(callee(functionDecl())) { + return callee(functionDecl(hasName(FunctionName))); +} + +static void emitDiagnostic(BoundNodes &Match, + const Decl *D, + BugReporter &BR, + AnalysisManager &AM, + const ObjCAutoreleaseRaceChecker *Checker) { + AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D); + const auto *SW = Match.getNodeAs(ProblematicWriteBind); + assert(SW); + BR.EmitBasicReport( + ADC->getDecl(), + Checker, + /*Name=*/"Writing into auto-releasing variable from a different queue", + /*Category=*/"Memory", + "Writing into an auto-releasing variable from a different queue " + "to which it belongs to is very likely to race and crash", + PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC), + SW->getSourceRange() + ); +} + +template +static void emitDiagnostics(T Matcher, + const Decl *D, + BugReporter &BR, + AnalysisManager &AM, + const ObjCAutoreleaseRaceChecker *Checker) { + auto Matches = match(Matcher, *D, AM.getASTContext()); + for (BoundNodes Match : Matches) + emitDiagnostic(Match, D, BR, AM, Checker); +} + +void ObjCAutoreleaseRaceChecker::checkASTCodeBody(const Decl *D, + AnalysisManager &AM, + BugReporter &BR) const { + const char* ParamBind = "autoreleasingparam"; + + // 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) + )); + + // FIXME: use a list of APIs which can dispatch in asynchronous manner. + auto CallsAsyncM = callExpr(allOf(callsName("dispatch_async"), + WritesInBlockM)); + + auto DoublePointerParamM = + parmVarDecl(hasType(pointerType( + pointee(ignoringParens(objcObjectPointerType()))))) + .bind(ParamBind); + + auto HasParamAndWritesAsyncM = allOf( + hasAnyParameter(DoublePointerParamM), + forEachDescendant(CallsAsyncM)); + + auto MethodM = objcMethodDecl(HasParamAndWritesAsyncM); + auto FunctionM = functionDecl(HasParamAndWritesAsyncM); + + // FIXME: AST-matcher limitations do not allow us to write a matcher + // joining the two (anyOf(MethodM, FunctionM) is somehow illegal). + emitDiagnostics(MethodM, D, BR, AM, this); + emitDiagnostics(FunctionM, D, BR, AM, this); +} + +void ento::registerAutoreleaseRaceChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} Index: test/Analysis/autoreleaseracechecker_test.m =================================================================== --- /dev/null +++ test/Analysis/autoreleaseracechecker_test.m @@ -0,0 +1,110 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,osx.cocoa.AutoreleaseRace %s -fblocks -verify +typedef signed char BOOL; +@protocol NSObject - (BOOL)isEqual:(id)object; @end +@interface NSObject {} ++(id)alloc; +-(id)init; +-(id)autorelease; +-(id)copy; +-(id)retain; +@end +typedef int NSZone; +typedef int NSCoder; +@protocol NSCopying - (id)copyWithZone:(NSZone *)zone; @end +@protocol NSCoding - (void)encodeWithCoder:(NSCoder *)aCoder; @end +@interface NSError : NSObject {} ++ (id)errorWithDomain:(int)domain; +@end + +typedef int dispatch_semaphore_t; +typedef void (^block_t)(); +typedef NSError** MYERROR; + +typedef struct dispatch_queue_s *dispatch_queue_t; +typedef void (^dispatch_block_t)(void); +extern dispatch_queue_t queue; +void dispatch_async(dispatch_queue_t queue, dispatch_block_t block); +dispatch_semaphore_t dispatch_semaphore_create(int); + +void dispatch_semaphore_wait(dispatch_semaphore_t, int); +void dispatch_semaphore_signal(dispatch_semaphore_t); + +@interface I : NSObject +- (BOOL) writeToErrorInBlock:(NSError *__autoreleasing *)error; +- (BOOL) writeToErrorInBlockMultipleTimes:(NSError *__autoreleasing *)error; +- (BOOL) writeToErrorInBlockNoAnnotation:(NSError **)error; +- (BOOL) writeToError:(NSError *__autoreleasing *)error; +@end + +@implementation I + +- (BOOL) writeToErrorInBlock:(NSError *__autoreleasing *)error { + dispatch_semaphore_t sem = dispatch_semaphore_create(0l); + dispatch_async(queue, ^{ + if (error) { + *error = [NSError errorWithDomain:1]; // expected-warning{{Writing into an auto-releasing variable}} + } + dispatch_semaphore_signal(sem); + }); + + dispatch_semaphore_wait(sem, 100); + return 0; +} + +- (BOOL) writeToErrorInBlockMultipleTimes:(NSError *__autoreleasing *)error { + dispatch_semaphore_t sem = dispatch_semaphore_create(0l); + dispatch_async(queue, ^{ + if (error) { + *error = [NSError errorWithDomain:1]; // expected-warning{{Writing into an auto-releasing variable}} + } + dispatch_semaphore_signal(sem); + }); + dispatch_async(queue, ^{ + if (error) { + *error = [NSError errorWithDomain:1]; // expected-warning{{Writing into an auto-releasing variable}} + *error = [NSError errorWithDomain:1]; // expected-warning{{Writing into an auto-releasing variable}} + } + dispatch_semaphore_signal(sem); + }); + *error = [NSError errorWithDomain:1]; // no-warning + + dispatch_semaphore_wait(sem, 100); + return 0; +} + +- (BOOL) writeToErrorInBlockNoAnnotation:(NSError **)error { + dispatch_semaphore_t sem = dispatch_semaphore_create(0l); + dispatch_async(queue, ^{ + if (error) { + *error = [NSError errorWithDomain:1]; // expected-warning{{Writing into an auto-releasing variable}} + } + dispatch_semaphore_signal(sem); + }); + + dispatch_semaphore_wait(sem, 100); + return 0; +} + +- (BOOL) writeToError:(NSError *__autoreleasing *)error { + *error = [NSError errorWithDomain:1]; // no-warning + return 0; +} +@end + +BOOL writeToErrorInBlockFromCFunc(NSError *__autoreleasing* error) { + dispatch_semaphore_t sem = dispatch_semaphore_create(0l); + dispatch_async(queue, ^{ + if (error) { + *error = [NSError errorWithDomain:1]; // expected-warning{{Writing into an auto-releasing variable}} + } + dispatch_semaphore_signal(sem); + }); + + dispatch_semaphore_wait(sem, 100); + return 0; +} + +BOOL writeToErrorNoWarning(NSError *__autoreleasing* error) { + *error = [NSError errorWithDomain:1]; // no-warning + return 0; +}