From e99ba6e1f9f1a682292247ad7898e1e133d51fe5 Mon Sep 17 00:00:00 2001 From: George Karpenkov Date: Fri, 10 Aug 2018 22:27:04 +0000 Subject: [PATCH] [analyzer] Record nullability implications on getting items from NSDictionary If we get an item from a dictionary, we know that the item is non-null if and only if the key is non-null. This patch is a rather hacky way to record this implication, because some logic needs to be duplicated from the solver. And yet, it's pretty simple, performant, and works. Other possible approaches: - Record the implication, in future rely on Z3 to pick it up. - Generalize the current code and move it to the constraint manager. rdar://34990742 Differential Revision: https://reviews.llvm.org/D50124 llvm-svn: 339482 --- .../Checkers/TrustNonnullChecker.cpp | 187 +++++++++++++++++++-- .../system-header-simulator-for-nullability.h | 51 ++++++ clang/test/Analysis/trustnonnullchecker_test.m | 105 +++++++++++- 3 files changed, 328 insertions(+), 15 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp index f3d6801..f42c78a 100644 --- a/clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp @@ -1,4 +1,4 @@ -//== TrustNonnullChecker.cpp - Checker for trusting annotations -*- C++ -*--==// +//== TrustNonnullChecker.cpp --------- API nullability modeling -*- C++ -*--==// // // The LLVM Compiler Infrastructure // @@ -7,12 +7,20 @@ // //===----------------------------------------------------------------------===// // -// This checker adds an assumption that methods annotated with _Nonnull +// This checker adds nullability-related assumptions: +// +// 1. Methods annotated with _Nonnull // which come from system headers actually return a non-null pointer. // +// 2. NSDictionary key is non-null after the keyword subscript operation +// on read if and only if the resulting expression is non-null. +// +// 3. NSMutableDictionary index is non-null after a write operation. +// //===----------------------------------------------------------------------===// #include "ClangSACheckers.h" +#include "SelectorExtras.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" @@ -22,10 +30,129 @@ using namespace clang; using namespace ento; +/// Records implications between symbols. +/// The semantics is: +/// (antecedent != 0) => (consequent != 0) +/// These implications are then read during the evaluation of the assumption, +/// and the appropriate antecedents are applied. +REGISTER_MAP_WITH_PROGRAMSTATE(NonNullImplicationMap, SymbolRef, SymbolRef) + +/// The semantics is: +/// (antecedent == 0) => (consequent == 0) +REGISTER_MAP_WITH_PROGRAMSTATE(NullImplicationMap, SymbolRef, SymbolRef) + namespace { -class TrustNonnullChecker : public Checker { +class TrustNonnullChecker : public Checker { + // Do not try to iterate over symbols with higher complexity. + static unsigned constexpr ComplexityThreshold = 10; + Selector ObjectForKeyedSubscriptSel; + Selector ObjectForKeySel; + Selector SetObjectForKeyedSubscriptSel; + Selector SetObjectForKeySel; + +public: + TrustNonnullChecker(ASTContext &Ctx) + : ObjectForKeyedSubscriptSel( + getKeywordSelector(Ctx, "objectForKeyedSubscript")), + ObjectForKeySel(getKeywordSelector(Ctx, "objectForKey")), + SetObjectForKeyedSubscriptSel( + getKeywordSelector(Ctx, "setObject", "forKeyedSubscript")), + SetObjectForKeySel(getKeywordSelector(Ctx, "setObject", "forKey")) {} + + ProgramStateRef evalAssume(ProgramStateRef State, + SVal Cond, + bool Assumption) const { + const SymbolRef CondS = Cond.getAsSymbol(); + if (!CondS || CondS->computeComplexity() > ComplexityThreshold) + return State; + + for (auto B=CondS->symbol_begin(), E=CondS->symbol_end(); B != E; ++B) { + const SymbolRef Antecedent = *B; + State = addImplication(Antecedent, State, true); + State = addImplication(Antecedent, State, false); + } + + return State; + } + + void checkPostCall(const CallEvent &Call, CheckerContext &C) const { + // Only trust annotations for system headers for non-protocols. + if (!Call.isInSystemHeader()) + return; + + ProgramStateRef State = C.getState(); + + if (isNonNullPtr(Call, C)) + if (auto L = Call.getReturnValue().getAs()) + State = State->assume(*L, /*Assumption=*/true); + + C.addTransition(State); + } + + void checkPostObjCMessage(const ObjCMethodCall &Msg, + CheckerContext &C) const { + const ObjCInterfaceDecl *ID = Msg.getReceiverInterface(); + if (!ID) + return; + + ProgramStateRef State = C.getState(); + + // Index to setter for NSMutableDictionary is assumed to be non-null, + // as an exception is thrown otherwise. + if (interfaceHasSuperclass(ID, "NSMutableDictionary") && + (Msg.getSelector() == SetObjectForKeyedSubscriptSel || + Msg.getSelector() == SetObjectForKeySel)) { + if (auto L = Msg.getArgSVal(1).getAs()) + State = State->assume(*L, /*Assumption=*/true); + } + + // Record an implication: index is non-null if the output is non-null. + if (interfaceHasSuperclass(ID, "NSDictionary") && + (Msg.getSelector() == ObjectForKeyedSubscriptSel || + Msg.getSelector() == ObjectForKeySel)) { + SymbolRef ArgS = Msg.getArgSVal(0).getAsSymbol(); + SymbolRef RetS = Msg.getReturnValue().getAsSymbol(); + + if (ArgS && RetS) { + // Emulate an implication: the argument is non-null if + // the return value is non-null. + State = State->set(RetS, ArgS); + + // Conversely, when the argument is null, the return value + // is definitely null. + State = State->set(ArgS, RetS); + } + } + + C.addTransition(State); + } + + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + State = dropDeadFromGDM(SymReaper, State); + State = dropDeadFromGDM(SymReaper, State); + + C.addTransition(State); + } + private: + + /// \returns State with GDM \p MapName where all dead symbols were + // removed. + template + ProgramStateRef dropDeadFromGDM(SymbolReaper &SymReaper, + ProgramStateRef State) const { + for (const std::pair &P : State->get()) + if (!SymReaper.isLive(P.first) || !SymReaper.isLive(P.second)) + State = State->remove(P.first); + return State; + } + /// \returns Whether we trust the result of the method call to be /// a non-null pointer. bool isNonNullPtr(const CallEvent &Call, CheckerContext &C) const { @@ -66,19 +193,51 @@ private: return false; } -public: - void checkPostCall(const CallEvent &Call, CheckerContext &C) const { - // Only trust annotations for system headers for non-protocols. - if (!Call.isInSystemHeader()) - return; + /// \return Whether \p ID has a superclass by the name \p ClassName. + bool interfaceHasSuperclass(const ObjCInterfaceDecl *ID, + StringRef ClassName) const { + if (ID->getIdentifier()->getName() == ClassName) + return true; - ProgramStateRef State = C.getState(); + if (const ObjCInterfaceDecl *Super = ID->getSuperClass()) + return interfaceHasSuperclass(Super, ClassName); - if (isNonNullPtr(Call, C)) - if (auto L = Call.getReturnValue().getAs()) - State = State->assume(*L, /*Assumption=*/true); + return false; + } - C.addTransition(State); + + /// \return a state with an optional implication added (if exists) + /// from a map of recorded implications. + /// If \p Negated is true, checks NullImplicationMap, and assumes + /// the negation of \p Antecedent. + /// Checks NonNullImplicationMap and assumes \p Antecedent otherwise. + ProgramStateRef addImplication(SymbolRef Antecedent, + ProgramStateRef State, + bool Negated) const { + SValBuilder &SVB = State->getStateManager().getSValBuilder(); + const SymbolRef *Consequent = + Negated ? State->get(Antecedent) + : State->get(Antecedent); + if (!Consequent) + return State; + + SVal AntecedentV = SVB.makeSymbolVal(Antecedent); + if ((Negated && State->isNonNull(AntecedentV).isConstrainedTrue()) + || (!Negated && State->isNull(AntecedentV).isConstrainedTrue())) { + SVal ConsequentS = SVB.makeSymbolVal(*Consequent); + State = State->assume(ConsequentS.castAs(), Negated); + + // Drop implications from the map. + if (Negated) { + State = State->remove(Antecedent); + State = State->remove(*Consequent); + } else { + State = State->remove(Antecedent); + State = State->remove(*Consequent); + } + } + + return State; } }; @@ -86,5 +245,5 @@ public: void ento::registerTrustNonnullChecker(CheckerManager &Mgr) { - Mgr.registerChecker(); + Mgr.registerChecker(Mgr.getASTContext()); } diff --git a/clang/test/Analysis/Inputs/system-header-simulator-for-nullability.h b/clang/test/Analysis/Inputs/system-header-simulator-for-nullability.h index 751057d..1f6a2b1 100644 --- a/clang/test/Analysis/Inputs/system-header-simulator-for-nullability.h +++ b/clang/test/Analysis/Inputs/system-header-simulator-for-nullability.h @@ -9,6 +9,8 @@ NS_ASSUME_NONNULL_BEGIN typedef struct _NSZone NSZone; +typedef unsigned long NSUInteger; +@class NSCoder, NSEnumerator; @protocol NSObject + (instancetype)alloc; @@ -24,6 +26,22 @@ typedef struct _NSZone NSZone; - (id)mutableCopyWithZone:(nullable NSZone *)zone; @end +@protocol NSCoding +- (void)encodeWithCoder:(NSCoder *)aCoder; +@end + +@protocol NSSecureCoding +@required ++ (BOOL)supportsSecureCoding; +@end + +typedef struct { + unsigned long state; + id *itemsPtr; + unsigned long *mutationsPtr; + unsigned long extra[5]; +} NSFastEnumerationState; + __attribute__((objc_root_class)) @interface NSObject @@ -52,3 +70,36 @@ NSString* _Nonnull getString(); @end NS_ASSUME_NONNULL_END + +@interface NSDictionary : NSObject + +- (NSUInteger)count; +- (id)objectForKey:(id)aKey; +- (NSEnumerator *)keyEnumerator; +- (id)objectForKeyedSubscript:(id)aKey; + +@end + +@interface NSDictionary (NSDictionaryCreation) + ++ (id)dictionary; ++ (id)dictionaryWithObject:(id)object forKey:(id )key; ++ (instancetype)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 (NSExtendedMutableDictionary) + +- (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary; +- (void)removeAllObjects; +- (void)setDictionary:(NSDictionary *)otherDictionary; +- (void)setObject:(id)obj forKeyedSubscript:(id )key __attribute__((availability(macosx,introduced=10.8))); + +@end diff --git a/clang/test/Analysis/trustnonnullchecker_test.m b/clang/test/Analysis/trustnonnullchecker_test.m index 67b6bd2..e7ff5e1 100644 --- a/clang/test/Analysis/trustnonnullchecker_test.m +++ b/clang/test/Analysis/trustnonnullchecker_test.m @@ -1,7 +1,9 @@ -// RUN: %clang_analyze_cc1 -fblocks -analyze -analyzer-checker=core,nullability,apiModeling -verify %s +// RUN: %clang_analyze_cc1 -fblocks -analyze -analyzer-checker=core,nullability,apiModeling,debug.ExprInspection -verify %s #include "Inputs/system-header-simulator-for-nullability.h" +void clang_analyzer_warnIfReached(); + NSString* _Nonnull trust_nonnull_framework_annotation() { NSString* out = [NSString generateString]; if (out) {} @@ -67,3 +69,104 @@ NSString * _Nonnull distrustProtocol(id o) { return out; // expected-warning{{}} } +// If the return value is non-nil, the index is non-nil. +NSString *_Nonnull retImpliesIndex(NSString *s, + NSDictionary *dic) { + id obj = dic[s]; + if (s) {} + if (obj) + return s; // no-warning + return @"foo"; +} + +NSString *_Nonnull retImpliesIndexOtherMethod(NSString *s, + NSDictionary *dic) { + id obj = [dic objectForKey:s]; + if (s) {} + if (obj) + return s; // no-warning + return @"foo"; +} + +NSString *_Nonnull retImpliesIndexOnRHS(NSString *s, + NSDictionary *dic) { + id obj = dic[s]; + if (s) {} + if (nil != obj) + return s; // no-warning + return @"foo"; +} + +NSString *_Nonnull retImpliesIndexReverseCheck(NSString *s, + NSDictionary *dic) { + id obj = dic[s]; + if (s) {} + if (!obj) + return @"foo"; + return s; // no-warning +} + +NSString *_Nonnull retImpliesIndexReverseCheckOnRHS(NSString *s, + NSDictionary *dic) { + id obj = dic[s]; + if (s) {} + if (nil == obj) + return @"foo"; + return s; // no-warning +} + +NSString *_Nonnull retImpliesIndexWrongBranch(NSString *s, + NSDictionary *dic) { + id obj = dic[s]; + if (s) {} + if (!obj) + return s; // expected-warning{{}} + return @"foo"; +} + +NSString *_Nonnull retImpliesIndexWrongBranchOnRHS(NSString *s, + NSDictionary *dic) { + id obj = dic[s]; + if (s) {} + if (nil == obj) + return s; // expected-warning{{}} + return @"foo"; +} + +// The return value could still be nil for a non-nil index. +NSDictionary *_Nonnull indexDoesNotImplyRet(NSString *s, + NSDictionary *dic) { + id obj = dic[s]; + if (obj) {} + if (s) + return obj; // expected-warning{{}} + return [[NSDictionary alloc] init]; +} + +// The return value could still be nil for a non-nil index. +NSDictionary *_Nonnull notIndexImpliesNotRet(NSString *s, + NSDictionary *dic) { + id obj = dic[s]; + if (!s) { + if (obj != nil) { + clang_analyzer_warnIfReached(); // no-warning + } + } + return [[NSDictionary alloc] init]; +} + +NSString *_Nonnull checkAssumeOnMutableDictionary(NSMutableDictionary *d, + NSString *k, + NSString *val) { + d[k] = val; + if (k) {} + return k; // no-warning +} + +NSString *_Nonnull checkAssumeOnMutableDictionaryOtherMethod(NSMutableDictionary *d, + NSString *k, + NSString *val) { + [d setObject:val forKey:k]; + if (k) {} + return k; // no-warning +} -- 2.7.4