123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- //== TrustNonnullChecker.cpp --------- API nullability modeling -*- 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
- //
- //===----------------------------------------------------------------------===//
- //
- // 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 "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
- #include "clang/Analysis/SelectorExtras.h"
- #include "clang/StaticAnalyzer/Core/Checker.h"
- #include "clang/StaticAnalyzer/Core/CheckerManager.h"
- #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
- #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
- #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.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,
- 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 {
- QualType ExprRetType = Call.getResultType();
- if (!ExprRetType->isAnyPointerType())
- return false;
- if (getNullabilityAnnotation(ExprRetType) == Nullability::Nonnull)
- return true;
- // The logic for ObjC instance method calls is more complicated,
- // as the return value is nil when the receiver is nil.
- if (!isa<ObjCMethodCall>(&Call))
- return false;
- const auto *MCall = cast<ObjCMethodCall>(&Call);
- const ObjCMethodDecl *MD = MCall->getDecl();
- // Distrust protocols.
- if (isa<ObjCProtocolDecl>(MD->getDeclContext()))
- return false;
- QualType DeclRetType = MD->getReturnType();
- if (getNullabilityAnnotation(DeclRetType) != Nullability::Nonnull)
- return false;
- // For class messages it is sufficient for the declaration to be
- // annotated _Nonnull.
- if (!MCall->isInstanceMessage())
- return true;
- // Alternatively, the analyzer could know that the receiver is not null.
- SVal Receiver = MCall->getReceiverSVal();
- ConditionTruthVal TV = C.getState()->isNonNull(Receiver);
- if (TV.isConstrainedTrue())
- return true;
- return false;
- }
- /// \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;
- if (const ObjCInterfaceDecl *Super = ID->getSuperClass())
- return interfaceHasSuperclass(Super, ClassName);
- return false;
- }
- /// \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 InputState,
- bool Negated) const {
- if (!InputState)
- return nullptr;
- SValBuilder &SVB = InputState->getStateManager().getSValBuilder();
- const SymbolRef *Consequent =
- Negated ? InputState->get<NonNullImplicationMap>(Antecedent)
- : InputState->get<NullImplicationMap>(Antecedent);
- if (!Consequent)
- return InputState;
- SVal AntecedentV = SVB.makeSymbolVal(Antecedent);
- ProgramStateRef State = InputState;
- if ((Negated && InputState->isNonNull(AntecedentV).isConstrainedTrue())
- || (!Negated && InputState->isNull(AntecedentV).isConstrainedTrue())) {
- SVal ConsequentS = SVB.makeSymbolVal(*Consequent);
- State = InputState->assume(ConsequentS.castAs<DefinedSVal>(), Negated);
- if (!State)
- return nullptr;
- // 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;
- }
- };
- } // end empty namespace
- void ento::registerTrustNonnullChecker(CheckerManager &Mgr) {
- Mgr.registerChecker<TrustNonnullChecker>(Mgr.getASTContext());
- }
- bool ento::shouldRegisterTrustNonnullChecker(const CheckerManager &mgr) {
- return true;
- }
|