[analyzer] Support for OSObjects out parameters in RetainCountChecker

rdar://46357478
rdar://47121327

Differential Revision: https://reviews.llvm.org/D56240

llvm-svn: 350982
This commit is contained in:
George Karpenkov 2019-01-11 23:35:17 +00:00
parent 9f3a279f2c
commit 5be959c88e
7 changed files with 410 additions and 80 deletions

View File

@ -77,13 +77,21 @@ enum ArgEffectKind {
IncRef,
/// The argument is a pointer to a retain-counted object; on exit, the new
/// value of the pointer is a +0 value or NULL.
/// value of the pointer is a +0 value.
UnretainedOutParameter,
/// The argument is a pointer to a retain-counted object; on exit, the new
/// value of the pointer is a +1 value or NULL.
/// value of the pointer is a +1 value.
RetainedOutParameter,
/// The argument is a pointer to a retain-counted object; on exit, the new
/// value of the pointer is a +1 value iff the return code is zero.
RetainedOutParameterOnZero,
/// The argument is a pointer to a retain-counted object; on exit, the new
/// value of the pointer is a +1 value iff the return code is non-zero.
RetainedOutParameterOnNonZero,
/// The argument is treated as potentially escaping, meaning that
/// even when its reference count hits 0 it should be treated as still
/// possibly being alive as someone else *may* be holding onto the object.

View File

@ -529,38 +529,92 @@ void RetainCountChecker::processSummaryOfInlined(const RetainSummary &Summ,
C.addTransition(state);
}
static ProgramStateRef updateOutParameter(ProgramStateRef State,
SVal ArgVal,
ArgEffectKind Effect) {
auto *ArgRegion = dyn_cast_or_null<TypedValueRegion>(ArgVal.getAsRegion());
if (!ArgRegion)
return State;
static bool shouldEscapeRegion(const MemRegion *R) {
QualType PointeeTy = ArgRegion->getValueType();
if (!coreFoundation::isCFObjectRef(PointeeTy))
return State;
// We do not currently model what happens when a symbol is
// assigned to a struct field, so be conservative here and let the symbol
// go. TODO: This could definitely be improved upon.
return !R->hasStackStorage() || !isa<VarRegion>(R);
}
SVal PointeeVal = State->getSVal(ArgRegion);
SymbolRef Pointee = PointeeVal.getAsLocSymbol();
if (!Pointee)
return State;
static SmallVector<ProgramStateRef, 2>
updateOutParameters(ProgramStateRef State, const RetainSummary &Summ,
const CallEvent &CE) {
switch (Effect) {
case UnretainedOutParameter:
State = setRefBinding(State, Pointee,
RefVal::makeNotOwned(ObjKind::CF, PointeeTy));
break;
case RetainedOutParameter:
// Do nothing. Retained out parameters will either point to a +1 reference
// or NULL, but the way you check for failure differs depending on the API.
// Consequently, we don't have a good way to track them yet.
break;
SVal L = CE.getReturnValue();
default:
llvm_unreachable("only for out parameters");
// Splitting is required to support out parameters,
// as out parameters might be created only on the "success" branch.
// We want to avoid eagerly splitting unless out parameters are actually
// needed.
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 (auto DL = L.getAs<DefinedOrUnknownSVal>()) {
AssumeNonZeroReturn = AssumeNonZeroReturn->assume(*DL, true);
AssumeZeroReturn = AssumeZeroReturn->assume(*DL, false);
}
}
return State;
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,
@ -582,10 +636,7 @@ void RetainCountChecker::checkSummary(const RetainSummary &Summ,
SVal V = CallOrMsg.getArgSVal(idx);
ArgEffect Effect = Summ.getArg(idx);
if (Effect.getKind() == RetainedOutParameter ||
Effect.getKind() == UnretainedOutParameter) {
state = updateOutParameter(state, V, Effect.getKind());
} else if (SymbolRef Sym = V.getAsLocSymbol()) {
if (SymbolRef Sym = V.getAsLocSymbol()) {
if (const RefVal *T = getRefBinding(state, Sym)) {
if (shouldEscapeOSArgumentOnCall(CallOrMsg, idx, T))
@ -661,10 +712,15 @@ void RetainCountChecker::checkSummary(const RetainSummary &Summ,
state = setRefBinding(state, Sym, *updatedRefVal);
}
if (DeallocSent) {
C.addTransition(state, C.getPredecessor(), &DeallocSentTag);
} else {
C.addTransition(state);
SmallVector<ProgramStateRef, 2> Out =
updateOutParameters(state, Summ, CallOrMsg);
for (ProgramStateRef St : Out) {
if (DeallocSent) {
C.addTransition(St, C.getPredecessor(), &DeallocSentTag);
} else {
C.addTransition(St);
}
}
}
@ -700,6 +756,8 @@ ProgramStateRef RetainCountChecker::updateSymbol(ProgramStateRef state,
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.");
@ -1094,29 +1152,10 @@ void RetainCountChecker::checkBind(SVal loc, SVal val, const Stmt *S,
//
// (1) we are binding to something that is not a memory region.
// (2) we are binding to a memregion that does not have stack storage
// (3) we are binding to a memregion with stack storage that the store
// does not understand.
ProgramStateRef state = C.getState();
if (auto regionLoc = loc.getAs<loc::MemRegionVal>()) {
escapes = !regionLoc->getRegion()->hasStackStorage();
if (!escapes) {
// To test (3), generate a new state with the binding added. If it is
// the same state, then it escapes (since the store cannot represent
// the binding).
// Do this only if we know that the store is not supposed to generate the
// same state.
SVal StoredVal = state->getSVal(regionLoc->getRegion());
if (StoredVal != val)
escapes = (state == (state->bindLoc(*regionLoc, val, C.getLocationContext())));
}
if (!escapes) {
// Case 4: We do not currently model what happens when a symbol is
// assigned to a struct field, so be conservative here and let the symbol
// go. TODO: This could definitely be improved upon.
escapes = !isa<VarRegion>(regionLoc->getRegion());
}
escapes = shouldEscapeRegion(regionLoc->getRegion());
}
// If we are storing the value into an auto function scope variable annotated

View File

@ -113,11 +113,31 @@ static bool shouldGenerateNote(llvm::raw_string_ostream &os,
return true;
}
/// Finds argument index of the out paramter in the call {@code S}
/// corresponding to the symbol {@code Sym}.
/// If none found, returns None.
static Optional<unsigned> findArgIdxOfSymbol(ProgramStateRef CurrSt,
const LocationContext *LCtx,
SymbolRef &Sym,
Optional<CallEventRef<>> CE) {
if (!CE)
return None;
for (unsigned Idx = 0; Idx < (*CE)->getNumArgs(); Idx++)
if (const MemRegion *MR = (*CE)->getArgSVal(Idx).getAsRegion())
if (const auto *TR = dyn_cast<TypedValueRegion>(MR))
if (CurrSt->getSVal(MR, TR->getValueType()).getAsSymExpr() == Sym)
return Idx;
return None;
}
static void generateDiagnosticsForCallLike(ProgramStateRef CurrSt,
const LocationContext *LCtx,
const RefVal &CurrV, SymbolRef &Sym,
const Stmt *S,
llvm::raw_string_ostream &os) {
CallEventManager &Mgr = CurrSt->getStateManager().getCallEventManager();
if (const CallExpr *CE = dyn_cast<CallExpr>(S)) {
// Get the name of the callee (if it is available)
// from the tracked SVal.
@ -139,7 +159,6 @@ static void generateDiagnosticsForCallLike(ProgramStateRef CurrSt,
os << "Operator 'new'";
} else {
assert(isa<ObjCMessageExpr>(S));
CallEventManager &Mgr = CurrSt->getStateManager().getCallEventManager();
CallEventRef<ObjCMethodCall> Call =
Mgr.getObjCMethodCall(cast<ObjCMessageExpr>(S), CurrSt, LCtx);
@ -156,7 +175,15 @@ static void generateDiagnosticsForCallLike(ProgramStateRef CurrSt,
}
}
os << " returns ";
Optional<CallEventRef<>> CE = Mgr.getCall(S, CurrSt, LCtx);
auto Idx = findArgIdxOfSymbol(CurrSt, LCtx, Sym, CE);
// If index is not found, we assume that the symbol was returned.
if (!Idx) {
os << " returns ";
} else {
os << " writes ";
}
if (CurrV.getObjKind() == ObjKind::CF) {
os << "a Core Foundation object of type '"
@ -185,6 +212,25 @@ static void generateDiagnosticsForCallLike(ProgramStateRef CurrSt,
assert(CurrV.isNotOwned());
os << "+0 retain count";
}
if (Idx) {
os << " into an out parameter '";
const ParmVarDecl *PVD = (*CE)->parameters()[*Idx];
PVD->getNameForDiagnostic(os, PVD->getASTContext().getPrintingPolicy(),
/*Qualified=*/false);
os << "'";
QualType RT = (*CE)->getResultType();
if (!RT.isNull() && !RT->isVoidType()) {
SVal RV = (*CE)->getReturnValue();
if (CurrSt->isNull(RV).isConstrainedTrue()) {
os << " (assuming the call returns zero)";
} else if (CurrSt->isNonNull(RV).isConstrainedTrue()) {
os << " (assuming the call returns non-zero)";
}
}
}
}
namespace clang {

View File

@ -88,7 +88,9 @@ Optional<ObjKind> RetainSummaryManager::hasAnyEnabledAttrOf(const Decl *D,
return None;
K = ObjKind::ObjC;
} else if (isOneOf<T, OSConsumedAttr, OSConsumesThisAttr,
OSReturnsNotRetainedAttr, OSReturnsRetainedAttr>()) {
OSReturnsNotRetainedAttr, OSReturnsRetainedAttr,
OSReturnsRetainedOnZeroAttr,
OSReturnsRetainedOnNonZeroAttr>()) {
if (!TrackOSObjects)
return None;
K = ObjKind::OS;
@ -522,6 +524,8 @@ static ArgEffect getStopTrackingHardEquivalent(ArgEffect E) {
case IncRef:
case UnretainedOutParameter:
case RetainedOutParameter:
case RetainedOutParameterOnZero:
case RetainedOutParameterOnNonZero:
case MayEscape:
case StopTracking:
case StopTrackingHard:
@ -811,6 +815,29 @@ RetainSummaryManager::getRetEffectFromAnnotations(QualType RetTy,
return None;
}
/// \return Whether the chain of typedefs starting from {@code QT}
/// has a typedef with a given name {@code Name}.
static bool hasTypedefNamed(QualType QT,
StringRef Name) {
while (auto *T = dyn_cast<TypedefType>(QT)) {
const auto &Context = T->getDecl()->getASTContext();
if (T->getDecl()->getIdentifier() == &Context.Idents.get(Name))
return true;
QT = T->getDecl()->getUnderlyingType();
}
return false;
}
static QualType getCallableReturnType(const NamedDecl *ND) {
if (const auto *FD = dyn_cast<FunctionDecl>(ND)) {
return FD->getReturnType();
} else if (const auto *MD = dyn_cast<ObjCMethodDecl>(ND)) {
return MD->getReturnType();
} else {
llvm_unreachable("Unexpected decl");
}
}
bool RetainSummaryManager::applyParamAnnotationEffect(
const ParmVarDecl *pd, unsigned parm_idx, const NamedDecl *FD,
RetainSummaryTemplate &Template) {
@ -820,21 +847,54 @@ bool RetainSummaryManager::applyParamAnnotationEffect(
GeneralizedConsumedAttr>(pd, QT)) {
Template->addArg(AF, parm_idx, ArgEffect(DecRef, *K));
return true;
} else if (auto K =
hasAnyEnabledAttrOf<CFReturnsRetainedAttr,
GeneralizedReturnsRetainedAttr>(pd, QT)) {
Template->addArg(AF, parm_idx, ArgEffect(RetainedOutParameter, *K));
} else if (auto K = hasAnyEnabledAttrOf<
CFReturnsRetainedAttr, OSReturnsRetainedAttr,
OSReturnsRetainedOnNonZeroAttr, OSReturnsRetainedOnZeroAttr,
GeneralizedReturnsRetainedAttr>(pd, QT)) {
// For OSObjects, we try to guess whether the object is created based
// on the return value.
if (K == ObjKind::OS) {
QualType QT = getCallableReturnType(FD);
bool HasRetainedOnZero = pd->hasAttr<OSReturnsRetainedOnZeroAttr>();
bool HasRetainedOnNonZero = pd->hasAttr<OSReturnsRetainedOnNonZeroAttr>();
// The usual convention is to create an object on non-zero return, but
// it's reverted if the typedef chain has a typedef kern_return_t,
// because kReturnSuccess constant is defined as zero.
// The convention can be overwritten by custom attributes.
bool SuccessOnZero =
HasRetainedOnZero ||
(hasTypedefNamed(QT, "kern_return_t") && !HasRetainedOnNonZero);
bool ShouldSplit = !QT.isNull() && !QT->isVoidType();
ArgEffectKind AK = RetainedOutParameter;
if (ShouldSplit && SuccessOnZero) {
AK = RetainedOutParameterOnZero;
} else if (ShouldSplit && (!SuccessOnZero || HasRetainedOnNonZero)) {
AK = RetainedOutParameterOnNonZero;
}
Template->addArg(AF, parm_idx, ArgEffect(AK, ObjKind::OS));
}
// For others:
// Do nothing. Retained out parameters will either point to a +1 reference
// or NULL, but the way you check for failure differs depending on the
// API. Consequently, we don't have a good way to track them yet.
return true;
} else if (auto K = hasAnyEnabledAttrOf<CFReturnsNotRetainedAttr>(pd, QT)) {
} else if (auto K = hasAnyEnabledAttrOf<CFReturnsNotRetainedAttr,
OSReturnsNotRetainedAttr,
GeneralizedReturnsNotRetainedAttr>(
pd, QT)) {
Template->addArg(AF, parm_idx, ArgEffect(UnretainedOutParameter, *K));
return true;
} else {
if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
for (const auto *OD : MD->overridden_methods()) {
const ParmVarDecl *OP = OD->parameters()[parm_idx];
if (applyParamAnnotationEffect(OP, parm_idx, OD, Template))
return true;
}
}
if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
for (const auto *OD : MD->overridden_methods()) {
const ParmVarDecl *OP = OD->parameters()[parm_idx];
if (applyParamAnnotationEffect(OP, parm_idx, OD, Template))
return true;
}
}

View File

@ -25854,9 +25854,9 @@
</array>
<key>depth</key><integer>0</integer>
<key>extended_message</key>
<string>Call to function &apos;getViaParam&apos; returns a Core Foundation object of type &apos;CFTypeRef&apos; with a +0 retain count</string>
<string>Call to function &apos;getViaParam&apos; writes a Core Foundation object of type &apos;CFTypeRef&apos; with a +0 retain count into an out parameter &apos;outObj&apos;</string>
<key>message</key>
<string>Call to function &apos;getViaParam&apos; returns a Core Foundation object of type &apos;CFTypeRef&apos; with a +0 retain count</string>
<string>Call to function &apos;getViaParam&apos; writes a Core Foundation object of type &apos;CFTypeRef&apos; with a +0 retain count into an out parameter &apos;outObj&apos;</string>
</dict>
<dict>
<key>kind</key><string>control</string>
@ -26010,9 +26010,9 @@
</array>
<key>depth</key><integer>0</integer>
<key>extended_message</key>
<string>Call to function &apos;getViaParam2&apos; returns a Core Foundation object of type &apos;CFTypeRef&apos; with a +0 retain count</string>
<string>Call to function &apos;getViaParam2&apos; writes a Core Foundation object of type &apos;CFTypeRef&apos; with a +0 retain count into an out parameter &apos;outObj&apos;</string>
<key>message</key>
<string>Call to function &apos;getViaParam2&apos; returns a Core Foundation object of type &apos;CFTypeRef&apos; with a +0 retain count</string>
<string>Call to function &apos;getViaParam2&apos; writes a Core Foundation object of type &apos;CFTypeRef&apos; with a +0 retain count into an out parameter &apos;outObj&apos;</string>
</dict>
<dict>
<key>kind</key><string>control</string>

View File

@ -25923,9 +25923,9 @@
</array>
<key>depth</key><integer>0</integer>
<key>extended_message</key>
<string>Call to function &apos;getViaParam&apos; returns a Core Foundation object of type &apos;CFTypeRef&apos; with a +0 retain count</string>
<string>Call to function &apos;getViaParam&apos; writes a Core Foundation object of type &apos;CFTypeRef&apos; with a +0 retain count into an out parameter &apos;outObj&apos;</string>
<key>message</key>
<string>Call to function &apos;getViaParam&apos; returns a Core Foundation object of type &apos;CFTypeRef&apos; with a +0 retain count</string>
<string>Call to function &apos;getViaParam&apos; writes a Core Foundation object of type &apos;CFTypeRef&apos; with a +0 retain count into an out parameter &apos;outObj&apos;</string>
</dict>
<dict>
<key>kind</key><string>control</string>
@ -26079,9 +26079,9 @@
</array>
<key>depth</key><integer>0</integer>
<key>extended_message</key>
<string>Call to function &apos;getViaParam2&apos; returns a Core Foundation object of type &apos;CFTypeRef&apos; with a +0 retain count</string>
<string>Call to function &apos;getViaParam2&apos; writes a Core Foundation object of type &apos;CFTypeRef&apos; with a +0 retain count into an out parameter &apos;outObj&apos;</string>
<key>message</key>
<string>Call to function &apos;getViaParam2&apos; returns a Core Foundation object of type &apos;CFTypeRef&apos; with a +0 retain count</string>
<string>Call to function &apos;getViaParam2&apos; writes a Core Foundation object of type &apos;CFTypeRef&apos; with a +0 retain count into an out parameter &apos;outObj&apos;</string>
</dict>
<dict>
<key>kind</key><string>control</string>

View File

@ -5,6 +5,8 @@ struct OSMetaClass;
#define OS_CONSUME __attribute__((os_consumed))
#define OS_RETURNS_RETAINED __attribute__((os_returns_retained))
#define OS_RETURNS_RETAINED_ON_ZERO __attribute__((os_returns_retained_on_zero))
#define OS_RETURNS_RETAINED_ON_NONZERO __attribute__((os_returns_retained_on_non_zero))
#define OS_RETURNS_NOT_RETAINED __attribute__((os_returns_not_retained))
#define OS_CONSUMES_THIS __attribute__((os_consumes_this))
@ -94,6 +96,182 @@ void escape(void *);
void escape_with_source(void *p) {}
bool coin();
typedef int kern_return_t;
typedef kern_return_t IOReturn;
typedef kern_return_t OSReturn;
#define kOSReturnSuccess 0
#define kIOReturnSuccess 0
bool write_into_out_param_on_success(OS_RETURNS_RETAINED OSObject **obj);
void use_out_param() {
OSObject *obj;
if (write_into_out_param_on_success(&obj)) {
obj->release();
}
}
void use_out_param_leak() {
OSObject *obj;
write_into_out_param_on_success(&obj); // expected-note-re{{Call to function 'write_into_out_param_on_success' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'obj' (assuming the call returns non-zero){{$}}}}
} // expected-warning{{Potential leak of an object stored into 'obj'}}
// expected-note@-1{{Object leaked: object allocated and stored into 'obj' is not referenced later in this execution path and has a retain count of +1}}
bool write_into_out_param_on_failure(OS_RETURNS_RETAINED_ON_ZERO OSObject **obj);
void use_out_param_leak2() {
OSObject *obj;
write_into_out_param_on_failure(&obj); // expected-note-re{{Call to function 'write_into_out_param_on_failure' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'obj' (assuming the call returns zero){{$}}}}
} // expected-warning{{Potential leak of an object stored into 'obj'}}
// expected-note@-1{{Object leaked: object allocated and stored into 'obj' is not referenced later in this execution path and has a retain count of +1}}
void use_out_param_on_failure() {
OSObject *obj;
if (!write_into_out_param_on_failure(&obj)) {
obj->release();
}
}
IOReturn write_into_out_param_on_nonzero(OS_RETURNS_RETAINED_ON_NONZERO OSObject **obj);
void use_out_param_on_nonzero() {
OSObject *obj;
if (write_into_out_param_on_nonzero(&obj) != kIOReturnSuccess) {
obj->release();
}
}
bool write_into_two_out_params(OS_RETURNS_RETAINED OSObject **a,
OS_RETURNS_RETAINED OSObject **b);
void use_write_into_two_out_params() {
OSObject *obj1;
OSObject *obj2;
if (write_into_two_out_params(&obj1, &obj2)) {
obj1->release();
obj2->release();
}
}
void use_write_two_out_params_leak() {
OSObject *obj1;
OSObject *obj2;
write_into_two_out_params(&obj1, &obj2); // expected-note-re{{Call to function 'write_into_two_out_params' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'a' (assuming the call returns non-zero){{$}}}}
// expected-note-re@-1{{Call to function 'write_into_two_out_params' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'b' (assuming the call returns non-zero){{$}}}}
} // expected-warning{{Potential leak of an object stored into 'obj1'}}
// expected-warning@-1{{Potential leak of an object stored into 'obj2'}}
// expected-note@-2{{Object leaked: object allocated and stored into 'obj1' is not referenced later in this execution path and has a retain count of +1}}
// expected-note@-3{{Object leaked: object allocated and stored into 'obj2' is not referenced later in this execution path and has a retain count of +1}}
void always_write_into_two_out_params(OS_RETURNS_RETAINED OSObject **a,
OS_RETURNS_RETAINED OSObject **b);
void use_always_write_into_two_out_params() {
OSObject *obj1;
OSObject *obj2;
always_write_into_two_out_params(&obj1, &obj2);
obj1->release();
obj2->release();
}
void use_always_write_into_two_out_params_leak() {
OSObject *obj1;
OSObject *obj2;
always_write_into_two_out_params(&obj1, &obj2); // expected-note-re{{Call to function 'always_write_into_two_out_params' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'a'{{$}}}}
// expected-note-re@-1{{Call to function 'always_write_into_two_out_params' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'b'{{$}}}}
} // expected-warning{{Potential leak of an object stored into 'obj1'}}
// expected-warning@-1{{Potential leak of an object stored into 'obj2'}}
// expected-note@-2{{Object leaked: object allocated and stored into 'obj1' is not referenced later in this execution path and has a retain count of +1}}
// expected-note@-3{{Object leaked: object allocated and stored into 'obj2' is not referenced later in this execution path and has a retain count of +1}}
char *write_into_out_param_on_nonnull(OS_RETURNS_RETAINED OSObject **obj);
void use_out_param_osreturn_on_nonnull() {
OSObject *obj;
if (write_into_out_param_on_nonnull(&obj)) {
obj->release();
}
}
void use_out_param_leak_osreturn_on_nonnull() {
OSObject *obj;
write_into_out_param_on_nonnull(&obj); // expected-note-re{{Call to function 'write_into_out_param_on_nonnull' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'obj' (assuming the call returns non-zero){{$}}}}
} // expected-warning{{Potential leak of an object stored into 'obj'}}
// expected-note@-1{{Object leaked: object allocated and stored into 'obj' is not referenced later in this execution path and has a retain count of +1}}
bool write_optional_out_param(OS_RETURNS_RETAINED OSObject **obj=nullptr);
void use_optional_out_param() {
if (write_optional_out_param()) {};
}
OSReturn write_into_out_param_on_os_success(OS_RETURNS_RETAINED OSObject **obj);
void write_into_non_retained_out_param(OS_RETURNS_NOT_RETAINED OSObject **obj);
void use_write_into_non_retained_out_param() {
OSObject *obj;
write_into_non_retained_out_param(&obj);
}
void use_write_into_non_retained_out_param_uaf() {
OSObject *obj;
write_into_non_retained_out_param(&obj); // expected-note-re{{Call to function 'write_into_non_retained_out_param' writes an OSObject of type 'OSObject' with a +0 retain count into an out parameter 'obj'{{$}}}}
obj->release(); // expected-warning{{Incorrect decrement of the reference count of an object that is not owned at this point by the caller}}
// expected-note@-1{{Incorrect decrement of the reference count of an object that is not owned at this point by the caller}}
}
void always_write_into_out_param(OS_RETURNS_RETAINED OSObject **obj);
void pass_through_out_param(OSObject **obj) {
always_write_into_out_param(obj);
}
void always_write_into_out_param_has_source(OS_RETURNS_RETAINED OSObject **obj) {
*obj = new OSObject; // expected-note{{Operator 'new' returns an OSObject of type 'OSObject' with a +1 retain count}}
}
void use_always_write_into_out_param_has_source_leak() {
OSObject *obj;
always_write_into_out_param_has_source(&obj); // expected-note{{Calling 'always_write_into_out_param_has_source'}}
// expected-note@-1{{Returning from 'always_write_into_out_param_has_source'}}
} // expected-warning{{Potential leak of an object stored into 'obj'}}
// expected-note@-1{{Object leaked: object allocated and stored into 'obj' is not referenced later in this execution path and has a retain count of +1}}
void use_void_out_param_osreturn() {
OSObject *obj;
always_write_into_out_param(&obj);
obj->release();
}
void use_void_out_param_osreturn_leak() {
OSObject *obj;
always_write_into_out_param(&obj); // expected-note-re{{Call to function 'always_write_into_out_param' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'obj'{{$}}}}
} // expected-warning{{Potential leak of an object stored into 'obj'}}
// expected-note@-1{{Object leaked: object allocated and stored into 'obj' is not referenced later in this execution path and has a retain count of +1}}
void use_out_param_osreturn() {
OSObject *obj;
if (write_into_out_param_on_os_success(&obj) == kOSReturnSuccess) {
obj->release();
}
}
void use_out_param_leak_osreturn() {
OSObject *obj;
write_into_out_param_on_os_success(&obj); // expected-note-re{{Call to function 'write_into_out_param_on_os_success' writes an OSObject of type 'OSObject' with a +1 retain count into an out parameter 'obj' (assuming the call returns zero){{$}}}}
} // expected-warning{{Potential leak of an object stored into 'obj'}}
// expected-note@-1{{Object leaked: object allocated and stored into 'obj' is not referenced later in this execution path and has a retain count of +1}}
struct StructWithField {
OSObject *obj;
void initViaOutParamCall() { // no warning on writing into fields
always_write_into_out_param(&obj);
}
};
bool os_consume_violation_two_args(OS_CONSUME OSObject *obj, bool extra) {
if (coin()) { // expected-note{{Assuming the condition is false}}
// expected-note@-1{{Taking false branch}}
@ -431,4 +609,3 @@ typedef bool (^Blk)(OSObject *);
void test_escape_to_unknown_block(Blk blk) {
blk(getObject()); // no-crash
}