summaryrefslogtreecommitdiff
path: root/test/Analysis/osobject-retain-release.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'test/Analysis/osobject-retain-release.cpp')
-rw-r--r--test/Analysis/osobject-retain-release.cpp611
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
+}