Index: clang/include/clang/StaticAnalyzer/Core/RetainSummaryManager.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/RetainSummaryManager.h +++ clang/include/clang/StaticAnalyzer/Core/RetainSummaryManager.h @@ -77,13 +77,21 @@ 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. Index: clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp +++ clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp @@ -529,38 +529,80 @@ C.addTransition(state); } -static ProgramStateRef updateOutParameter(ProgramStateRef State, - SVal ArgVal, - ArgEffectKind Effect) { - auto *ArgRegion = dyn_cast_or_null(ArgVal.getAsRegion()); - if (!ArgRegion) - return State; - - QualType PointeeTy = ArgRegion->getValueType(); - if (!coreFoundation::isCFObjectRef(PointeeTy)) - return State; - - SVal PointeeVal = State->getSVal(ArgRegion); - SymbolRef Pointee = PointeeVal.getAsLocSymbol(); - if (!Pointee) - return State; - - 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; +static SmallVector +updateOutParameters(ProgramStateRef State, const RetainSummary &Summ, + const CallEvent &CE) { + SVal L = CE.getReturnValue(); + + // 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()) { + 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(ArgVal.getAsRegion()); + if (!ArgRegion) + continue; - default: - llvm_unreachable("only for out parameters"); + QualType PointeeTy = ArgRegion->getValueType(); + SVal PointeeVal = State->getSVal(ArgRegion); + SymbolRef Pointee = PointeeVal.getAsLocSymbol(); + if (!Pointee) + 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; + } } - return State; + if (SplitNecessary) { + return {AssumeNonZeroReturn, AssumeZeroReturn}; + } else { + assert(AssumeZeroReturn == AssumeNonZeroReturn); + return {AssumeZeroReturn}; + } } void RetainCountChecker::checkSummary(const RetainSummary &Summ, @@ -582,10 +624,7 @@ 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 +700,15 @@ state = setRefBinding(state, Sym, *updatedRefVal); } - if (DeallocSent) { - C.addTransition(state, C.getPredecessor(), &DeallocSentTag); - } else { - C.addTransition(state); + SmallVector Out = + updateOutParameters(state, Summ, CallOrMsg); + + for (ProgramStateRef St : Out) { + if (DeallocSent) { + C.addTransition(St, C.getPredecessor(), &DeallocSentTag); + } else { + C.addTransition(St); + } } } @@ -700,6 +744,8 @@ 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."); Index: clang/lib/StaticAnalyzer/Core/RetainSummaryManager.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/RetainSummaryManager.cpp +++ clang/lib/StaticAnalyzer/Core/RetainSummaryManager.cpp @@ -522,6 +522,8 @@ case IncRef: case UnretainedOutParameter: case RetainedOutParameter: + case RetainedOutParameterOnZero: + case RetainedOutParameterOnNonZero: case MayEscape: case StopTracking: case StopTrackingHard: @@ -811,6 +813,29 @@ 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(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(ND)) { + return FD->getReturnType(); + } else if (const auto *MD = dyn_cast(ND)) { + return MD->getReturnType(); + } else { + llvm_unreachable("Unexpected decl"); + } +} + bool RetainSummaryManager::applyParamAnnotationEffect( const ParmVarDecl *pd, unsigned parm_idx, const NamedDecl *FD, RetainSummaryTemplate &Template) { @@ -822,19 +847,46 @@ return true; } else if (auto K = hasAnyEnabledAttrOf(pd, QT)) { - Template->addArg(AF, parm_idx, ArgEffect(RetainedOutParameter, *K)); + + // For OSObjects, we try to guess whether the object is created based + // on the return value. + if (K == ObjKind::OS) { + QualType QT = getCallableReturnType(FD); + + // 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, + // becauese kReturnSuccess constant is defined as zero. + bool SuccessOnZero = hasTypedefNamed(QT, "kern_return_t"); + bool ShouldSplit = !QT.isNull() && !QT->isVoidType(); + ArgEffectKind AK = RetainedOutParameter; + if (ShouldSplit && SuccessOnZero) { + AK = RetainedOutParameterOnZero; + } else if (ShouldSplit && !SuccessOnZero) { + AK = RetainedOutParameterOnNonZero; + } + Template->addArg(AF, parm_idx, ArgEffect(AK, *K)); + } + + // 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(pd, QT)) { + } else if (auto K = hasAnyEnabledAttrOf( + pd, QT)) { Template->addArg(AF, parm_idx, ArgEffect(UnretainedOutParameter, *K)); return true; - } else { - if (const auto *MD = dyn_cast(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(FD)) { + for (const auto *OD : MD->overridden_methods()) { + const ParmVarDecl *OP = OD->parameters()[parm_idx]; + if (applyParamAnnotationEffect(OP, parm_idx, OD, Template)) + return true; } } Index: clang/test/Analysis/osobject-retain-release.cpp =================================================================== --- clang/test/Analysis/osobject-retain-release.cpp +++ clang/test/Analysis/osobject-retain-release.cpp @@ -94,6 +94,134 @@ void escape_with_source(void *p) {} bool coin(); +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{{Call to function 'write_into_out_param_on_success' returns an OSObject of type OSObject with a +1 retain count}} +} // 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}} + +typedef int kern_return_t; +typedef kern_return_t IOReturn; +typedef kern_return_t OSReturn; +#define kOSReturnSuccess 0 +#define kIOReturnSuccess 0 + +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{{Call to function 'write_into_two_out_params' returns an OSObject of type OSObject with a +1 retain count}} + // expected-note@-1{{Call to function 'write_into_two_out_params' returns an OSObject of type OSObject with a +1 retain count}} +} // 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{{Call to function 'always_write_into_two_out_params' returns an OSObject of type OSObject with a +1 retain count}} + // expected-note@-1{{Call to function 'always_write_into_two_out_params' returns an OSObject of type OSObject with a +1 retain count}} +} // 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{{Call to function 'write_into_out_param_on_nonnull' returns an OSObject of type OSObject with a +1 retain count}} +} // 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{{Call to function 'write_into_non_retained_out_param' returns an OSObject of type OSObject with a +0 retain count}} + 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 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{{Call to function 'always_write_into_out_param' returns an OSObject of type OSObject with a +1 retain count}} +} // 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{{Call to function 'write_into_out_param_on_os_success' returns an OSObject of type OSObject with a +1 retain count}} +} // 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 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 +559,3 @@ void test_escape_to_unknown_block(Blk blk) { blk(getObject()); // no-crash } -