-//== TrustNonnullChecker.cpp - Checker for trusting annotations -*- C++ -*--==//
+//== TrustNonnullChecker.cpp --------- API nullability modeling -*- C++ -*--==//
//
// The LLVM Compiler Infrastructure
//
//
//===----------------------------------------------------------------------===//
//
-// 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"
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<check::PostCall> {
+class TrustNonnullChecker : public Checker<check::PostCall,
+ check::PostObjCMessage,
+ check::DeadSymbols,
+ eval::Assume> {
+ // 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<Loc>())
+ 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<Loc>())
+ 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<NonNullImplicationMap>(RetS, ArgS);
+
+ // Conversely, when the argument is null, the return value
+ // is definitely null.
+ State = State->set<NullImplicationMap>(ArgS, RetS);
+ }
+ }
+
+ C.addTransition(State);
+ }
+
+ void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ State = dropDeadFromGDM<NullImplicationMap>(SymReaper, State);
+ State = dropDeadFromGDM<NonNullImplicationMap>(SymReaper, State);
+
+ C.addTransition(State);
+ }
+
private:
+
+ /// \returns State with GDM \p MapName where all dead symbols were
+ // removed.
+ template <typename MapName>
+ ProgramStateRef dropDeadFromGDM(SymbolReaper &SymReaper,
+ ProgramStateRef State) const {
+ for (const std::pair<SymbolRef, SymbolRef> &P : State->get<MapName>())
+ if (!SymReaper.isLive(P.first) || !SymReaper.isLive(P.second))
+ State = State->remove<MapName>(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 {
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<Loc>())
- 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<NonNullImplicationMap>(Antecedent)
+ : State->get<NullImplicationMap>(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<DefinedSVal>(), Negated);
+
+ // Drop implications from the map.
+ if (Negated) {
+ State = State->remove<NonNullImplicationMap>(Antecedent);
+ State = State->remove<NullImplicationMap>(*Consequent);
+ } else {
+ State = State->remove<NullImplicationMap>(Antecedent);
+ State = State->remove<NonNullImplicationMap>(*Consequent);
+ }
+ }
+
+ return State;
}
};
void ento::registerTrustNonnullChecker(CheckerManager &Mgr) {
- Mgr.registerChecker<TrustNonnullChecker>();
+ Mgr.registerChecker<TrustNonnullChecker>(Mgr.getASTContext());
}
-// 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) {}
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
+}