forked from OSchip/llvm-project
				
			
		
			
				
	
	
		
			258 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			258 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C++
		
	
	
	
//== 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;
 | 
						|
}
 |