forked from OSchip/llvm-project
[analyzer] Warn about nil elements/keys/values in array and dictionary literals.
llvm-svn: 181738
This commit is contained in:
parent
cdb5b61a2d
commit
bb2a2c865f
|
@ -90,20 +90,53 @@ static FoundationClass findKnownClass(const ObjCInterfaceDecl *ID) {
|
|||
//===----------------------------------------------------------------------===//
|
||||
|
||||
namespace {
|
||||
class NilArgChecker : public Checker<check::PreObjCMessage> {
|
||||
class NilArgChecker : public Checker<check::PreObjCMessage,
|
||||
check::PostStmt<ObjCDictionaryLiteral>,
|
||||
check::PostStmt<ObjCArrayLiteral> > {
|
||||
mutable OwningPtr<APIMisuse> BT;
|
||||
|
||||
void WarnIfNilArg(CheckerContext &C,
|
||||
const ObjCMethodCall &msg, unsigned Arg,
|
||||
FoundationClass Class,
|
||||
bool CanBeSubscript = false) const;
|
||||
void warnIfNilExpr(const Expr *E,
|
||||
const char *Msg,
|
||||
CheckerContext &C) const;
|
||||
|
||||
void warnIfNilArg(CheckerContext &C,
|
||||
const ObjCMethodCall &msg, unsigned Arg,
|
||||
FoundationClass Class,
|
||||
bool CanBeSubscript = false) const;
|
||||
|
||||
void generateBugReport(ExplodedNode *N,
|
||||
llvm::raw_svector_ostream &os,
|
||||
SourceRange Range,
|
||||
const Expr *Expr,
|
||||
CheckerContext &C) const;
|
||||
|
||||
public:
|
||||
void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
|
||||
void checkPostStmt(const ObjCDictionaryLiteral *DL,
|
||||
CheckerContext &C) const;
|
||||
void checkPostStmt(const ObjCArrayLiteral *AL,
|
||||
CheckerContext &C) const;
|
||||
};
|
||||
}
|
||||
|
||||
void NilArgChecker::WarnIfNilArg(CheckerContext &C,
|
||||
void NilArgChecker::warnIfNilExpr(const Expr *E,
|
||||
const char *Msg,
|
||||
CheckerContext &C) const {
|
||||
ProgramStateRef State = C.getState();
|
||||
SVal SV = State->getSVal(E, C.getLocationContext());
|
||||
if (State->isNull(SV).isConstrainedTrue()) {
|
||||
|
||||
if (ExplodedNode *N = C.generateSink()) {
|
||||
SmallString<128> sbuf;
|
||||
llvm::raw_svector_ostream os(sbuf);
|
||||
os << Msg;
|
||||
generateBugReport(N, os, E->getSourceRange(), E, C);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void NilArgChecker::warnIfNilArg(CheckerContext &C,
|
||||
const ObjCMethodCall &msg,
|
||||
unsigned int Arg,
|
||||
FoundationClass Class,
|
||||
|
@ -113,9 +146,6 @@ void NilArgChecker::WarnIfNilArg(CheckerContext &C,
|
|||
if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue())
|
||||
return;
|
||||
|
||||
if (!BT)
|
||||
BT.reset(new APIMisuse("nil argument"));
|
||||
|
||||
if (ExplodedNode *N = C.generateSink()) {
|
||||
SmallString<128> sbuf;
|
||||
llvm::raw_svector_ostream os(sbuf);
|
||||
|
@ -149,14 +179,26 @@ void NilArgChecker::WarnIfNilArg(CheckerContext &C,
|
|||
<< msg.getSelector().getAsString() << "' cannot be nil";
|
||||
}
|
||||
}
|
||||
|
||||
BugReport *R = new BugReport(*BT, os.str(), N);
|
||||
R->addRange(msg.getArgSourceRange(Arg));
|
||||
bugreporter::trackNullOrUndefValue(N, msg.getArgExpr(Arg), *R);
|
||||
C.emitReport(R);
|
||||
|
||||
generateBugReport(N, os, msg.getArgSourceRange(Arg),
|
||||
msg.getArgExpr(Arg), C);
|
||||
}
|
||||
}
|
||||
|
||||
void NilArgChecker::generateBugReport(ExplodedNode *N,
|
||||
llvm::raw_svector_ostream &os,
|
||||
SourceRange Range,
|
||||
const Expr *Expr,
|
||||
CheckerContext &C) const {
|
||||
if (!BT)
|
||||
BT.reset(new APIMisuse("nil argument"));
|
||||
|
||||
BugReport *R = new BugReport(*BT, os.str(), N);
|
||||
R->addRange(Range);
|
||||
bugreporter::trackNullOrUndefValue(N, Expr, *R);
|
||||
C.emitReport(R);
|
||||
}
|
||||
|
||||
void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
|
||||
CheckerContext &C) const {
|
||||
const ObjCInterfaceDecl *ID = msg.getReceiverInterface();
|
||||
|
@ -225,28 +267,43 @@ void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
|
|||
if (S.getNameForSlot(0).equals("dictionaryWithObject") &&
|
||||
S.getNameForSlot(1).equals("forKey")) {
|
||||
Arg = 0;
|
||||
WarnIfNilArg(C, msg, /* Arg */1, Class);
|
||||
warnIfNilArg(C, msg, /* Arg */1, Class);
|
||||
} else if (S.getNameForSlot(0).equals("setObject") &&
|
||||
S.getNameForSlot(1).equals("forKey")) {
|
||||
Arg = 0;
|
||||
WarnIfNilArg(C, msg, /* Arg */1, Class);
|
||||
warnIfNilArg(C, msg, /* Arg */1, Class);
|
||||
} else if (S.getNameForSlot(0).equals("setObject") &&
|
||||
S.getNameForSlot(1).equals("forKeyedSubscript")) {
|
||||
CanBeSubscript = true;
|
||||
Arg = 0;
|
||||
WarnIfNilArg(C, msg, /* Arg */1, Class, CanBeSubscript);
|
||||
warnIfNilArg(C, msg, /* Arg */1, Class, CanBeSubscript);
|
||||
} else if (S.getNameForSlot(0).equals("removeObjectForKey")) {
|
||||
Arg = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If argument is '0', report a warning.
|
||||
if ((Arg != InvalidArgIndex))
|
||||
WarnIfNilArg(C, msg, Arg, Class, CanBeSubscript);
|
||||
warnIfNilArg(C, msg, Arg, Class, CanBeSubscript);
|
||||
|
||||
}
|
||||
|
||||
void NilArgChecker::checkPostStmt(const ObjCArrayLiteral *AL,
|
||||
CheckerContext &C) const {
|
||||
for (unsigned i = 0; i < AL->getNumElements(); ++i) {
|
||||
warnIfNilExpr(AL->getElement(i), "Array element cannot be nil", C);
|
||||
}
|
||||
}
|
||||
|
||||
void NilArgChecker::checkPostStmt(const ObjCDictionaryLiteral *DL,
|
||||
CheckerContext &C) const {
|
||||
for (unsigned i = 0; i < DL->getNumElements(); ++i) {
|
||||
ObjCDictionaryElement Element = DL->getKeyValueElement(i);
|
||||
warnIfNilExpr(Element.Key, "Dictionary key cannot be nil", C);
|
||||
warnIfNilExpr(Element.Value, "Dictionary value cannot be nil", C);
|
||||
}
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Error reporting.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -36,6 +36,10 @@ typedef struct _NSZone NSZone;
|
|||
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx __attribute__((availability(macosx,introduced=10.8)));
|
||||
@end
|
||||
|
||||
@interface NSArray (NSArrayCreation)
|
||||
+ (instancetype)arrayWithObjects:(const id [])objects count:(NSUInteger)cnt;
|
||||
@end
|
||||
|
||||
@interface NSMutableArray : NSArray
|
||||
|
||||
- (void)addObject:(id)anObject;
|
||||
|
@ -58,6 +62,8 @@ typedef struct _NSZone NSZone;
|
|||
|
||||
+ (id)dictionary;
|
||||
+ (id)dictionaryWithObject:(id)object forKey:(id <NSCopying>)key;
|
||||
+ (instancetype)dictionaryWithObjects:(const id [])objects forKeys:(const id <NSCopying> [])keys count:(NSUInteger)cnt;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSMutableDictionary : NSDictionary
|
||||
|
@ -147,6 +153,33 @@ NSDictionary *testNilArgNSDictionary2(NSObject *obj) {
|
|||
return [NSDictionary dictionaryWithObject:obj forKey:0]; // expected-warning {{Key argument to 'dictionaryWithObject:forKey:' cannot be nil}}
|
||||
}
|
||||
|
||||
id testCreateDictionaryLiteralKey(id value, id nilKey) {
|
||||
if (nilKey)
|
||||
;
|
||||
return @{@"abc":value, nilKey:@"abc"}; // expected-warning {{Dictionary key cannot be nil}}
|
||||
}
|
||||
|
||||
id testCreateDictionaryLiteralValue(id nilValue) {
|
||||
if (nilValue)
|
||||
;
|
||||
return @{@"abc":nilValue}; // expected-warning {{Dictionary value cannot be nil}}
|
||||
}
|
||||
|
||||
id testCreateDictionaryLiteral(id nilValue, id nilKey) {
|
||||
if (nilValue)
|
||||
;
|
||||
if (nilKey)
|
||||
;
|
||||
return @{@"abc":nilValue, nilKey:@"abc"}; // expected-warning {{Dictionary key cannot be nil}}
|
||||
// expected-warning@-1 {{Dictionary value cannot be nil}}
|
||||
}
|
||||
|
||||
id testCreateArrayLiteral(id myNil) {
|
||||
if (myNil)
|
||||
;
|
||||
return @[ @"a", myNil, @"c" ]; // expected-warning {{Array element cannot be nil}}
|
||||
}
|
||||
|
||||
// Test inline defensive checks suppression.
|
||||
void idc(id x) {
|
||||
if (x)
|
||||
|
|
Loading…
Reference in New Issue