diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst index c1eedb33e74d2..4cbd31f44d3f6 100644 --- a/clang/docs/analyzer/checkers.rst +++ b/clang/docs/analyzer/checkers.rst @@ -3713,6 +3713,26 @@ Here are some examples of situations that we warn about as they *might* be poten NSObject* unretained = retained.get(); // warn } +webkit.RetainPtrCtorAdoptChecker +"""""""""""""""""""""""""""""""" +The goal of this rule is to make sure the constructor of RetinPtr as well as adoptNS and adoptCF are used correctly. +When creating a RetainPtr with +1 semantics, adoptNS or adoptCF should be used, and in +0 semantics, RetainPtr constructor should be used. +Warn otherwise. + +These are examples of cases that we consider correct: + + .. code-block:: cpp + + RetainPtr ptr = adoptNS([[NSObject alloc] init]); // ok + RetainPtr ptr = CGImageGetColorSpace(image); // ok + +Here are some examples of cases that we consider incorrect use of RetainPtr constructor and adoptCF + + .. code-block:: cpp + + RetainPtr ptr = [[NSObject alloc] init]; // warn + auto ptr = adoptCF(CGImageGetColorSpace(image)); // warn + Debug Checkers --------------- diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 410f841630660..9aa696d8803b1 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1786,4 +1786,8 @@ def UnretainedLocalVarsChecker : Checker<"UnretainedLocalVarsChecker">, HelpText<"Check unretained local variables.">, Documentation; +def RetainPtrCtorAdoptChecker : Checker<"RetainPtrCtorAdoptChecker">, + HelpText<"Check for correct use of RetainPtr constructor, adoptNS, and adoptCF">, + Documentation; + } // end alpha.webkit diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 5910043440987..0b6b169d7b447 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -133,6 +133,7 @@ add_clang_library(clangStaticAnalyzerCheckers WebKit/MemoryUnsafeCastChecker.cpp WebKit/PtrTypesSemantics.cpp WebKit/RefCntblBaseVirtualDtorChecker.cpp + WebKit/RetainPtrCtorAdoptChecker.cpp WebKit/RawPtrRefCallArgsChecker.cpp WebKit/UncountedLambdaCapturesChecker.cpp WebKit/RawPtrRefLocalVarsChecker.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index 7899b19854806..7e7bd49ca0bdb 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -225,15 +225,16 @@ void RetainTypeChecker::visitTypedef(const TypedefDecl *TD) { return; for (auto *Redecl : RT->getDecl()->getMostRecentDecl()->redecls()) { - if (Redecl->getAttr()) { + if (Redecl->getAttr() || + Redecl->getAttr()) { CFPointees.insert(RT); return; } } } -bool RetainTypeChecker::isUnretained(const QualType QT) { - if (ento::cocoa::isCocoaObjectRef(QT) && !IsARCEnabled) +bool RetainTypeChecker::isUnretained(const QualType QT, bool ignoreARC) { + if (ento::cocoa::isCocoaObjectRef(QT) && (!IsARCEnabled || ignoreARC)) return true; auto CanonicalType = QT.getCanonicalType(); auto PointeeType = CanonicalType->getPointeeType(); diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h index fcc1a41dba78b..f69d374cbbf90 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h @@ -75,7 +75,8 @@ class RetainTypeChecker { public: void visitTranslationUnitDecl(const TranslationUnitDecl *); void visitTypedef(const TypedefDecl *); - bool isUnretained(const QualType); + bool isUnretained(const QualType, bool ignoreARC = false); + bool isARCEnabled() const { return IsARCEnabled; } }; /// \returns true if \p Class is NS or CF objects AND not retained, false if diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp new file mode 100644 index 0000000000000..a97e497ad315d --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp @@ -0,0 +1,346 @@ +//=======- RetainPtrCtorAdoptChecker.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 +// +//===----------------------------------------------------------------------===// + +#include "ASTUtils.h" +#include "DiagOutputUtils.h" +#include "PtrTypesSemantics.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/Analysis/RetainSummaryManager.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/CheckerContext.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/SetVector.h" +#include + +using namespace clang; +using namespace ento; + +namespace { + +class RetainPtrCtorAdoptChecker + : public Checker> { +private: + BugType Bug; + mutable BugReporter *BR; + mutable std::unique_ptr Summaries; + mutable RetainTypeChecker RTC; + +public: + RetainPtrCtorAdoptChecker() + : Bug(this, "Correct use of RetainPtr, adoptNS, and adoptCF", + "WebKit coding guidelines") {} + + void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, + BugReporter &BRArg) const { + BR = &BRArg; + + // The calls to checkAST* from AnalysisConsumer don't + // visit template instantiations or lambda classes. We + // want to visit those, so we make our own RecursiveASTVisitor. + struct LocalVisitor : public RecursiveASTVisitor { + const RetainPtrCtorAdoptChecker *Checker; + Decl *DeclWithIssue{nullptr}; + + using Base = RecursiveASTVisitor; + + explicit LocalVisitor(const RetainPtrCtorAdoptChecker *Checker) + : Checker(Checker) { + assert(Checker); + } + + bool shouldVisitTemplateInstantiations() const { return true; } + bool shouldVisitImplicitCode() const { return false; } + + bool TraverseDecl(Decl *D) { + llvm::SaveAndRestore SavedDecl(DeclWithIssue); + if (D && (isa(D) || isa(D))) + DeclWithIssue = D; + return Base::TraverseDecl(D); + } + + bool TraverseClassTemplateDecl(ClassTemplateDecl *CTD) { + if (safeGetName(CTD) == "RetainPtr") + return true; // Skip the contents of RetainPtr. + return Base::TraverseClassTemplateDecl(CTD); + } + + bool VisitTypedefDecl(TypedefDecl *TD) { + Checker->RTC.visitTypedef(TD); + return true; + } + + bool VisitCallExpr(const CallExpr *CE) { + Checker->visitCallExpr(CE, DeclWithIssue); + return true; + } + + bool VisitCXXConstructExpr(const CXXConstructExpr *CE) { + Checker->visitConstructExpr(CE, DeclWithIssue); + return true; + } + + llvm::SetVector Decls; + llvm::DenseSet CRTPs; + }; + + LocalVisitor visitor(this); + Summaries = std::make_unique( + TUD->getASTContext(), true /* trackObjCAndCFObjects */, + false /* trackOSObjects */); + RTC.visitTranslationUnitDecl(TUD); + visitor.TraverseDecl(const_cast(TUD)); + } + + bool isAdoptFn(const Decl *FnDecl) const { + auto Name = safeGetName(FnDecl); + return Name == "adoptNS" || Name == "adoptCF" || Name == "adoptNSArc" || + Name == "adoptCFArc"; + } + + bool isAdoptNS(const Decl *FnDecl) const { + auto Name = safeGetName(FnDecl); + return Name == "adoptNS" || Name == "adoptNSArc"; + } + + void visitCallExpr(const CallExpr *CE, const Decl *DeclWithIssue) const { + if (BR->getSourceManager().isInSystemHeader(CE->getExprLoc())) + return; + + auto *F = CE->getDirectCallee(); + if (!F) + return; + + if (!isAdoptFn(F) || !CE->getNumArgs()) + return; + + auto *Arg = CE->getArg(0)->IgnoreParenCasts(); + auto Result = isOwned(Arg); + auto Name = safeGetName(F); + if (Result == IsOwnedResult::Unknown) + reportUnknown(Name, CE, DeclWithIssue); + else if (Result == IsOwnedResult::NotOwned) { + if (RTC.isARCEnabled() && isAdoptNS(F)) { + if (!isAllocInit(Arg)) + reportUseAfterFree(Name, CE, DeclWithIssue, "when ARC is disabled"); + } else + reportUseAfterFree(Name, CE, DeclWithIssue); + } + } + + void visitConstructExpr(const CXXConstructExpr *CE, + const Decl *DeclWithIssue) const { + if (BR->getSourceManager().isInSystemHeader(CE->getExprLoc())) + return; + + auto *Ctor = CE->getConstructor(); + if (!Ctor) + return; + + auto *Cls = Ctor->getParent(); + if (!Cls) + return; + + if (safeGetName(Cls) != "RetainPtr" || !CE->getNumArgs()) + return; + + // Ignore RetainPtr construction inside adoptNS, adoptCF, and retainPtr. + if (isAdoptFn(DeclWithIssue) || safeGetName(DeclWithIssue) == "retainPtr") + return; + + std::string Name = "RetainPtr constructor"; + auto *Arg = CE->getArg(0)->IgnoreParenCasts(); + auto Result = isOwned(Arg); + if (Result == IsOwnedResult::Unknown) + reportUnknown(Name, CE, DeclWithIssue); + else if (Result == IsOwnedResult::Owned) + reportLeak(Name, CE, DeclWithIssue); + else if (RTC.isARCEnabled() && isAllocInit(Arg)) + reportLeak(Name, CE, DeclWithIssue, "when ARC is disabled"); + } + + bool isAllocInit(const Expr *E) const { + auto *ObjCMsgExpr = dyn_cast(E); + if (!ObjCMsgExpr) + return false; + auto Selector = ObjCMsgExpr->getSelector(); + auto NameForFirstSlot = Selector.getNameForSlot(0); + if (NameForFirstSlot == "alloc" || NameForFirstSlot.starts_with("copy") || + NameForFirstSlot.starts_with("mutableCopy")) + return true; + if (!NameForFirstSlot.starts_with("init")) + return false; + if (!ObjCMsgExpr->isInstanceMessage()) + return false; + auto *Receiver = ObjCMsgExpr->getInstanceReceiver()->IgnoreParenCasts(); + if (!Receiver) + return false; + auto *InnerObjCMsgExpr = dyn_cast(Receiver); + if (!InnerObjCMsgExpr) + return false; + auto InnerSelector = InnerObjCMsgExpr->getSelector(); + return InnerSelector.getNameForSlot(0) == "alloc"; + } + + enum class IsOwnedResult { Unknown, Skip, Owned, NotOwned }; + IsOwnedResult isOwned(const Expr *E) const { + while (1) { + if (isa(E)) + return IsOwnedResult::NotOwned; + if (auto *DRE = dyn_cast(E)) { + auto QT = DRE->getType(); + if (isRetainPtrType(QT)) + return IsOwnedResult::NotOwned; + QT = QT.getCanonicalType(); + if (RTC.isUnretained(QT, true /* ignoreARC */)) + return IsOwnedResult::NotOwned; + auto *PointeeType = QT->getPointeeType().getTypePtrOrNull(); + if (PointeeType && PointeeType->isVoidType()) + return IsOwnedResult::NotOwned; // Assume reading void* as +0. + } + if (auto *TE = dyn_cast(E)) { + E = TE->getSubExpr(); + continue; + } + if (auto *ObjCMsgExpr = dyn_cast(E)) { + auto Summary = Summaries->getSummary(AnyCall(ObjCMsgExpr)); + auto RetEffect = Summary->getRetEffect(); + switch (RetEffect.getKind()) { + case RetEffect::NoRet: + return IsOwnedResult::Unknown; + case RetEffect::OwnedSymbol: + return IsOwnedResult::Owned; + case RetEffect::NotOwnedSymbol: + return IsOwnedResult::NotOwned; + case RetEffect::OwnedWhenTrackedReceiver: + if (auto *Receiver = ObjCMsgExpr->getInstanceReceiver()) { + E = Receiver->IgnoreParenCasts(); + continue; + } + return IsOwnedResult::Unknown; + case RetEffect::NoRetHard: + return IsOwnedResult::Unknown; + } + } + if (auto *CXXCE = dyn_cast(E)) { + if (auto *MD = CXXCE->getMethodDecl()) { + auto *Cls = MD->getParent(); + if (auto *CD = dyn_cast(MD)) { + auto QT = CD->getConversionType().getCanonicalType(); + auto *ResultType = QT.getTypePtrOrNull(); + if (safeGetName(Cls) == "RetainPtr" && ResultType && + (ResultType->isPointerType() || ResultType->isReferenceType() || + ResultType->isObjCObjectPointerType())) + return IsOwnedResult::NotOwned; + } + if (safeGetName(MD) == "leakRef" && safeGetName(Cls) == "RetainPtr") + return IsOwnedResult::Owned; + } + } + if (auto *CE = dyn_cast(E)) { + if (auto *Callee = CE->getDirectCallee()) { + if (isAdoptFn(Callee)) + return IsOwnedResult::NotOwned; + if (safeGetName(Callee) == "__builtin___CFStringMakeConstantString") + return IsOwnedResult::NotOwned; + auto RetType = Callee->getReturnType(); + if (isRetainPtrType(RetType)) + return IsOwnedResult::NotOwned; + } else if (auto *CalleeExpr = CE->getCallee()) { + if (isa(CalleeExpr)) + return IsOwnedResult::Skip; // Wait for instantiation. + } + auto Summary = Summaries->getSummary(AnyCall(CE)); + auto RetEffect = Summary->getRetEffect(); + switch (RetEffect.getKind()) { + case RetEffect::NoRet: + return IsOwnedResult::Unknown; + case RetEffect::OwnedSymbol: + return IsOwnedResult::Owned; + case RetEffect::NotOwnedSymbol: + return IsOwnedResult::NotOwned; + case RetEffect::OwnedWhenTrackedReceiver: + return IsOwnedResult::Unknown; + case RetEffect::NoRetHard: + return IsOwnedResult::Unknown; + } + } + break; + } + return IsOwnedResult::Unknown; + } + + template + void reportUnknown(std::string &Name, const ExprType *CE, + const Decl *DeclWithIssue) const { + SmallString<100> Buf; + llvm::raw_svector_ostream Os(Buf); + + Os << Name << " is called on an object in unknown state."; + + PathDiagnosticLocation BSLoc(CE->getSourceRange().getBegin(), + BR->getSourceManager()); + auto Report = std::make_unique(Bug, Os.str(), BSLoc); + Report->addRange(CE->getSourceRange()); + Report->setDeclWithIssue(DeclWithIssue); + BR->emitReport(std::move(Report)); + } + + void reportUseAfterFree(std::string &Name, const CallExpr *CE, + const Decl *DeclWithIssue, + const char *condition = nullptr) const { + SmallString<100> Buf; + llvm::raw_svector_ostream Os(Buf); + + Os << "Incorrect use of " << Name + << ". The argument is +0 and results in an use-after-free"; + if (condition) + Os << " " << condition; + Os << "."; + + PathDiagnosticLocation BSLoc(CE->getSourceRange().getBegin(), + BR->getSourceManager()); + auto Report = std::make_unique(Bug, Os.str(), BSLoc); + Report->addRange(CE->getSourceRange()); + Report->setDeclWithIssue(DeclWithIssue); + BR->emitReport(std::move(Report)); + } + + void reportLeak(std::string &Name, const CXXConstructExpr *CE, + const Decl *DeclWithIssue, + const char *condition = nullptr) const { + SmallString<100> Buf; + llvm::raw_svector_ostream Os(Buf); + + Os << "Incorrect use of " << Name + << ". The argument is +1 and results in a memory leak"; + if (condition) + Os << " " << condition; + Os << "."; + + PathDiagnosticLocation BSLoc(CE->getSourceRange().getBegin(), + BR->getSourceManager()); + auto Report = std::make_unique(Bug, Os.str(), BSLoc); + Report->addRange(CE->getSourceRange()); + Report->setDeclWithIssue(DeclWithIssue); + BR->emitReport(std::move(Report)); + } +}; +} // namespace + +void ento::registerRetainPtrCtorAdoptChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterRetainPtrCtorAdoptChecker(const CheckerManager &mgr) { + return true; +} diff --git a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h index 7bb33bcb6cf44..9b13810d0c5c9 100644 --- a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h +++ b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h @@ -1,30 +1,94 @@ @class NSString; @class NSArray; @class NSMutableArray; +#define nil ((id)0) #define CF_BRIDGED_TYPE(T) __attribute__((objc_bridge(T))) +#define CF_BRIDGED_MUTABLE_TYPE(T) __attribute__((objc_bridge_mutable(T))) typedef CF_BRIDGED_TYPE(id) void * CFTypeRef; +typedef signed char BOOL; typedef signed long CFIndex; -typedef const struct __CFAllocator * CFAllocatorRef; +typedef unsigned long NSUInteger; +typedef const struct CF_BRIDGED_TYPE(id) __CFAllocator * CFAllocatorRef; typedef const struct CF_BRIDGED_TYPE(NSString) __CFString * CFStringRef; typedef const struct CF_BRIDGED_TYPE(NSArray) __CFArray * CFArrayRef; -typedef struct CF_BRIDGED_TYPE(NSMutableArray) __CFArray * CFMutableArrayRef; +typedef struct CF_BRIDGED_MUTABLE_TYPE(NSMutableArray) __CFArray * CFMutableArrayRef; +typedef struct CF_BRIDGED_MUTABLE_TYPE(CFRunLoopRef) __CFRunLoop * CFRunLoopRef; extern const CFAllocatorRef kCFAllocatorDefault; +typedef struct _NSZone NSZone; CFMutableArrayRef CFArrayCreateMutable(CFAllocatorRef allocator, CFIndex capacity); extern void CFArrayAppendValue(CFMutableArrayRef theArray, const void *value); CFArrayRef CFArrayCreate(CFAllocatorRef allocator, const void **values, CFIndex numValues); CFIndex CFArrayGetCount(CFArrayRef theArray); +CFRunLoopRef CFRunLoopGetCurrent(void); +CFRunLoopRef CFRunLoopGetMain(void); extern CFTypeRef CFRetain(CFTypeRef cf); extern void CFRelease(CFTypeRef cf); +#define CFSTR(cStr) ((CFStringRef) __builtin___CFStringMakeConstantString ("" cStr "")) +extern Class NSClassFromString(NSString *aClassName); __attribute__((objc_root_class)) @interface NSObject + (instancetype) alloc; ++ (Class) class; ++ (Class) superclass; - (instancetype) init; - (instancetype)retain; - (void)release; +- (BOOL)isKindOfClass:(Class)aClass; +@end + +@protocol NSCopying +- (id)copyWithZone:(NSZone *)zone; +@end + +@protocol NSFastEnumeration +- (int)countByEnumeratingWithState:(void *)state objects:(id *)objects count:(unsigned)count; +- (void)protocolMethod; +@end + +@interface NSEnumerator +@end + +@interface NSDictionary : NSObject +- (NSUInteger)count; +- (id)objectForKey:(id)aKey; +- (id)objectForKeyedSubscript:(id)aKey; +- (NSEnumerator *)keyEnumerator; ++ (id)dictionary; ++ (id)dictionaryWithObject:(id)object forKey:(id )key; ++ (instancetype)dictionaryWithObjects:(const id [])objects forKeys:(const id [])keys count:(NSUInteger)cnt; +@end + +@interface NSArray : NSObject +- (NSUInteger)count; +- (NSEnumerator *)objectEnumerator; ++ (NSArray *)arrayWithObjects:(const id [])objects count:(NSUInteger)count; +@end + +@interface NSString : NSObject +- (NSUInteger)length; +- (NSString *)stringByAppendingString:(NSString *)aString; +- ( const char *)UTF8String; +- (id)initWithUTF8String:(const char *)nullTerminatedCString; ++ (id)stringWithUTF8String:(const char *)nullTerminatedCString; +@end + +@interface NSMutableString : NSString +@end + +@interface NSValue : NSObject +- (void)getValue:(void *)value; +@end + +@interface NSNumber : NSValue +- (char)charValue; +- (id)initWithInt:(int)value; ++ (NSNumber *)numberWithInt:(int)value; @end @interface SomeObj : NSObject +- (SomeObj *)mutableCopy; +- (SomeObj *)copyWithValue:(int)value; - (void)doWork; - (SomeObj *)other; - (SomeObj *)next; @@ -57,28 +121,34 @@ template struct RetainPtr { PtrType t; RetainPtr() : t(nullptr) { } - RetainPtr(PtrType t) : t(t) { if (t) - CFRetain(t); + CFRetain(toCFTypeRef(t)); } - RetainPtr(RetainPtr&& o) - : RetainPtr(o.t) - { - o.t = nullptr; - } - RetainPtr(const RetainPtr& o) + RetainPtr(RetainPtr&&); + RetainPtr(const RetainPtr&); + template + RetainPtr(const RetainPtr& o) : RetainPtr(o.t) + {} + RetainPtr operator=(const RetainPtr& o) { + if (t) + CFRelease(toCFTypeRef(t)); + t = o.t; + if (t) + CFRetain(toCFTypeRef(t)); + return *this; } - RetainPtr operator=(const RetainPtr& o) + template + RetainPtr operator=(const RetainPtr& o) { if (t) - CFRelease(t); + CFRelease(toCFTypeRef(t)); t = o.t; if (t) - CFRetain(t); + CFRetain(toCFTypeRef(t)); return *this; } ~RetainPtr() { @@ -86,7 +156,7 @@ template struct RetainPtr { } void clear() { if (t) - CFRelease(t); + CFRelease(toCFTypeRef(t)); t = nullptr; } void swap(RetainPtr& o) { @@ -102,10 +172,19 @@ template struct RetainPtr { swap(o); return *this; } + PtrType leakRef() + { + PtrType s = t; + t = nullptr; + return s; + } operator PtrType() const { return t; } operator bool() const { return t; } private: + CFTypeRef toCFTypeRef(id ptr) { return (__bridge CFTypeRef)ptr; } + CFTypeRef toCFTypeRef(const void* ptr) { return (CFTypeRef)ptr; } + template friend RetainPtr adoptNS(U*); template friend RetainPtr adoptCF(U); @@ -113,9 +192,26 @@ template struct RetainPtr { RetainPtr(PtrType t, AdoptTag) : t(t) { } }; +template +RetainPtr::RetainPtr(RetainPtr&& o) + : RetainPtr(o.t) +{ + o.t = nullptr; +} + +template +RetainPtr::RetainPtr(const RetainPtr& o) + : RetainPtr(o.t) +{ +} + template RetainPtr adoptNS(T* t) { +#if __has_feature(objc_arc) + return t; +#else return RetainPtr(t, RetainPtr::Adopt); +#endif } template @@ -123,9 +219,31 @@ RetainPtr adoptCF(T t) { return RetainPtr(t, RetainPtr::Adopt); } +template inline RetainPtr retainPtr(T ptr) +{ + return ptr; +} + +template inline RetainPtr retainPtr(T* ptr) +{ + return ptr; +} + +inline NSObject *bridge_cast(CFTypeRef object) +{ + return (__bridge NSObject *)object; +} + +inline CFTypeRef bridge_cast(NSObject *object) +{ + return (__bridge CFTypeRef)object; +} + } using WTF::RetainPtr; using WTF::adoptNS; using WTF::adoptCF; +using WTF::retainPtr; using WTF::downcast; +using WTF::bridge_cast; \ No newline at end of file diff --git a/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use-arc.mm b/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use-arc.mm new file mode 100644 index 0000000000000..5d0c64e0f44b6 --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use-arc.mm @@ -0,0 +1,85 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.RetainPtrCtorAdoptChecker -fobjc-arc -verify %s + +#include "objc-mock-types.h" + +void basic_correct() { + auto ns1 = adoptNS([SomeObj alloc]); + auto ns2 = adoptNS([[SomeObj alloc] init]); + RetainPtr ns3 = [ns1.get() next]; + auto ns4 = adoptNS([ns3 mutableCopy]); + auto ns5 = adoptNS([ns3 copyWithValue:3]); + auto ns6 = retainPtr([ns3 next]); + CFMutableArrayRef cf1 = adoptCF(CFArrayCreateMutable(kCFAllocatorDefault, 10)); +} + +CFMutableArrayRef provide_cf(); + +void basic_wrong() { + RetainPtr ns1 = [[SomeObj alloc] init]; + // expected-warning@-1{{Incorrect use of RetainPtr constructor. The argument is +1 and results in a memory leak when ARC is disabled [alpha.webkit.RetainPtrCtorAdoptChecker]}} + auto ns2 = adoptNS([ns1.get() next]); + // expected-warning@-1{{Incorrect use of adoptNS. The argument is +0 and results in an use-after-free when ARC is disabled [alpha.webkit.RetainPtrCtorAdoptChecker]}} + RetainPtr cf1 = CFArrayCreateMutable(kCFAllocatorDefault, 10); + // expected-warning@-1{{Incorrect use of RetainPtr constructor. The argument is +1 and results in a memory leak [alpha.webkit.RetainPtrCtorAdoptChecker]}} + RetainPtr cf2 = adoptCF(provide_cf()); + // expected-warning@-1{{Incorrect use of adoptCF. The argument is +0 and results in an use-after-free [alpha.webkit.RetainPtrCtorAdoptChecker]}} +} + +RetainPtr return_nullptr() { + return nullptr; +} + +RetainPtr return_retainptr() { + RetainPtr foo; + return foo; +} + +CFTypeRef CopyValueForSomething(); + +void cast_retainptr() { + RetainPtr foo; + RetainPtr bar = static_cast(foo); + + auto baz = adoptCF(CopyValueForSomething()).get(); + RetainPtr v = static_cast(baz); +} + +void adopt_retainptr() { + RetainPtr foo = adoptNS([[SomeObj alloc] init]); +} + +RetainPtr return_arg(CFArrayRef arg) { + return arg; +} + +class MemberInit { +public: + MemberInit(CFMutableArrayRef array, NSString *str, CFRunLoopRef runLoop) + : m_array(array) + , m_str(str) + , m_runLoop(runLoop) + { } + +private: + RetainPtr m_array; + RetainPtr m_str; + RetainPtr m_runLoop; +}; +void create_member_init() { + MemberInit init { CFArrayCreateMutable(kCFAllocatorDefault, 10), @"hello", CFRunLoopGetCurrent() }; +} + +RetainPtr cfstr() { + return CFSTR(""); +} + +template +static RetainPtr bridge_cast(RetainPtr&& ptr) +{ + return adoptNS((__bridge NSArray *)(ptr.leakRef())); +} + +RetainPtr create_cf_array(); +RetainPtr return_bridge_cast() { + return bridge_cast(create_cf_array()); +} diff --git a/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use.mm b/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use.mm new file mode 100644 index 0000000000000..20b9e2d485c95 --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use.mm @@ -0,0 +1,85 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.RetainPtrCtorAdoptChecker -verify %s + +#include "objc-mock-types.h" + +void basic_correct() { + auto ns1 = adoptNS([SomeObj alloc]); + auto ns2 = adoptNS([[SomeObj alloc] init]); + RetainPtr ns3 = [ns1.get() next]; + auto ns4 = adoptNS([ns3 mutableCopy]); + auto ns5 = adoptNS([ns3 copyWithValue:3]); + auto ns6 = retainPtr([ns3 next]); + CFMutableArrayRef cf1 = adoptCF(CFArrayCreateMutable(kCFAllocatorDefault, 10)); +} + +CFMutableArrayRef provide_cf(); + +void basic_wrong() { + RetainPtr ns1 = [[SomeObj alloc] init]; + // expected-warning@-1{{Incorrect use of RetainPtr constructor. The argument is +1 and results in a memory leak [alpha.webkit.RetainPtrCtorAdoptChecker]}} + auto ns2 = adoptNS([ns1.get() next]); + // expected-warning@-1{{Incorrect use of adoptNS. The argument is +0 and results in an use-after-free [alpha.webkit.RetainPtrCtorAdoptChecker]}} + RetainPtr cf1 = CFArrayCreateMutable(kCFAllocatorDefault, 10); + // expected-warning@-1{{Incorrect use of RetainPtr constructor. The argument is +1 and results in a memory leak [alpha.webkit.RetainPtrCtorAdoptChecker]}} + RetainPtr cf2 = adoptCF(provide_cf()); + // expected-warning@-1{{Incorrect use of adoptCF. The argument is +0 and results in an use-after-free [alpha.webkit.RetainPtrCtorAdoptChecker]}} +} + +RetainPtr return_nullptr() { + return nullptr; +} + +RetainPtr return_retainptr() { + RetainPtr foo; + return foo; +} + +CFTypeRef CopyValueForSomething(); + +void cast_retainptr() { + RetainPtr foo; + RetainPtr bar = static_cast(foo); + + auto baz = adoptCF(CopyValueForSomething()).get(); + RetainPtr v = static_cast(baz); +} + +void adopt_retainptr() { + RetainPtr foo = adoptNS([[SomeObj alloc] init]); +} + +RetainPtr return_arg(CFArrayRef arg) { + return arg; +} + +class MemberInit { +public: + MemberInit(CFMutableArrayRef array, NSString *str, CFRunLoopRef runLoop) + : m_array(array) + , m_str(str) + , m_runLoop(runLoop) + { } + +private: + RetainPtr m_array; + RetainPtr m_str; + RetainPtr m_runLoop; +}; +void create_member_init() { + MemberInit init { CFArrayCreateMutable(kCFAllocatorDefault, 10), @"hello", CFRunLoopGetCurrent() }; +} + +RetainPtr cfstr() { + return CFSTR(""); +} + +template +static RetainPtr bridge_cast(RetainPtr&& ptr) +{ + return adoptNS((__bridge NSArray *)(ptr.leakRef())); +} + +RetainPtr create_cf_array(); +RetainPtr return_bridge_cast() { + return bridge_cast(create_cf_array()); +}