diff options
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp')
| -rw-r--r-- | clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp | 257 | 
1 files changed, 257 insertions, 0 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp new file mode 100644 index 0000000000000..62a4c2ab0209c --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp @@ -0,0 +1,257 @@ +//== 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 LangOptions &LO) { +  return true; +}  | 
