#include "RetainCountChecker.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
using namespace clang;
using namespace ento;
using namespace retaincountchecker;
REGISTER_MAP_WITH_PROGRAMSTATE(RefBindings, SymbolRef, RefVal)
namespace clang {
namespace ento {
namespace retaincountchecker {
const RefVal *getRefBinding(ProgramStateRef State, SymbolRef Sym) {
return State->get<RefBindings>(Sym);
}
} } }
static ProgramStateRef setRefBinding(ProgramStateRef State, SymbolRef Sym,
RefVal Val) {
assert(Sym != nullptr);
return State->set<RefBindings>(Sym, Val);
}
static ProgramStateRef removeRefBinding(ProgramStateRef State, SymbolRef Sym) {
return State->remove<RefBindings>(Sym);
}
void RefVal::print(raw_ostream &Out) const {
if (!T.isNull())
Out << "Tracked " << T << " | ";
switch (getKind()) {
default: llvm_unreachable("Invalid RefVal kind");
case Owned: {
Out << "Owned";
unsigned cnt = getCount();
if (cnt) Out << " (+ " << cnt << ")";
break;
}
case NotOwned: {
Out << "NotOwned";
unsigned cnt = getCount();
if (cnt) Out << " (+ " << cnt << ")";
break;
}
case ReturnedOwned: {
Out << "ReturnedOwned";
unsigned cnt = getCount();
if (cnt) Out << " (+ " << cnt << ")";
break;
}
case ReturnedNotOwned: {
Out << "ReturnedNotOwned";
unsigned cnt = getCount();
if (cnt) Out << " (+ " << cnt << ")";
break;
}
case Released:
Out << "Released";
break;
case ErrorDeallocNotOwned:
Out << "-dealloc (not-owned)";
break;
case ErrorLeak:
Out << "Leaked";
break;
case ErrorLeakReturned:
Out << "Leaked (Bad naming)";
break;
case ErrorUseAfterRelease:
Out << "Use-After-Release [ERROR]";
break;
case ErrorReleaseNotOwned:
Out << "Release of Not-Owned [ERROR]";
break;
case RefVal::ErrorOverAutorelease:
Out << "Over-autoreleased";
break;
case RefVal::ErrorReturnedNotOwned:
Out << "Non-owned object returned instead of owned";
break;
}
switch (getIvarAccessHistory()) {
case IvarAccessHistory::None:
break;
case IvarAccessHistory::AccessedDirectly:
Out << " [direct ivar access]";
break;
case IvarAccessHistory::ReleasedAfterDirectAccess:
Out << " [released after direct ivar access]";
}
if (ACnt) {
Out << " [autorelease -" << ACnt << ']';
}
}
namespace {
class StopTrackingCallback final : public SymbolVisitor {
ProgramStateRef state;
public:
StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {}
ProgramStateRef getState() const { return state; }
bool VisitSymbol(SymbolRef sym) override {
state = removeRefBinding(state, sym);
return true;
}
};
}
void RetainCountChecker::checkPostStmt(const BlockExpr *BE,
CheckerContext &C) const {
if (!BE->getBlockDecl()->hasCaptures())
return;
ProgramStateRef state = C.getState();
auto *R = cast<BlockDataRegion>(C.getSVal(BE).getAsRegion());
BlockDataRegion::referenced_vars_iterator I = R->referenced_vars_begin(),
E = R->referenced_vars_end();
if (I == E)
return;
SmallVector<const MemRegion*, 10> Regions;
const LocationContext *LC = C.getLocationContext();
MemRegionManager &MemMgr = C.getSValBuilder().getRegionManager();
for ( ; I != E; ++I) {
const VarRegion *VR = I.getCapturedRegion();
if (VR->getSuperRegion() == R) {
VR = MemMgr.getVarRegion(VR->getDecl(), LC);
}
Regions.push_back(VR);
}
state = state->scanReachableSymbols<StopTrackingCallback>(Regions).getState();
C.addTransition(state);
}
void RetainCountChecker::checkPostStmt(const CastExpr *CE,
CheckerContext &C) const {
const ObjCBridgedCastExpr *BE = dyn_cast<ObjCBridgedCastExpr>(CE);
if (!BE)
return;
QualType QT = CE->getType();
ObjKind K;
if (QT->isObjCObjectPointerType()) {
K = ObjKind::ObjC;
} else {
K = ObjKind::CF;
}
ArgEffect AE = ArgEffect(IncRef, K);
switch (BE->getBridgeKind()) {
case OBC_Bridge:
return;
case OBC_BridgeRetained:
AE = AE.withKind(IncRef);
break;
case OBC_BridgeTransfer:
AE = AE.withKind(DecRefBridgedTransferred);
break;
}
ProgramStateRef state = C.getState();
SymbolRef Sym = C.getSVal(CE).getAsLocSymbol();
if (!Sym)
return;
const RefVal* T = getRefBinding(state, Sym);
if (!T)
return;
RefVal::Kind hasErr = (RefVal::Kind) 0;
state = updateSymbol(state, Sym, *T, AE, hasErr, C);
if (hasErr) {
return;
}
C.addTransition(state);
}
void RetainCountChecker::processObjCLiterals(CheckerContext &C,
const Expr *Ex) const {
ProgramStateRef state = C.getState();
const ExplodedNode *pred = C.getPredecessor();
for (const Stmt *Child : Ex->children()) {
SVal V = pred->getSVal(Child);
if (SymbolRef sym = V.getAsSymbol())
if (const RefVal* T = getRefBinding(state, sym)) {
RefVal::Kind hasErr = (RefVal::Kind) 0;
state = updateSymbol(state, sym, *T,
ArgEffect(MayEscape, ObjKind::ObjC), hasErr, C);
if (hasErr) {
processNonLeakError(state, Child->getSourceRange(), hasErr, sym, C);
return;
}
}
}
if (SymbolRef sym =
state->getSVal(Ex, pred->getLocationContext()).getAsSymbol()) {
QualType ResultTy = Ex->getType();
state = setRefBinding(state, sym,
RefVal::makeNotOwned(ObjKind::ObjC, ResultTy));
}
C.addTransition(state);
}
void RetainCountChecker::checkPostStmt(const ObjCArrayLiteral *AL,
CheckerContext &C) const {
processObjCLiterals(C, AL);
}
void RetainCountChecker::checkPostStmt(const ObjCDictionaryLiteral *DL,
CheckerContext &C) const {
processObjCLiterals(C, DL);
}
void RetainCountChecker::checkPostStmt(const ObjCBoxedExpr *Ex,
CheckerContext &C) const {
const ExplodedNode *Pred = C.getPredecessor();
ProgramStateRef State = Pred->getState();
if (SymbolRef Sym = Pred->getSVal(Ex).getAsSymbol()) {
QualType ResultTy = Ex->getType();
State = setRefBinding(State, Sym,
RefVal::makeNotOwned(ObjKind::ObjC, ResultTy));
}
C.addTransition(State);
}
void RetainCountChecker::checkPostStmt(const ObjCIvarRefExpr *IRE,
CheckerContext &C) const {
Optional<Loc> IVarLoc = C.getSVal(IRE).getAs<Loc>();
if (!IVarLoc)
return;
ProgramStateRef State = C.getState();
SymbolRef Sym = State->getSVal(*IVarLoc).getAsSymbol();
if (!Sym || !isa_and_nonnull<ObjCIvarRegion>(Sym->getOriginRegion()))
return;
QualType Ty = Sym->getType();
ObjKind Kind;
if (Ty->isObjCRetainableType())
Kind = ObjKind::ObjC;
else if (coreFoundation::isCFObjectRef(Ty))
Kind = ObjKind::CF;
else
return;
ConstraintManager &CMgr = State->getConstraintManager();
if (CMgr.isNull(State, Sym).isConstrainedTrue())
return;
if (const RefVal *RV = getRefBinding(State, Sym)) {
if (RV->getIvarAccessHistory() != RefVal::IvarAccessHistory::None ||
isSynthesizedAccessor(C.getStackFrame())) {
return;
}
C.addTransition(setRefBinding(State, Sym, RV->withIvarAccess()));
return;
}
RefVal PlusZero = RefVal::makeNotOwned(Kind, Ty);
if (isSynthesizedAccessor(C.getStackFrame())) {
C.addTransition(setRefBinding(State, Sym, PlusZero));
return;
}
State = setRefBinding(State, Sym, PlusZero.withIvarAccess());
C.addTransition(State);
}
static bool isReceiverUnconsumedSelf(const CallEvent &Call) {
if (const auto *MC = dyn_cast<ObjCMethodCall>(&Call)) {
return MC->getMethodFamily() == OMF_init && MC->isReceiverSelfOrSuper() &&
!Call.getLocationContext()
->getAnalysisDeclContext()
->getParentMap()
.isConsumedExpr(Call.getOriginExpr());
}
return false;
}
const static RetainSummary *getSummary(RetainSummaryManager &Summaries,
const CallEvent &Call,
QualType ReceiverType) {
const Expr *CE = Call.getOriginExpr();
AnyCall C =
CE ? *AnyCall::forExpr(CE)
: AnyCall(cast<CXXDestructorDecl>(Call.getDecl()));
return Summaries.getSummary(C, Call.hasNonZeroCallbackArg(),
isReceiverUnconsumedSelf(Call), ReceiverType);
}
void RetainCountChecker::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
RetainSummaryManager &Summaries = getSummaryManager(C);
QualType ReceiverType;
if (const auto *MC = dyn_cast<ObjCMethodCall>(&Call)) {
if (MC->isInstanceMessage()) {
SVal ReceiverV = MC->getReceiverSVal();
if (SymbolRef Sym = ReceiverV.getAsLocSymbol())
if (const RefVal *T = getRefBinding(C.getState(), Sym))
ReceiverType = T->getType();
}
}
const RetainSummary *Summ = getSummary(Summaries, Call, ReceiverType);
if (C.wasInlined) {
processSummaryOfInlined(*Summ, Call, C);
return;
}
checkSummary(*Summ, Call, C);
}
static QualType GetReturnType(const Expr *RetE, ASTContext &Ctx) {
QualType RetTy = RetE->getType();
if (const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(RetE))
if (const ObjCObjectPointerType *PT = RetTy->getAs<ObjCObjectPointerType>())
if (PT->isObjCQualifiedIdType() || PT->isObjCIdType() ||
PT->isObjCClassType()) {
const ObjCInterfaceDecl *D = ME->getReceiverInterface();
return !D ? RetTy :
Ctx.getObjCObjectPointerType(Ctx.getObjCInterfaceType(D));
}
return RetTy;
}
static Optional<RefVal> refValFromRetEffect(RetEffect RE,
QualType ResultTy) {
if (RE.isOwned()) {
return RefVal::makeOwned(RE.getObjKind(), ResultTy);
} else if (RE.notOwned()) {
return RefVal::makeNotOwned(RE.getObjKind(), ResultTy);
}
return None;
}
static bool isPointerToObject(QualType QT) {
QualType PT = QT->getPointeeType();
if (!PT.isNull())
if (PT->getAsCXXRecordDecl())
return true;
return false;
}
static bool shouldEscapeOSArgumentOnCall(const CallEvent &CE, unsigned ArgIdx,
const RefVal *TrackedValue) {
if (TrackedValue->getObjKind() != ObjKind::OS)
return false;
if (ArgIdx >= CE.parameters().size())
return false;
return !isPointerToObject(CE.parameters()[ArgIdx]->getType());
}
void RetainCountChecker::processSummaryOfInlined(const RetainSummary &Summ,
const CallEvent &CallOrMsg,
CheckerContext &C) const {
ProgramStateRef state = C.getState();
for (unsigned idx = 0, e = CallOrMsg.getNumArgs(); idx != e; ++idx) {
SVal V = CallOrMsg.getArgSVal(idx);
if (SymbolRef Sym = V.getAsLocSymbol()) {
bool ShouldRemoveBinding = Summ.getArg(idx).getKind() == StopTrackingHard;
if (const RefVal *T = getRefBinding(state, Sym))
if (shouldEscapeOSArgumentOnCall(CallOrMsg, idx, T))
ShouldRemoveBinding = true;
if (ShouldRemoveBinding)
state = removeRefBinding(state, Sym);
}
}
if (const auto *MsgInvocation = dyn_cast<ObjCMethodCall>(&CallOrMsg)) {
if (SymbolRef Sym = MsgInvocation->getReceiverSVal().getAsLocSymbol()) {
if (Summ.getReceiverEffect().getKind() == StopTrackingHard) {
state = removeRefBinding(state, Sym);
}
}
}
RetEffect RE = Summ.getRetEffect();
if (SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol()) {
if (RE.getKind() == RetEffect::NoRetHard)
state = removeRefBinding(state, Sym);
}
C.addTransition(state);
}
static bool isSmartPtrField(const MemRegion *MR) {
const auto *TR = dyn_cast<TypedValueRegion>(
cast<SubRegion>(MR)->getSuperRegion());
return TR && RetainSummaryManager::isKnownSmartPointer(TR->getValueType());
}
static bool shouldEscapeRegion(const MemRegion *R) {
if (isSmartPtrField(R))
return false;
const auto *VR = dyn_cast<VarRegion>(R);
if (!R->hasStackStorage() || !VR)
return true;
const VarDecl *VD = VR->getDecl();
if (!VD->hasAttr<CleanupAttr>())
return false; return true;
}
static SmallVector<ProgramStateRef, 2>
updateOutParameters(ProgramStateRef State, const RetainSummary &Summ,
const CallEvent &CE) {
SVal L = CE.getReturnValue();
bool SplitNecessary = false;
for (auto &P : Summ.getArgEffects())
if (P.second.getKind() == RetainedOutParameterOnNonZero ||
P.second.getKind() == RetainedOutParameterOnZero)
SplitNecessary = true;
ProgramStateRef AssumeNonZeroReturn = State;
ProgramStateRef AssumeZeroReturn = State;
if (SplitNecessary) {
if (!CE.getResultType()->isScalarType()) {
return {State};
}
if (auto DL = L.getAs<DefinedOrUnknownSVal>()) {
AssumeNonZeroReturn = AssumeNonZeroReturn->assume(*DL, true);
AssumeZeroReturn = AssumeZeroReturn->assume(*DL, false);
}
}
for (unsigned idx = 0, e = CE.getNumArgs(); idx != e; ++idx) {
SVal ArgVal = CE.getArgSVal(idx);
ArgEffect AE = Summ.getArg(idx);
auto *ArgRegion = dyn_cast_or_null<TypedValueRegion>(ArgVal.getAsRegion());
if (!ArgRegion)
continue;
QualType PointeeTy = ArgRegion->getValueType();
SVal PointeeVal = State->getSVal(ArgRegion);
SymbolRef Pointee = PointeeVal.getAsLocSymbol();
if (!Pointee)
continue;
if (shouldEscapeRegion(ArgRegion))
continue;
auto makeNotOwnedParameter = [&](ProgramStateRef St) {
return setRefBinding(St, Pointee,
RefVal::makeNotOwned(AE.getObjKind(), PointeeTy));
};
auto makeOwnedParameter = [&](ProgramStateRef St) {
return setRefBinding(St, Pointee,
RefVal::makeOwned(ObjKind::OS, PointeeTy));
};
switch (AE.getKind()) {
case UnretainedOutParameter:
AssumeNonZeroReturn = makeNotOwnedParameter(AssumeNonZeroReturn);
AssumeZeroReturn = makeNotOwnedParameter(AssumeZeroReturn);
break;
case RetainedOutParameter:
AssumeNonZeroReturn = makeOwnedParameter(AssumeNonZeroReturn);
AssumeZeroReturn = makeOwnedParameter(AssumeZeroReturn);
break;
case RetainedOutParameterOnNonZero:
AssumeNonZeroReturn = makeOwnedParameter(AssumeNonZeroReturn);
break;
case RetainedOutParameterOnZero:
AssumeZeroReturn = makeOwnedParameter(AssumeZeroReturn);
break;
default:
break;
}
}
if (SplitNecessary) {
return {AssumeNonZeroReturn, AssumeZeroReturn};
} else {
assert(AssumeZeroReturn == AssumeNonZeroReturn);
return {AssumeZeroReturn};
}
}
void RetainCountChecker::checkSummary(const RetainSummary &Summ,
const CallEvent &CallOrMsg,
CheckerContext &C) const {
ProgramStateRef state = C.getState();
RefVal::Kind hasErr = (RefVal::Kind) 0;
SourceRange ErrorRange;
SymbolRef ErrorSym = nullptr;
bool DeallocSent = false;
for (unsigned idx = 0, e = CallOrMsg.getNumArgs(); idx != e; ++idx) {
SVal V = CallOrMsg.getArgSVal(idx);
ArgEffect Effect = Summ.getArg(idx);
if (SymbolRef Sym = V.getAsLocSymbol()) {
if (const RefVal *T = getRefBinding(state, Sym)) {
if (shouldEscapeOSArgumentOnCall(CallOrMsg, idx, T))
Effect = ArgEffect(StopTrackingHard, ObjKind::OS);
state = updateSymbol(state, Sym, *T, Effect, hasErr, C);
if (hasErr) {
ErrorRange = CallOrMsg.getArgSourceRange(idx);
ErrorSym = Sym;
break;
} else if (Effect.getKind() == Dealloc) {
DeallocSent = true;
}
}
}
}
bool ReceiverIsTracked = false;
if (!hasErr) {
if (const auto *MsgInvocation = dyn_cast<ObjCMethodCall>(&CallOrMsg)) {
if (SymbolRef Sym = MsgInvocation->getReceiverSVal().getAsLocSymbol()) {
if (const RefVal *T = getRefBinding(state, Sym)) {
ReceiverIsTracked = true;
state = updateSymbol(state, Sym, *T,
Summ.getReceiverEffect(), hasErr, C);
if (hasErr) {
ErrorRange = MsgInvocation->getOriginExpr()->getReceiverRange();
ErrorSym = Sym;
} else if (Summ.getReceiverEffect().getKind() == Dealloc) {
DeallocSent = true;
}
}
}
} else if (const auto *MCall = dyn_cast<CXXMemberCall>(&CallOrMsg)) {
if (SymbolRef Sym = MCall->getCXXThisVal().getAsLocSymbol()) {
if (const RefVal *T = getRefBinding(state, Sym)) {
state = updateSymbol(state, Sym, *T, Summ.getThisEffect(),
hasErr, C);
if (hasErr) {
ErrorRange = MCall->getOriginExpr()->getSourceRange();
ErrorSym = Sym;
}
}
}
}
}
if (hasErr) {
processNonLeakError(state, ErrorRange, hasErr, ErrorSym, C);
return;
}
RetEffect RE = Summ.getRetEffect();
if (RE.getKind() == RetEffect::OwnedWhenTrackedReceiver) {
if (ReceiverIsTracked)
RE = getSummaryManager(C).getObjAllocRetEffect();
else
RE = RetEffect::MakeNoRet();
}
if (SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol()) {
QualType ResultTy = CallOrMsg.getResultType();
if (RE.notOwned()) {
const Expr *Ex = CallOrMsg.getOriginExpr();
assert(Ex);
ResultTy = GetReturnType(Ex, C.getASTContext());
}
if (Optional<RefVal> updatedRefVal = refValFromRetEffect(RE, ResultTy))
state = setRefBinding(state, Sym, *updatedRefVal);
}
SmallVector<ProgramStateRef, 2> Out =
updateOutParameters(state, Summ, CallOrMsg);
for (ProgramStateRef St : Out) {
if (DeallocSent) {
C.addTransition(St, C.getPredecessor(), &getDeallocSentTag());
} else {
C.addTransition(St);
}
}
}
ProgramStateRef RetainCountChecker::updateSymbol(ProgramStateRef state,
SymbolRef sym, RefVal V,
ArgEffect AE,
RefVal::Kind &hasErr,
CheckerContext &C) const {
bool IgnoreRetainMsg = (bool)C.getASTContext().getLangOpts().ObjCAutoRefCount;
if (AE.getObjKind() == ObjKind::ObjC && IgnoreRetainMsg) {
switch (AE.getKind()) {
default:
break;
case IncRef:
AE = AE.withKind(DoNothing);
break;
case DecRef:
AE = AE.withKind(DoNothing);
break;
case DecRefAndStopTrackingHard:
AE = AE.withKind(StopTracking);
break;
}
}
if (V.getKind() == RefVal::Released) {
V = V ^ RefVal::ErrorUseAfterRelease;
hasErr = V.getKind();
return setRefBinding(state, sym, V);
}
switch (AE.getKind()) {
case UnretainedOutParameter:
case RetainedOutParameter:
case RetainedOutParameterOnZero:
case RetainedOutParameterOnNonZero:
llvm_unreachable("Applies to pointer-to-pointer parameters, which should "
"not have ref state.");
case Dealloc: switch (V.getKind()) {
default:
llvm_unreachable("Invalid RefVal state for an explicit dealloc.");
case RefVal::Owned:
V = V ^ RefVal::Released;
V.clearCounts();
return setRefBinding(state, sym, V);
case RefVal::NotOwned:
V = V ^ RefVal::ErrorDeallocNotOwned;
hasErr = V.getKind();
break;
}
break;
case MayEscape:
if (V.getKind() == RefVal::Owned) {
V = V ^ RefVal::NotOwned;
break;
}
LLVM_FALLTHROUGH;
case DoNothing:
return state;
case Autorelease:
V = V.autorelease();
break;
case StopTracking:
case StopTrackingHard:
return removeRefBinding(state, sym);
case IncRef:
switch (V.getKind()) {
default:
llvm_unreachable("Invalid RefVal state for a retain.");
case RefVal::Owned:
case RefVal::NotOwned:
V = V + 1;
break;
}
break;
case DecRef:
case DecRefBridgedTransferred:
case DecRefAndStopTrackingHard:
switch (V.getKind()) {
default:
llvm_unreachable("Invalid RefVal state for a release.");
case RefVal::Owned:
assert(V.getCount() > 0);
if (V.getCount() == 1) {
if (AE.getKind() == DecRefBridgedTransferred ||
V.getIvarAccessHistory() ==
RefVal::IvarAccessHistory::AccessedDirectly)
V = V ^ RefVal::NotOwned;
else
V = V ^ RefVal::Released;
} else if (AE.getKind() == DecRefAndStopTrackingHard) {
return removeRefBinding(state, sym);
}
V = V - 1;
break;
case RefVal::NotOwned:
if (V.getCount() > 0) {
if (AE.getKind() == DecRefAndStopTrackingHard)
return removeRefBinding(state, sym);
V = V - 1;
} else if (V.getIvarAccessHistory() ==
RefVal::IvarAccessHistory::AccessedDirectly) {
if (AE.getKind() == DecRefAndStopTrackingHard)
return removeRefBinding(state, sym);
V = V.releaseViaIvar() ^ RefVal::Released;
} else {
V = V ^ RefVal::ErrorReleaseNotOwned;
hasErr = V.getKind();
}
break;
}
break;
}
return setRefBinding(state, sym, V);
}
const RefCountBug &
RetainCountChecker::errorKindToBugKind(RefVal::Kind ErrorKind,
SymbolRef Sym) const {
switch (ErrorKind) {
case RefVal::ErrorUseAfterRelease:
return *UseAfterRelease;
case RefVal::ErrorReleaseNotOwned:
return *ReleaseNotOwned;
case RefVal::ErrorDeallocNotOwned:
if (Sym->getType()->getPointeeCXXRecordDecl())
return *FreeNotOwned;
return *DeallocNotOwned;
default:
llvm_unreachable("Unhandled error.");
}
}
void RetainCountChecker::processNonLeakError(ProgramStateRef St,
SourceRange ErrorRange,
RefVal::Kind ErrorKind,
SymbolRef Sym,
CheckerContext &C) const {
if (const RefVal *RV = getRefBinding(St, Sym))
if (RV->getIvarAccessHistory() != RefVal::IvarAccessHistory::None)
return;
ExplodedNode *N = C.generateErrorNode(St);
if (!N)
return;
auto report = std::make_unique<RefCountReport>(
errorKindToBugKind(ErrorKind, Sym),
C.getASTContext().getLangOpts(), N, Sym);
report->addRange(ErrorRange);
C.emitReport(std::move(report));
}
bool RetainCountChecker::evalCall(const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef state = C.getState();
const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
if (!FD)
return false;
const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
if (!CE)
return false;
RetainSummaryManager &SmrMgr = getSummaryManager(C);
QualType ResultTy = Call.getResultType();
bool hasTrustedImplementationAnnotation = false;
const LocationContext *LCtx = C.getLocationContext();
using BehaviorSummary = RetainSummaryManager::BehaviorSummary;
Optional<BehaviorSummary> BSmr =
SmrMgr.canEval(CE, FD, hasTrustedImplementationAnnotation);
if (!BSmr)
return false;
if (BSmr == BehaviorSummary::Identity ||
BSmr == BehaviorSummary::IdentityOrZero ||
BSmr == BehaviorSummary::IdentityThis) {
const Expr *BindReturnTo =
(BSmr == BehaviorSummary::IdentityThis)
? cast<CXXMemberCallExpr>(CE)->getImplicitObjectArgument()
: CE->getArg(0);
SVal RetVal = state->getSVal(BindReturnTo, LCtx);
if (RetVal.isUnknown() ||
(hasTrustedImplementationAnnotation && !ResultTy.isNull())) {
SValBuilder &SVB = C.getSValBuilder();
RetVal =
SVB.conjureSymbolVal(nullptr, CE, LCtx, ResultTy, C.blockCount());
}
state = state->BindExpr(CE, LCtx, RetVal, false);
if (BSmr == BehaviorSummary::IdentityOrZero) {
ProgramStateRef NullOutputState = C.getState();
NullOutputState = NullOutputState->BindExpr(
CE, LCtx, C.getSValBuilder().makeNullWithType(ResultTy),
false);
C.addTransition(NullOutputState, &getCastFailTag());
if (auto L = RetVal.getAs<DefinedOrUnknownSVal>())
state = state->assume(*L, true);
}
}
C.addTransition(state);
return true;
}
ExplodedNode * RetainCountChecker::processReturn(const ReturnStmt *S,
CheckerContext &C) const {
ExplodedNode *Pred = C.getPredecessor();
if (!C.inTopFrame())
return Pred;
if (!S)
return Pred;
const Expr *RetE = S->getRetValue();
if (!RetE)
return Pred;
ProgramStateRef state = C.getState();
SymbolRef Sym = state->getSValAsScalarOrLoc(RetE, C.getLocationContext())
.getAsLocSymbol(true);
if (!Sym)
return Pred;
const RefVal *T = getRefBinding(state, Sym);
if (!T)
return Pred;
RefVal X = *T;
switch (X.getKind()) {
case RefVal::Owned: {
unsigned cnt = X.getCount();
assert(cnt > 0);
X.setCount(cnt - 1);
X = X ^ RefVal::ReturnedOwned;
break;
}
case RefVal::NotOwned: {
unsigned cnt = X.getCount();
if (cnt) {
X.setCount(cnt - 1);
X = X ^ RefVal::ReturnedOwned;
} else {
X = X ^ RefVal::ReturnedNotOwned;
}
break;
}
default:
return Pred;
}
state = setRefBinding(state, Sym, X);
Pred = C.addTransition(state);
if (!Pred)
return nullptr;
static CheckerProgramPointTag AutoreleaseTag(this, "Autorelease");
state = handleAutoreleaseCounts(state, Pred, &AutoreleaseTag, C, Sym, X, S);
if (!state)
return nullptr;
T = getRefBinding(state, Sym);
assert(T);
X = *T;
RetainSummaryManager &Summaries = getSummaryManager(C);
const Decl *CD = &Pred->getCodeDecl();
RetEffect RE = RetEffect::MakeNoRet();
if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(CD)) {
const RetainSummary *Summ = Summaries.getSummary(AnyCall(MD));
RE = Summ->getRetEffect();
} else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(CD)) {
if (!isa<CXXMethodDecl>(FD)) {
const RetainSummary *Summ = Summaries.getSummary(AnyCall(FD));
RE = Summ->getRetEffect();
}
}
return checkReturnWithRetEffect(S, C, Pred, RE, X, Sym, state);
}
ExplodedNode * RetainCountChecker::checkReturnWithRetEffect(const ReturnStmt *S,
CheckerContext &C,
ExplodedNode *Pred,
RetEffect RE, RefVal X,
SymbolRef Sym,
ProgramStateRef state) const {
if (X.getIvarAccessHistory() != RefVal::IvarAccessHistory::None)
return Pred;
if (X.isReturnedOwned() && X.getCount() == 0) {
if (RE.getKind() != RetEffect::NoRet) {
if (!RE.isOwned()) {
X = X ^ RefVal::ErrorLeakReturned;
state = setRefBinding(state, Sym, X);
static CheckerProgramPointTag ReturnOwnLeakTag(this, "ReturnsOwnLeak");
ExplodedNode *N = C.addTransition(state, Pred, &ReturnOwnLeakTag);
if (N) {
const LangOptions &LOpts = C.getASTContext().getLangOpts();
auto R =
std::make_unique<RefLeakReport>(*LeakAtReturn, LOpts, N, Sym, C);
C.emitReport(std::move(R));
}
return N;
}
}
} else if (X.isReturnedNotOwned()) {
if (RE.isOwned()) {
if (X.getIvarAccessHistory() ==
RefVal::IvarAccessHistory::AccessedDirectly) {
state = setRefBinding(state, Sym,
X.releaseViaIvar() ^ RefVal::ReturnedOwned);
} else {
state = setRefBinding(state, Sym, X ^ RefVal::ErrorReturnedNotOwned);
static CheckerProgramPointTag
ReturnNotOwnedTag(this, "ReturnNotOwnedForOwned");
ExplodedNode *N = C.addTransition(state, Pred, &ReturnNotOwnedTag);
if (N) {
auto R = std::make_unique<RefCountReport>(
*ReturnNotOwnedForOwned, C.getASTContext().getLangOpts(), N, Sym);
C.emitReport(std::move(R));
}
return N;
}
}
}
return Pred;
}
void RetainCountChecker::checkBind(SVal loc, SVal val, const Stmt *S,
CheckerContext &C) const {
ProgramStateRef state = C.getState();
const MemRegion *MR = loc.getAsRegion();
if (MR && shouldEscapeRegion(MR)) {
state = state->scanReachableSymbols<StopTrackingCallback>(val).getState();
C.addTransition(state);
}
}
ProgramStateRef RetainCountChecker::evalAssume(ProgramStateRef state,
SVal Cond,
bool Assumption) const {
RefBindingsTy B = state->get<RefBindings>();
if (B.isEmpty())
return state;
bool changed = false;
RefBindingsTy::Factory &RefBFactory = state->get_context<RefBindings>();
ConstraintManager &CMgr = state->getConstraintManager();
for (auto &I : B) {
ConditionTruthVal AllocFailed = CMgr.isNull(state, I.first);
if (AllocFailed.isConstrainedTrue()) {
changed = true;
B = RefBFactory.remove(B, I.first);
}
}
if (changed)
state = state->set<RefBindings>(B);
return state;
}
ProgramStateRef RetainCountChecker::checkRegionChanges(
ProgramStateRef state, const InvalidatedSymbols *invalidated,
ArrayRef<const MemRegion *> ExplicitRegions,
ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx,
const CallEvent *Call) const {
if (!invalidated)
return state;
llvm::SmallPtrSet<SymbolRef, 8> AllowedSymbols;
for (const MemRegion *I : ExplicitRegions)
if (const SymbolicRegion *SR = I->StripCasts()->getAs<SymbolicRegion>())
AllowedSymbols.insert(SR->getSymbol());
for (SymbolRef sym : *invalidated) {
if (AllowedSymbols.count(sym))
continue;
state = removeRefBinding(state, sym);
}
return state;
}
ProgramStateRef
RetainCountChecker::handleAutoreleaseCounts(ProgramStateRef state,
ExplodedNode *Pred,
const ProgramPointTag *Tag,
CheckerContext &Ctx,
SymbolRef Sym,
RefVal V,
const ReturnStmt *S) const {
unsigned ACnt = V.getAutoreleaseCount();
if (!ACnt)
return state;
unsigned Cnt = V.getCount();
if (V.getKind() == RefVal::ReturnedOwned)
++Cnt;
if (ACnt > Cnt &&
V.getIvarAccessHistory() == RefVal::IvarAccessHistory::AccessedDirectly) {
V = V.releaseViaIvar();
--ACnt;
}
if (ACnt <= Cnt) {
if (ACnt == Cnt) {
V.clearCounts();
if (V.getKind() == RefVal::ReturnedOwned) {
V = V ^ RefVal::ReturnedNotOwned;
} else {
V = V ^ RefVal::NotOwned;
}
} else {
V.setCount(V.getCount() - ACnt);
V.setAutoreleaseCount(0);
}
return setRefBinding(state, Sym, V);
}
if (V.getIvarAccessHistory() != RefVal::IvarAccessHistory::None)
return state;
V = V ^ RefVal::ErrorOverAutorelease;
state = setRefBinding(state, Sym, V);
ExplodedNode *N = Ctx.generateSink(state, Pred, Tag);
if (N) {
SmallString<128> sbuf;
llvm::raw_svector_ostream os(sbuf);
os << "Object was autoreleased ";
if (V.getAutoreleaseCount() > 1)
os << V.getAutoreleaseCount() << " times but the object ";
else
os << "but ";
os << "has a +" << V.getCount() << " retain count";
const LangOptions &LOpts = Ctx.getASTContext().getLangOpts();
auto R = std::make_unique<RefCountReport>(*OverAutorelease, LOpts, N, Sym,
os.str());
Ctx.emitReport(std::move(R));
}
return nullptr;
}
ProgramStateRef
RetainCountChecker::handleSymbolDeath(ProgramStateRef state,
SymbolRef sid, RefVal V,
SmallVectorImpl<SymbolRef> &Leaked) const {
bool hasLeak;
if (V.getIvarAccessHistory() != RefVal::IvarAccessHistory::None)
hasLeak = false;
else if (V.isOwned())
hasLeak = true;
else if (V.isNotOwned() || V.isReturnedOwned())
hasLeak = (V.getCount() > 0);
else
hasLeak = false;
if (!hasLeak)
return removeRefBinding(state, sid);
Leaked.push_back(sid);
return setRefBinding(state, sid, V ^ RefVal::ErrorLeak);
}
ExplodedNode *
RetainCountChecker::processLeaks(ProgramStateRef state,
SmallVectorImpl<SymbolRef> &Leaked,
CheckerContext &Ctx,
ExplodedNode *Pred) const {
ExplodedNode *N = Ctx.addTransition(state, Pred);
const LangOptions &LOpts = Ctx.getASTContext().getLangOpts();
if (N) {
for (SymbolRef L : Leaked) {
const RefCountBug &BT = Pred ? *LeakWithinFunction : *LeakAtReturn;
Ctx.emitReport(std::make_unique<RefLeakReport>(BT, LOpts, N, L, Ctx));
}
}
return N;
}
void RetainCountChecker::checkBeginFunction(CheckerContext &Ctx) const {
if (!Ctx.inTopFrame())
return;
RetainSummaryManager &SmrMgr = getSummaryManager(Ctx);
const LocationContext *LCtx = Ctx.getLocationContext();
const Decl *D = LCtx->getDecl();
Optional<AnyCall> C = AnyCall::forDecl(D);
if (!C || SmrMgr.isTrustedReferenceCountImplementation(D))
return;
ProgramStateRef state = Ctx.getState();
const RetainSummary *FunctionSummary = SmrMgr.getSummary(*C);
ArgEffects CalleeSideArgEffects = FunctionSummary->getArgEffects();
for (unsigned idx = 0, e = C->param_size(); idx != e; ++idx) {
const ParmVarDecl *Param = C->parameters()[idx];
SymbolRef Sym = state->getSVal(state->getRegion(Param, LCtx)).getAsSymbol();
QualType Ty = Param->getType();
const ArgEffect *AE = CalleeSideArgEffects.lookup(idx);
if (AE) {
ObjKind K = AE->getObjKind();
if (K == ObjKind::Generalized || K == ObjKind::OS ||
(TrackNSCFStartParam && (K == ObjKind::ObjC || K == ObjKind::CF))) {
RefVal NewVal = AE->getKind() == DecRef ? RefVal::makeOwned(K, Ty)
: RefVal::makeNotOwned(K, Ty);
state = setRefBinding(state, Sym, NewVal);
}
}
}
Ctx.addTransition(state);
}
void RetainCountChecker::checkEndFunction(const ReturnStmt *RS,
CheckerContext &Ctx) const {
ExplodedNode *Pred = processReturn(RS, Ctx);
if (!Pred) {
return;
}
ProgramStateRef state = Pred->getState();
RefBindingsTy B = state->get<RefBindings>();
const LocationContext *LCtx = Pred->getLocationContext();
if (LCtx->getAnalysisDeclContext()->isBodyAutosynthesized()) {
assert(!LCtx->inTopFrame());
return;
}
for (auto &I : B) {
state = handleAutoreleaseCounts(state, Pred, nullptr, Ctx,
I.first, I.second);
if (!state)
return;
}
if (LCtx->getParent())
return;
B = state->get<RefBindings>();
SmallVector<SymbolRef, 10> Leaked;
for (auto &I : B)
state = handleSymbolDeath(state, I.first, I.second, Leaked);
processLeaks(state, Leaked, Ctx, Pred);
}
void RetainCountChecker::checkDeadSymbols(SymbolReaper &SymReaper,
CheckerContext &C) const {
ExplodedNode *Pred = C.getPredecessor();
ProgramStateRef state = C.getState();
SmallVector<SymbolRef, 10> Leaked;
for (const auto &I: state->get<RefBindings>()) {
SymbolRef Sym = I.first;
if (SymReaper.isDead(Sym)) {
static CheckerProgramPointTag Tag(this, "DeadSymbolAutorelease");
const RefVal &V = I.second;
state = handleAutoreleaseCounts(state, Pred, &Tag, C, Sym, V);
if (!state)
return;
state = handleSymbolDeath(state, Sym, *getRefBinding(state, Sym), Leaked);
}
}
if (Leaked.empty()) {
C.addTransition(state);
return;
}
Pred = processLeaks(state, Leaked, C, Pred);
if (!Pred)
return;
RefBindingsTy::Factory &F = state->get_context<RefBindings>();
RefBindingsTy B = state->get<RefBindings>();
for (SymbolRef L : Leaked)
B = F.remove(B, L);
state = state->set<RefBindings>(B);
C.addTransition(state, Pred);
}
void RetainCountChecker::printState(raw_ostream &Out, ProgramStateRef State,
const char *NL, const char *Sep) const {
RefBindingsTy B = State->get<RefBindings>();
if (B.isEmpty())
return;
Out << Sep << NL;
for (auto &I : B) {
Out << I.first << " : ";
I.second.print(Out);
Out << NL;
}
}
std::unique_ptr<CheckerProgramPointTag> RetainCountChecker::DeallocSentTag;
std::unique_ptr<CheckerProgramPointTag> RetainCountChecker::CastFailTag;
void ento::registerRetainCountBase(CheckerManager &Mgr) {
auto *Chk = Mgr.registerChecker<RetainCountChecker>();
Chk->DeallocSentTag =
std::make_unique<CheckerProgramPointTag>(Chk, "DeallocSent");
Chk->CastFailTag =
std::make_unique<CheckerProgramPointTag>(Chk, "DynamicCastFail");
}
bool ento::shouldRegisterRetainCountBase(const CheckerManager &mgr) {
return true;
}
void ento::registerRetainCountChecker(CheckerManager &Mgr) {
auto *Chk = Mgr.getChecker<RetainCountChecker>();
Chk->TrackObjCAndCFObjects = true;
Chk->TrackNSCFStartParam = Mgr.getAnalyzerOptions().getCheckerBooleanOption(
Mgr.getCurrentCheckerName(), "TrackNSCFStartParam");
#define INIT_BUGTYPE(KIND) \
Chk->KIND = std::make_unique<RefCountBug>(Mgr.getCurrentCheckerName(), \
RefCountBug::KIND);
INIT_BUGTYPE(UseAfterRelease)
INIT_BUGTYPE(ReleaseNotOwned)
INIT_BUGTYPE(DeallocNotOwned)
INIT_BUGTYPE(FreeNotOwned)
INIT_BUGTYPE(OverAutorelease)
INIT_BUGTYPE(ReturnNotOwnedForOwned)
INIT_BUGTYPE(LeakWithinFunction)
INIT_BUGTYPE(LeakAtReturn)
#undef INIT_BUGTYPE
}
bool ento::shouldRegisterRetainCountChecker(const CheckerManager &mgr) {
return true;
}
void ento::registerOSObjectRetainCountChecker(CheckerManager &Mgr) {
auto *Chk = Mgr.getChecker<RetainCountChecker>();
Chk->TrackOSObjects = true;
#define LAZY_INIT_BUGTYPE(KIND) \
if (!Chk->KIND) \
Chk->KIND = std::make_unique<RefCountBug>(Mgr.getCurrentCheckerName(), \
RefCountBug::KIND);
LAZY_INIT_BUGTYPE(UseAfterRelease)
LAZY_INIT_BUGTYPE(ReleaseNotOwned)
LAZY_INIT_BUGTYPE(DeallocNotOwned)
LAZY_INIT_BUGTYPE(FreeNotOwned)
LAZY_INIT_BUGTYPE(OverAutorelease)
LAZY_INIT_BUGTYPE(ReturnNotOwnedForOwned)
LAZY_INIT_BUGTYPE(LeakWithinFunction)
LAZY_INIT_BUGTYPE(LeakAtReturn)
#undef LAZY_INIT_BUGTYPE
}
bool ento::shouldRegisterOSObjectRetainCountChecker(const CheckerManager &mgr) {
return true;
}