From: Ted Kremenek Date: Tue, 6 Mar 2012 20:06:12 +0000 (+0000) Subject: Add static analyzer support for new NSArray/NSDictionary/NSNumber literals. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=1a45a5ff5d495cb6cd9a3d4d06317af79c0f634d;p=clang Add static analyzer support for new NSArray/NSDictionary/NSNumber literals. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@152139 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h b/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h index f886c3e1b3..6d1da6e445 100644 --- a/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h +++ b/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h @@ -16,6 +16,8 @@ #define LLVM_CLANG_GR_SVALBUILDER #include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/ExprObjC.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" @@ -216,6 +218,10 @@ public: BasicVals.getValue(integer->getValue(), integer->getType()->isUnsignedIntegerOrEnumerationType())); } + + nonloc::ConcreteInt makeBoolVal(const ObjCBoolLiteralExpr *boolean) { + return makeTruthVal(boolean->getValue(), boolean->getType()); + } nonloc::ConcreteInt makeBoolVal(const CXXBoolLiteralExpr *boolean); diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp b/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp index 6b14013eea..f7dd6c2127 100644 --- a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp @@ -1861,41 +1861,49 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, if (!PrevT) { const Stmt *S = cast(N->getLocation()).getStmt(); - if (const CallExpr *CE = dyn_cast(S)) { - // Get the name of the callee (if it is available). - SVal X = CurrSt->getSValAsScalarOrLoc(CE->getCallee(), LCtx); - if (const FunctionDecl *FD = X.getAsFunctionDecl()) - os << "Call to function '" << *FD << '\''; - else - os << "function call"; - } - else { - assert(isa(S)); - // The message expression may have between written directly or as - // a property access. Lazily determine which case we are looking at. - os << (isPropertyAccess(S, N->getParentMap()) ? "Property" : "Method"); - } + if (isa(S)) { + os << "NSArray literal is an object with a +0 retain count"; + } + else if (isa(S)) { + os << "NSDictionary literal is an object with a +0 retain count"; + } + else { + if (const CallExpr *CE = dyn_cast(S)) { + // Get the name of the callee (if it is available). + SVal X = CurrSt->getSValAsScalarOrLoc(CE->getCallee(), LCtx); + if (const FunctionDecl *FD = X.getAsFunctionDecl()) + os << "Call to function '" << *FD << '\''; + else + os << "function call"; + } + else { + assert(isa(S)); + // The message expression may have between written directly or as + // a property access. Lazily determine which case we are looking at. + os << (isPropertyAccess(S, N->getParentMap()) ? "Property" : "Method"); + } - if (CurrV.getObjKind() == RetEffect::CF) { - os << " returns a Core Foundation object with a "; - } - else { - assert (CurrV.getObjKind() == RetEffect::ObjC); - os << " returns an Objective-C object with a "; - } + if (CurrV.getObjKind() == RetEffect::CF) { + os << " returns a Core Foundation object with a "; + } + else { + assert (CurrV.getObjKind() == RetEffect::ObjC); + os << " returns an Objective-C object with a "; + } - if (CurrV.isOwned()) { - os << "+1 retain count"; + if (CurrV.isOwned()) { + os << "+1 retain count"; - if (GCEnabled) { - assert(CurrV.getObjKind() == RetEffect::CF); - os << ". " - "Core Foundation objects are not automatically garbage collected."; + if (GCEnabled) { + assert(CurrV.getObjKind() == RetEffect::CF); + os << ". " + "Core Foundation objects are not automatically garbage collected."; + } + } + else { + assert (CurrV.isNotOwned()); + os << "+0 retain count"; } - } - else { - assert (CurrV.isNotOwned()); - os << "+0 retain count"; } PathDiagnosticLocation Pos(S, BRC.getSourceManager(), @@ -2295,6 +2303,8 @@ class RetainCountChecker check::PostStmt, check::PostStmt, check::PostStmt, + check::PostStmt, + check::PostStmt, check::PostObjCMessage, check::PreStmt, check::RegionChanges, @@ -2439,7 +2449,10 @@ public: void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; void checkPostStmt(const CXXConstructExpr *CE, CheckerContext &C) const; + void checkPostStmt(const ObjCArrayLiteral *AL, CheckerContext &C) const; + void checkPostStmt(const ObjCDictionaryLiteral *DL, CheckerContext &C) const; void checkPostObjCMessage(const ObjCMessage &Msg, CheckerContext &C) const; + void checkSummary(const RetainSummary &Summ, const CallOrObjCMessage &Call, CheckerContext &C) const; @@ -2474,6 +2487,8 @@ public: void processNonLeakError(ProgramStateRef St, SourceRange ErrorRange, RefVal::Kind ErrorKind, SymbolRef Sym, CheckerContext &C) const; + + void processObjCLiterals(CheckerContext &C, const Expr *Ex) const; const ProgramPointTag *getDeadSymbolTag(SymbolRef sym) const; @@ -2637,6 +2652,49 @@ void RetainCountChecker::checkPostStmt(const CXXConstructExpr *CE, checkSummary(*Summ, CallOrObjCMessage(CE, state, C.getLocationContext()), C); } +void RetainCountChecker::processObjCLiterals(CheckerContext &C, + const Expr *Ex) const { + ProgramStateRef state = C.getState(); + const ExplodedNode *pred = C.getPredecessor(); + for (Stmt::const_child_iterator it = Ex->child_begin(), et = Ex->child_end() ; + it != et ; ++it) { + const Stmt *child = *it; + SVal V = state->getSVal(child, pred->getLocationContext()); + if (SymbolRef sym = V.getAsSymbol()) + if (const RefVal* T = state->get(sym)) { + RefVal::Kind hasErr = (RefVal::Kind) 0; + state = updateSymbol(state, sym, *T, MayEscape, hasErr, C); + if (hasErr) { + processNonLeakError(state, child->getSourceRange(), hasErr, sym, C); + return; + } + } + } + + // Return the object as autoreleased. + // RetEffect RE = RetEffect::MakeNotOwned(RetEffect::ObjC); + if (SymbolRef sym = + state->getSVal(Ex, pred->getLocationContext()).getAsSymbol()) { + QualType ResultTy = Ex->getType(); + state = state->set(sym, RefVal::makeNotOwned(RetEffect::ObjC, + ResultTy)); + } + + C.addTransition(state); +} + +void RetainCountChecker::checkPostStmt(const ObjCArrayLiteral *AL, + CheckerContext &C) const { + // Apply the 'MayEscape' to all values. + processObjCLiterals(C, AL); +} + +void RetainCountChecker::checkPostStmt(const ObjCDictionaryLiteral *DL, + CheckerContext &C) const { + // Apply the 'MayEscape' to all keys and values. + processObjCLiterals(C, DL); +} + void RetainCountChecker::checkPostObjCMessage(const ObjCMessage &Msg, CheckerContext &C) const { ProgramStateRef state = C.getState(); diff --git a/lib/StaticAnalyzer/Core/Environment.cpp b/lib/StaticAnalyzer/Core/Environment.cpp index 5207a506ae..b5ea3db7f3 100644 --- a/lib/StaticAnalyzer/Core/Environment.cpp +++ b/lib/StaticAnalyzer/Core/Environment.cpp @@ -79,6 +79,9 @@ SVal Environment::getSVal(const EnvironmentEntry &Entry, else return svalBuilder.makeIntVal(cast(E)); } + case Stmt::ObjCBoolLiteralExprClass: + return svalBuilder.makeBoolVal(cast(E)); + // For special C0xx nullptr case, make a null pointer SVal. case Stmt::CXXNullPtrLiteralExprClass: return svalBuilder.makeNull(); diff --git a/lib/StaticAnalyzer/Core/ExprEngine.cpp b/lib/StaticAnalyzer/Core/ExprEngine.cpp index 7528b4aaa4..408bcd3528 100644 --- a/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -555,6 +555,10 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, Bldr.addNodes(Dst); break; + // FIXME. + case Stmt::ObjCSubscriptRefExprClass: + break; + case Stmt::ObjCPropertyRefExprClass: // Implicitly handled by Environment::getSVal(). break; @@ -586,6 +590,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::ObjCIsaExprClass: case Stmt::ObjCProtocolExprClass: case Stmt::ObjCSelectorExprClass: + case Expr::ObjCNumericLiteralClass: case Stmt::ParenListExprClass: case Stmt::PredefinedExprClass: case Stmt::ShuffleVectorExprClass: @@ -602,6 +607,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::IntegerLiteralClass: case Stmt::CharacterLiteralClass: case Stmt::CXXBoolLiteralExprClass: + case Stmt::ObjCBoolLiteralExprClass: case Stmt::FloatingLiteralClass: case Stmt::SizeOfPackExprClass: case Stmt::StringLiteralClass: @@ -616,6 +622,36 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, break; } + case Expr::ObjCArrayLiteralClass: + case Expr::ObjCDictionaryLiteralClass: { + Bldr.takeNodes(Pred); + + ExplodedNodeSet preVisit; + getCheckerManager().runCheckersForPreStmt(preVisit, Pred, S, *this); + + // FIXME: explicitly model with a region and the actual contents + // of the container. For now, conjure a symbol. + ExplodedNodeSet Tmp; + StmtNodeBuilder Bldr2(preVisit, Tmp, *currentBuilderContext); + + for (ExplodedNodeSet::iterator it = preVisit.begin(), et = preVisit.end(); + it != et; ++it) { + ExplodedNode *N = *it; + const Expr *Ex = cast(S); + QualType resultType = Ex->getType(); + const LocationContext *LCtx = N->getLocationContext(); + SVal result = + svalBuilder.getConjuredSymbolVal(0, Ex, LCtx, resultType, + currentBuilderContext->getCurrentBlockCount()); + ProgramStateRef state = N->getState()->BindExpr(Ex, LCtx, result); + Bldr2.generateNode(S, N, state); + } + + getCheckerManager().runCheckersForPostStmt(Dst, Tmp, S, *this); + Bldr.addNodes(Dst); + break; + } + case Stmt::ArraySubscriptExprClass: Bldr.takeNodes(Pred); VisitLvalArraySubscriptExpr(cast(S), Pred, Dst); diff --git a/test/Analysis/objc-arc.m b/test/Analysis/objc-arc.m index b02af05151..a5bf05f38a 100644 --- a/test/Analysis/objc-arc.m +++ b/test/Analysis/objc-arc.m @@ -3,6 +3,7 @@ typedef signed char BOOL; typedef struct _NSZone NSZone; @class NSInvocation, NSMethodSignature, NSCoder, NSString, NSEnumerator; +typedef unsigned long NSUInteger; @protocol NSObject - (BOOL)isEqual:(id)object; @@ -10,12 +11,30 @@ typedef struct _NSZone NSZone; @protocol NSCopying - (id)copyWithZone:(NSZone *)zone; @end -@protocol NSCoding +@protocol NSCoding; +@protocol NSMutableCopying; +@protocol NSFastEnumeration - (void)encodeWithCoder:(NSCoder *)aCoder; @end +@protocol NSMutableCopying - (id)mutableCopyWithZone:(NSZone *)zone; +@end +@protocol NSCoding - (void)encodeWithCoder:(NSCoder *)aCoder; +@end @interface NSObject {} + (id)alloc; +- (id)init; +- (NSString *)description; @end +@interface NSArray : NSObject +- (NSUInteger)count; +- (id)initWithObjects:(const id [])objects count:(NSUInteger)cnt; ++ (id)arrayWithObject:(id)anObject; ++ (id)arrayWithObjects:(const id [])objects count:(NSUInteger)cnt; ++ (id)arrayWithObjects:(id)firstObj, ... __attribute__((sentinel(0,1))); +- (id)initWithObjects:(id)firstObj, ... __attribute__((sentinel(0,1))); +- (id)initWithArray:(NSArray *)array; +@end + typedef const struct __CFAllocator * CFAllocatorRef; extern const CFAllocatorRef kCFAllocatorDefault; typedef double CFTimeInterval; @@ -153,3 +172,32 @@ id test_return() { return x; // no-warning } +void test_objc_arrays() { + { // CASE ONE -- OBJECT IN ARRAY CREATED DIRECTLY + NSObject *o = [[NSObject alloc] init]; + NSArray *a = [[NSArray alloc] initWithObjects:o, (void*)0]; + [a description]; + [o description]; + } + + { // CASE TWO -- OBJECT IN ARRAY CREATED BY DUPING AUTORELEASED ARRAY + NSObject *o = [[NSObject alloc] init]; + NSArray *a1 = [NSArray arrayWithObjects:o, (void*)0]; + NSArray *a2 = [[NSArray alloc] initWithArray:a1]; + [a2 description]; + [o description]; + } + + { // CASE THREE -- OBJECT IN RETAINED @[] + NSObject *o = [[NSObject alloc] init]; + NSArray *a3 = @[o]; + [a3 description]; + [o description]; + } + { + // CASE 4, verify analyzer still working. + CFCreateString(); // expected-warning {{leak}} + } +} + + diff --git a/test/Analysis/objc-bool.m b/test/Analysis/objc-bool.m new file mode 100644 index 0000000000..631cd2d1fb --- /dev/null +++ b/test/Analysis/objc-bool.m @@ -0,0 +1,22 @@ +// RUN: %clang --analyze %s -o %t -verify + +// Test handling of ObjC bool literals. + +typedef signed char BOOL; + +void rdar_10597458() { + if (__objc_yes) + return; + int *p = 0; + *p = 0xDEADBEEF; // no-warning +} + +void rdar_10597458_b(BOOL b) { + if (b == __objc_no) + return; + + if (b == __objc_no) { + int *p = 0; + *p = 0xDEADBEEF; // no-warning + } +} diff --git a/test/Analysis/retain-release.m b/test/Analysis/retain-release.m index 1ca4876d5f..548564fe4c 100644 --- a/test/Analysis/retain-release.m +++ b/test/Analysis/retain-release.m @@ -115,10 +115,15 @@ typedef struct _NSZone NSZone; - (id)retain; - (oneway void)release; - (id)autorelease; +- (NSString *)description; - (id)init; -@end @protocol NSCopying - (id)copyWithZone:(NSZone *)zone; -@end @protocol NSMutableCopying - (id)mutableCopyWithZone:(NSZone *)zone; -@end @protocol NSCoding - (void)encodeWithCoder:(NSCoder *)aCoder; +@end +@protocol NSCopying +- (id)copyWithZone:(NSZone *)zone; +@end +@protocol NSMutableCopying - (id)mutableCopyWithZone:(NSZone *)zone; +@end +@protocol NSCoding - (void)encodeWithCoder:(NSCoder *)aCoder; @end @interface NSObject {} + (id)allocWithZone:(NSZone *)zone; @@ -132,13 +137,22 @@ extern id NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone); typedef struct { } NSFastEnumerationState; -@protocol NSFastEnumeration - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len; -@end @class NSString, NSDictionary; +@protocol NSFastEnumeration +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len; +@end +@class NSString, NSDictionary; @interface NSValue : NSObject - (void)getValue:(void *)value; @end @interface NSNumber : NSValue - (char)charValue; - (id)initWithInt:(int)value; @end @class NSString; -@interface NSArray : NSObject - (NSUInteger)count; +@interface NSArray : NSObject +- (NSUInteger)count; +- (id)initWithObjects:(const id [])objects count:(NSUInteger)cnt; ++ (id)arrayWithObject:(id)anObject; ++ (id)arrayWithObjects:(const id [])objects count:(NSUInteger)cnt; ++ (id)arrayWithObjects:(id)firstObj, ... __attribute__((sentinel(0,1))); +- (id)initWithObjects:(id)firstObj, ... __attribute__((sentinel(0,1))); +- (id)initWithArray:(NSArray *)array; @end @interface NSArray (NSArrayCreation) + (id)array; @end @interface NSAutoreleasePool : NSObject { } @@ -158,8 +172,12 @@ typedef double NSTimeInterval; + (id)dataWithBytesNoCopy:(void *)bytes length:(NSUInteger)length; + (id)dataWithBytesNoCopy:(void *)bytes length:(NSUInteger)length freeWhenDone:(BOOL)b; @end @class NSLocale, NSDate, NSCalendar, NSTimeZone, NSError, NSArray, NSMutableDictionary; -@interface NSDictionary : NSObject - (NSUInteger)count; -@end @interface NSMutableDictionary : NSDictionary - (void)removeObjectForKey:(id)aKey; +@interface NSDictionary : NSObject +- (NSUInteger)count; ++ (id)dictionaryWithObjects:(NSArray *)objects forKeys:(NSArray *)keys; ++ (id)dictionaryWithObjects:(const id [])objects forKeys:(const id [])keys count:(NSUInteger)cnt; +@end +@interface NSMutableDictionary : NSDictionary - (void)removeObjectForKey:(id)aKey; - (void)setObject:(id)anObject forKey:(id)aKey; @end @interface NSMutableDictionary (NSMutableDictionaryCreation) + (id)dictionaryWithCapacity:(NSUInteger)numItems; @end typedef double CGFloat; @@ -1629,3 +1647,52 @@ void rdar9658496() { xpc_release(xpc); } +//===----------------------------------------------------------------------===// +// ObjC literals support. +//===----------------------------------------------------------------------===// + +void test_objc_arrays() { + { // CASE ONE -- OBJECT IN ARRAY CREATED DIRECTLY + NSObject *o = [[NSObject alloc] init]; + NSArray *a = [[NSArray alloc] initWithObjects:o, (void*)0]; // expected-warning {{leak}} + [o release]; + [a description]; + [o description]; + } + + { // CASE TWO -- OBJECT IN ARRAY CREATED BY DUPING AUTORELEASED ARRAY + NSObject *o = [[NSObject alloc] init]; + NSArray *a1 = [NSArray arrayWithObjects:o, (void*)0]; + NSArray *a2 = [[NSArray alloc] initWithArray:a1]; // expected-warning {{leak}} + [o release]; + [a2 description]; + [o description]; + } + + { // CASE THREE -- OBJECT IN RETAINED @[] + NSObject *o = [[NSObject alloc] init]; + NSArray *a3 = [@[o] retain]; // expected-warning {{leak}} + [o release]; + [a3 description]; + [o description]; + } + + { // CASE FOUR -- OBJECT IN ARRAY CREATED BY DUPING @[] + NSObject *o = [[NSObject alloc] init]; + NSArray *a = [[NSArray alloc] initWithArray:@[o]]; // expected-warning {{leak}} + [o release]; + + [a description]; + [o description]; + } + + { // CASE FIVE -- OBJECT IN RETAINED @{} + NSValue *o = [[NSValue alloc] init]; + NSDictionary *a = [@{o : o} retain]; // expected-warning {{leak}} + [o release]; + + [a description]; + [o description]; + } +} + diff --git a/tools/scan-build/ccc-analyzer b/tools/scan-build/ccc-analyzer index 1b273b8c23..32a7301362 100755 --- a/tools/scan-build/ccc-analyzer +++ b/tools/scan-build/ccc-analyzer @@ -345,7 +345,8 @@ my %CompileOptionMap = ( ); my %LinkerOptionMap = ( - '-framework' => 1 + '-framework' => 1, + '-fobjc-link-runtime' => 0 ); my %CompilerLinkerOptionMap = (