diff options
Diffstat (limited to 'test/Analysis/osobject-retain-release.cpp')
-rw-r--r-- | test/Analysis/osobject-retain-release.cpp | 611 |
1 files changed, 611 insertions, 0 deletions
diff --git a/test/Analysis/osobject-retain-release.cpp b/test/Analysis/osobject-retain-release.cpp new file mode 100644 index 0000000000000..9d11a0623167c --- /dev/null +++ b/test/Analysis/osobject-retain-release.cpp @@ -0,0 +1,611 @@ +// RUN: %clang_analyze_cc1 -fblocks -analyze -analyzer-output=text\ +// RUN: -analyzer-checker=core,osx -verify %s + +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)) + +#define OSTypeID(type) (type::metaClass) + +#define OSDynamicCast(type, inst) \ + ((type *) OSMetaClassBase::safeMetaCast((inst), OSTypeID(type))) + +using size_t = decltype(sizeof(int)); + +struct OSObject { + virtual void retain(); + virtual void release() {}; + virtual void free(); + virtual ~OSObject(){} + + unsigned int foo() { return 42; } + + virtual OS_RETURNS_NOT_RETAINED OSObject *identity(); + + static OSObject *generateObject(int); + + static OSObject *getObject(); + static OSObject *GetObject(); + + static void * operator new(size_t size); + + static const OSMetaClass * const metaClass; +}; + +struct OSIterator : public OSObject { + + static const OSMetaClass * const metaClass; +}; + +struct OSArray : public OSObject { + unsigned int getCount(); + + OSIterator * getIterator(); + + OSObject *identity() override; + + virtual OSObject *generateObject(OSObject *input); + + virtual void consumeReference(OS_CONSUME OSArray *other); + + void putIntoArray(OSArray *array) OS_CONSUMES_THIS; + + template <typename T> + void putIntoT(T *owner) OS_CONSUMES_THIS; + + static OSArray *generateArrayHasCode() { + return new OSArray; + } + + static OSArray *withCapacity(unsigned int capacity); + static void consumeArray(OS_CONSUME OSArray * array); + + static OSArray* consumeArrayHasCode(OS_CONSUME OSArray * array) { + return nullptr; + } + + static OS_RETURNS_NOT_RETAINED OSArray *MaskedGetter(); + static OS_RETURNS_RETAINED OSArray *getOoopsActuallyCreate(); + + static const OSMetaClass * const metaClass; +}; + +struct MyArray : public OSArray { + void consumeReference(OSArray *other) override; + + OSObject *identity() override; + + OSObject *generateObject(OSObject *input) override; +}; + +struct OtherStruct { + static void doNothingToArray(OSArray *array); + OtherStruct(OSArray *arr); +}; + +struct OSMetaClassBase { + static OSObject *safeMetaCast(const OSObject *inst, const OSMetaClass *meta); +}; + +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}} + escape(obj); + return true; + } + return false; // expected-note{{Parameter 'obj' is marked as consuming, but the function did not consume the reference}} +} + +bool os_consume_violation(OS_CONSUME OSObject *obj) { + if (coin()) { // expected-note{{Assuming the condition is false}} + // expected-note@-1{{Taking false branch}} + escape(obj); + return true; + } + return false; // expected-note{{Parameter 'obj' is marked as consuming, but the function did not consume the reference}} +} + +void os_consume_ok(OS_CONSUME OSObject *obj) { + escape(obj); +} + +void use_os_consume_violation() { + OSObject *obj = new OSObject; // expected-note{{Operator 'new' returns an OSObject of type 'OSObject' with a +1 retain count}} + os_consume_violation(obj); // expected-note{{Calling 'os_consume_violation'}} + // expected-note@-1{{Returning from 'os_consume_violation'}} +} // expected-note{{Object leaked: object allocated and stored into 'obj' is not referenced later in this execution path and has a retain count of +1}} + // expected-warning@-1{{Potential leak of an object stored into 'obj'}} + +void use_os_consume_violation_two_args() { + OSObject *obj = new OSObject; // expected-note{{Operator 'new' returns an OSObject of type 'OSObject' with a +1 retain count}} + os_consume_violation_two_args(obj, coin()); // expected-note{{Calling 'os_consume_violation_two_args'}} + // expected-note@-1{{Returning from 'os_consume_violation_two_args'}} +} // expected-note{{Object leaked: object allocated and stored into 'obj' is not referenced later in this execution path and has a retain count of +1}} + // expected-warning@-1{{Potential leak of an object stored into 'obj'}} + +void use_os_consume_ok() { + OSObject *obj = new OSObject; + os_consume_ok(obj); +} + +void test_escaping_into_voidstar() { + OSObject *obj = new OSObject; + escape(obj); +} + +void test_escape_has_source() { + OSObject *obj = new OSObject; + if (obj) + escape_with_source(obj); + return; +} + +void test_no_infinite_check_recursion(MyArray *arr) { + OSObject *input = new OSObject; + OSObject *o = arr->generateObject(input); + o->release(); + input->release(); +} + + +void check_param_attribute_propagation(MyArray *parent) { + OSArray *arr = new OSArray; + parent->consumeReference(arr); +} + +unsigned int check_attribute_propagation(OSArray *arr) { + OSObject *other = arr->identity(); + OSArray *casted = OSDynamicCast(OSArray, other); + if (casted) + return casted->getCount(); + return 0; +} + +unsigned int check_attribute_indirect_propagation(MyArray *arr) { + OSObject *other = arr->identity(); + OSArray *casted = OSDynamicCast(OSArray, other); + if (casted) + return casted->getCount(); + return 0; +} + +void check_consumes_this(OSArray *owner) { + OSArray *arr = new OSArray; + arr->putIntoArray(owner); +} + +void check_consumes_this_with_template(OSArray *owner) { + OSArray *arr = new OSArray; + arr->putIntoT(owner); +} + +void check_free_no_error() { + OSArray *arr = OSArray::withCapacity(10); + arr->retain(); + arr->retain(); + arr->retain(); + arr->free(); +} + +void check_free_use_after_free() { + OSArray *arr = OSArray::withCapacity(10); // expected-note{{Call to method 'OSArray::withCapacity' returns an OSObject of type 'OSArray' with a +1 retain count}} + arr->retain(); // expected-note{{Reference count incremented. The object now has a +2 retain count}} + arr->free(); // expected-note{{Object released}} + arr->retain(); // expected-warning{{Reference-counted object is used after it is released}} + // expected-note@-1{{Reference-counted object is used after it is released}} +} + +unsigned int check_leak_explicit_new() { + OSArray *arr = new OSArray; // expected-note{{Operator 'new' returns an OSObject of type 'OSArray' with a +1 retain count}} + return arr->getCount(); // expected-note{{Object leaked: object allocated and stored into 'arr' is not referenced later in this execution path and has a retain count of +1}} + // expected-warning@-1{{Potential leak of an object stored into 'arr'}} +} + +unsigned int check_leak_factory() { + OSArray *arr = OSArray::withCapacity(10); // expected-note{{Call to method 'OSArray::withCapacity' returns an OSObject of type 'OSArray' with a +1 retain count}} + return arr->getCount(); // expected-note{{Object leaked: object allocated and stored into 'arr' is not referenced later in this execution path and has a retain count of +1}} + // expected-warning@-1{{Potential leak of an object stored into 'arr'}} +} + +void check_get_object() { + OSObject::getObject(); +} + +void check_Get_object() { + OSObject::GetObject(); +} + +void check_custom_iterator_rule(OSArray *arr) { + OSIterator *it = arr->getIterator(); + it->release(); +} + +void check_iterator_leak(OSArray *arr) { + arr->getIterator(); // expected-note{{Call to method 'OSArray::getIterator' returns an OSObject of type 'OSIterator' with a +1 retain count}} +} // expected-note{{Object leaked: allocated object of type 'OSIterator' is not referenced later}} + // expected-warning@-1{{Potential leak of an object of type 'OSIterator}}' + +void check_no_invalidation() { + OSArray *arr = OSArray::withCapacity(10); // expected-note{{Call to method 'OSArray::withCapacity' returns an OSObject of type 'OSArray' with a +1 retain count}} + OtherStruct::doNothingToArray(arr); +} // expected-warning{{Potential leak of an object stored into 'arr'}} + // expected-note@-1{{Object leaked}} + +void check_no_invalidation_other_struct() { + OSArray *arr = OSArray::withCapacity(10); // expected-note{{Call to method 'OSArray::withCapacity' returns an OSObject of type 'OSArray' with a +1 retain count}} + OtherStruct other(arr); // expected-warning{{Potential leak}} + // expected-note@-1{{Object leaked}} +} + +struct ArrayOwner : public OSObject { + OSArray *arr; + ArrayOwner(OSArray *arr) : arr(arr) {} + + static ArrayOwner* create(OSArray *arr) { + return new ArrayOwner(arr); + } + + OSArray *getArray() { + return arr; + } + + OSArray *createArray() { + return OSArray::withCapacity(10); + } + + OSArray *createArraySourceUnknown(); + + OSArray *getArraySourceUnknown(); +}; + +OSArray *generateArray() { + return OSArray::withCapacity(10); // expected-note{{Call to method 'OSArray::withCapacity' returns an OSObject of type 'OSArray' with a +1 retain count}} + // expected-note@-1{{Call to method 'OSArray::withCapacity' returns an OSObject of type 'OSArray' with a +1 retain count}} +} + +unsigned int check_leak_good_error_message() { + unsigned int out; + { + OSArray *leaked = generateArray(); // expected-note{{Calling 'generateArray'}} + // expected-note@-1{{Returning from 'generateArray'}} + out = leaked->getCount(); // expected-warning{{Potential leak of an object stored into 'leaked'}} + // expected-note@-1{{Object leaked: object allocated and stored into 'leaked' is not referenced later in this execution path and has a retain count of +1}} + } + return out; +} + +unsigned int check_leak_msg_temporary() { + return generateArray()->getCount(); // expected-warning{{Potential leak of an object}} + // expected-note@-1{{Calling 'generateArray'}} + // expected-note@-2{{Returning from 'generateArray'}} + // expected-note@-3{{Object leaked: allocated object of type 'OSArray' is not referenced later in this execution path and has a retain count of +1}} +} + +void check_confusing_getters() { + OSArray *arr = OSArray::withCapacity(10); + + ArrayOwner *AO = ArrayOwner::create(arr); + AO->getArray(); + + AO->release(); + arr->release(); +} + +void check_rc_consumed() { + OSArray *arr = OSArray::withCapacity(10); + OSArray::consumeArray(arr); +} + +void check_rc_consume_temporary() { + OSArray::consumeArray(OSArray::withCapacity(10)); +} + +void check_rc_getter() { + OSArray *arr = OSArray::MaskedGetter(); + (void)arr; +} + +void check_rc_create() { + OSArray *arr = OSArray::getOoopsActuallyCreate(); + arr->release(); +} + + +void check_dynamic_cast() { + OSArray *arr = OSDynamicCast(OSArray, OSObject::generateObject(1)); + arr->release(); +} + +unsigned int check_dynamic_cast_no_null_on_orig(OSObject *obj) { + OSArray *arr = OSDynamicCast(OSArray, obj); + if (arr) { + return arr->getCount(); + } else { + + // The fact that dynamic cast has failed should not imply that + // the input object was null. + return obj->foo(); // no-warning + } +} + +void check_dynamic_cast_null_branch(OSObject *obj) { + OSArray *arr1 = OSArray::withCapacity(10); // expected-note{{Call to method 'OSArray::withCapacity' returns an OSObject}} + OSArray *arr = OSDynamicCast(OSArray, obj); + if (!arr) // expected-note{{Taking true branch}} + return; // expected-warning{{Potential leak of an object stored into 'arr1'}} + // expected-note@-1{{Object leaked}} + arr1->release(); +} + +void check_dynamic_cast_null_check() { + OSArray *arr = OSDynamicCast(OSArray, OSObject::generateObject(1)); // expected-note{{Call to method 'OSObject::generateObject' returns an OSObject}} + // expected-warning@-1{{Potential leak of an object}} + // expected-note@-2{{Object leaked}} + if (!arr) + return; + arr->release(); +} + +void use_after_release() { + OSArray *arr = OSArray::withCapacity(10); // expected-note{{Call to method 'OSArray::withCapacity' returns an OSObject of type 'OSArray' with a +1 retain count}} + arr->release(); // expected-note{{Object released}} + arr->getCount(); // expected-warning{{Reference-counted object is used after it is released}} + // expected-note@-1{{Reference-counted object is used after it is released}} +} + +void potential_leak() { + OSArray *arr = OSArray::withCapacity(10); // expected-note{{Call to method 'OSArray::withCapacity' returns an OSObject of type 'OSArray' with a +1 retain count}} + arr->retain(); // expected-note{{Reference count incremented. The object now has a +2 retain count}} + arr->release(); // expected-note{{Reference count decremented. The object now has a +1 retain count}} + arr->getCount(); +} // expected-warning{{Potential leak of an object stored into 'arr'}} + // expected-note@-1{{Object leaked: object allocated and stored into 'arr' is not referenced later in this execution path and has a retain count of +1}} + +void proper_cleanup() { + OSArray *arr = OSArray::withCapacity(10); // +1 + arr->retain(); // +2 + arr->release(); // +1 + arr->getCount(); + arr->release(); // 0 +} + +unsigned int no_warning_on_getter(ArrayOwner *owner) { + OSArray *arr = owner->getArray(); + return arr->getCount(); +} + +unsigned int warn_on_overrelease(ArrayOwner *owner) { + // FIXME: summaries are not applied in case the source of the getter/setter + // is known. + // rdar://45681203 + OSArray *arr = owner->getArray(); + arr->release(); + return arr->getCount(); +} + +unsigned int nowarn_on_release_of_created(ArrayOwner *owner) { + OSArray *arr = owner->createArray(); + unsigned int out = arr->getCount(); + arr->release(); + return out; +} + +unsigned int nowarn_on_release_of_created_source_unknown(ArrayOwner *owner) { + OSArray *arr = owner->createArraySourceUnknown(); + unsigned int out = arr->getCount(); + arr->release(); + return out; +} + +unsigned int no_warn_ok_release(ArrayOwner *owner) { + OSArray *arr = owner->getArray(); // +0 + arr->retain(); // +1 + arr->release(); // +0 + return arr->getCount(); // no-warning +} + +unsigned int warn_on_overrelease_with_unknown_source(ArrayOwner *owner) { + OSArray *arr = owner->getArraySourceUnknown(); // expected-note{{Call to method 'ArrayOwner::getArraySourceUnknown' returns an OSObject of type 'OSArray' with a +0 retain count}} + arr->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}} + return arr->getCount(); +} + +unsigned int ok_release_with_unknown_source(ArrayOwner *owner) { + OSArray *arr = owner->getArraySourceUnknown(); // +0 + arr->retain(); // +1 + arr->release(); // +0 + return arr->getCount(); +} + +OSObject *getObject(); +typedef bool (^Blk)(OSObject *); + +void test_escape_to_unknown_block(Blk blk) { + blk(getObject()); // no-crash +} |