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 CheckerManager &mgr) {
|
||
|
return true;
|
||
|
}
|