| // RUN: %clang_analyze_cc1 -std=c++14 -fblocks -analyzer-output=text\ | |
| // RUN: -analyzer-checker=core,osx,debug.ExprInspection -verify %s | |
| #include "os_object_base.h" | |
| #include "os_smart_ptr.h" | |
| void clang_analyzer_eval(bool); | |
| 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) { // expected-note{{Parameter 'array' starts at +1, as it is marked as consuming}} | |
| return nullptr; // expected-warning{{Potential leak of an object of type 'OSArray'}} | |
| // expected-note@-1{{Object leaked: allocated object of type 'OSArray' is not referenced later in this execution path and has a retain count of +1}} | |
| } | |
| 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); | |
| }; | |
| bool test_meta_cast_no_leak(OSMetaClassBase *arg) { | |
| return arg && arg->metaCast("blah") != nullptr; | |
| } | |
| static void consumedMismatch(OS_CONSUME OSObject *a, | |
| OSObject *b) { // expected-note{{Parameter 'b' starts at +0}} | |
| a->release(); | |
| b->retain(); // expected-note{{Reference count incremented. The object now has a +1 retain count}} | |
| } // expected-warning{{Potential leak of an object of type 'OSObject'}} | |
| // expected-note@-1{{Object leaked: allocated object of type 'OSObject' is not referenced later in this execution path and has a retain count of +1}} | |
| 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}} | |
| void cleanup(OSObject **obj); | |
| void test_cleanup_escaping() { | |
| __attribute__((cleanup(cleanup))) OSObject *obj; | |
| always_write_into_out_param(&obj); // no-warning, the value has escaped. | |
| } | |
| 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(); | |
| } | |
| void check_required_cast() { | |
| OSArray *arr = OSRequiredCast(OSArray, OSObject::generateObject(1)); | |
| arr->release(); // no-warning | |
| } | |
| void check_cast_behavior(OSObject *obj) { | |
| OSArray *arr1 = OSDynamicCast(OSArray, obj); | |
| clang_analyzer_eval(arr1 == obj); // expected-warning{{TRUE}} | |
| // expected-note@-1{{TRUE}} | |
| // expected-note@-2{{Assuming 'arr1' is not equal to 'obj'}} | |
| // expected-warning@-3{{FALSE}} | |
| // expected-note@-4 {{FALSE}} | |
| OSArray *arr2 = OSRequiredCast(OSArray, obj); | |
| clang_analyzer_eval(arr2 == obj); // expected-warning{{TRUE}} | |
| // expected-note@-1{{TRUE}} | |
| } | |
| 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); // expected-note{{Assuming dynamic cast returns null due to type mismatch}} | |
| if (!arr) // expected-note{{'arr' is null}} | |
| // expected-note@-1{{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}} | |
| // expected-note@-3{{Assuming dynamic cast returns null due to type mismatch}} | |
| if (!arr) | |
| return; | |
| arr->release(); | |
| } | |
| void check_dynamic_cast_alias() { | |
| OSObject *originalPtr = OSObject::generateObject(1); // expected-note {{Call to method 'OSObject::generateObject' returns an OSObject}} | |
| OSArray *newPtr = OSDynamicCast(OSArray, originalPtr); // expected-note {{'newPtr' initialized to the value of 'originalPtr'}} | |
| if (newPtr) { // expected-note {{'newPtr' is non-null}} | |
| // expected-note@-1 {{Taking true branch}} | |
| originalPtr = OSObject::generateObject(42); | |
| (void)newPtr; | |
| } | |
| originalPtr->release(); // expected-warning {{Potential leak of an object stored into 'newPtr'}} | |
| // expected-note@-1 {{Object leaked: object allocated and stored into 'newPtr' is not referenced later in this execution path and has a retain count of +1}} | |
| } | |
| void check_dynamic_cast_alias_cond() { | |
| OSObject *originalPtr = OSObject::generateObject(1); // expected-note {{Call to method 'OSObject::generateObject' returns an OSObject}} | |
| OSArray *newPtr = 0; | |
| if ((newPtr = OSDynamicCast(OSArray, originalPtr))) { // expected-note {{The value of 'originalPtr' is assigned to 'newPtr'}} | |
| // expected-note@-1 {{'newPtr' is non-null}} | |
| // expected-note@-2 {{Taking true branch}} | |
| originalPtr = OSObject::generateObject(42); | |
| (void)newPtr; | |
| } | |
| originalPtr->release(); // expected-warning {{Potential leak of an object stored into 'newPtr'}} | |
| // expected-note@-1 {{Object leaked: object allocated and stored into 'newPtr' is not referenced later in this execution path and has a retain count of +1}} | |
| } | |
| void check_dynamic_cast_alias_intermediate() { | |
| OSObject *originalPtr = OSObject::generateObject(1); // expected-note {{Call to method 'OSObject::generateObject' returns an OSObject of type 'OSObject' with a +1 retain count}} | |
| OSObject *intermediate = originalPtr; // TODO: add note here as well | |
| OSArray *newPtr = 0; | |
| if ((newPtr = OSDynamicCast(OSArray, intermediate))) { // expected-note {{The value of 'intermediate' is assigned to 'newPtr'}} | |
| // expected-note@-1 {{'newPtr' is non-null}} | |
| // expected-note@-2 {{Taking true branch}} | |
| intermediate = OSObject::generateObject(42); | |
| (void)newPtr; | |
| } | |
| intermediate->release(); // expected-warning {{Potential leak of an object stored into 'newPtr'}} | |
| // expected-note@-1 {{Object leaked: object allocated and stored into 'newPtr' is not referenced later in this execution path and has a retain count of +1}} | |
| } | |
| void check_dynamic_cast_alias_intermediate_2() { | |
| OSObject *originalPtr = OSObject::generateObject(1); // expected-note {{Call to method 'OSObject::generateObject' returns an OSObject of type 'OSObject' with a +1 retain count}} | |
| OSObject *intermediate = originalPtr; // TODO: add note here as well | |
| OSArray *newPtr = 0; | |
| if ((newPtr = OSDynamicCast(OSArray, intermediate))) { // expected-note {{Value assigned to 'newPtr'}} | |
| // expected-note@-1 {{'newPtr' is non-null}} | |
| // expected-note@-2 {{Taking true branch}} | |
| intermediate = OSObject::generateObject(42); | |
| (void)originalPtr; | |
| } | |
| (void)newPtr; | |
| intermediate->release(); // expected-warning {{Potential leak of an object stored into 'newPtr'}} | |
| // expected-note@-1 {{Object leaked: object allocated and stored into 'newPtr' is not referenced later in this execution path and has a retain count of +1}} | |
| } | |
| 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. | |
| 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 | |
| } | |
| using OSObjectPtr = os::smart_ptr<OSObject>; | |
| void test_smart_ptr_uaf() { | |
| OSObject *obj = new OSObject; // expected-note{{Operator 'new' returns an OSObject of type 'OSObject' with a +1 retain count}} | |
| { | |
| OSObjectPtr p(obj); // expected-note{{Calling constructor for 'smart_ptr<OSObject>'}} | |
| // expected-note@-1{{Returning from constructor for 'smart_ptr<OSObject>'}} | |
| // expected-note@os_smart_ptr.h:13{{Field 'pointer' is non-null}} | |
| // expected-note@os_smart_ptr.h:13{{Taking true branch}} | |
| // expected-note@os_smart_ptr.h:14{{Calling 'smart_ptr::_retain'}} | |
| // expected-note@os_smart_ptr.h:71{{Reference count incremented. The object now has a +2 retain count}} | |
| // expected-note@os_smart_ptr.h:14{{Returning from 'smart_ptr::_retain'}} | |
| } // expected-note{{Calling '~smart_ptr'}} | |
| // expected-note@os_smart_ptr.h:35{{Field 'pointer' is non-null}} | |
| // expected-note@os_smart_ptr.h:35{{Taking true branch}} | |
| // expected-note@os_smart_ptr.h:36{{Calling 'smart_ptr::_release'}} | |
| // expected-note@os_smart_ptr.h:76{{Reference count decremented. The object now has a +1 retain count}} | |
| // expected-note@os_smart_ptr.h:36{{Returning from 'smart_ptr::_release'}} | |
| // expected-note@-6{{Returning from '~smart_ptr'}} | |
| obj->release(); // expected-note{{Object released}} | |
| obj->release(); // expected-warning{{Reference-counted object is used after it is released}} | |
| // expected-note@-1{{Reference-counted object is used after it is released}} | |
| } | |
| void test_smart_ptr_leak() { | |
| OSObject *obj = new OSObject; // expected-note{{Operator 'new' returns an OSObject of type 'OSObject' with a +1 retain count}} | |
| { | |
| OSObjectPtr p(obj); // expected-note{{Calling constructor for 'smart_ptr<OSObject>'}} | |
| // expected-note@-1{{Returning from constructor for 'smart_ptr<OSObject>'}} | |
| // expected-note@-2 {{'p' initialized here}} | |
| // expected-note@os_smart_ptr.h:13{{Field 'pointer' is non-null}} | |
| // expected-note@os_smart_ptr.h:13{{Taking true branch}} | |
| // expected-note@os_smart_ptr.h:14{{Calling 'smart_ptr::_retain'}} | |
| // expected-note@os_smart_ptr.h:71{{Reference count incremented. The object now has a +2 retain count}} | |
| // expected-note@os_smart_ptr.h:14{{Returning from 'smart_ptr::_retain'}} | |
| } // expected-note{{Calling '~smart_ptr'}} | |
| // expected-note@os_smart_ptr.h:35{{Field 'pointer' is non-null}} | |
| // expected-note@os_smart_ptr.h:35{{Taking true branch}} | |
| // expected-note@os_smart_ptr.h:36{{Calling 'smart_ptr::_release'}} | |
| // expected-note@os_smart_ptr.h:76{{Reference count decremented. The object now has a +1 retain count}} | |
| // expected-note@os_smart_ptr.h:36{{Returning from 'smart_ptr::_release'}} | |
| // expected-note@-6{{Returning from '~smart_ptr'}} | |
| } // expected-warning{{Potential leak of an object stored into 'p'}} | |
| // expected-note@-1{{Object leaked: object allocated and stored into 'p' is not referenced later in this execution path and has a retain count of +1}} | |
| void test_smart_ptr_no_leak() { | |
| OSObject *obj = new OSObject; | |
| { | |
| OSObjectPtr p(obj); | |
| } | |
| obj->release(); | |
| } | |
| void test_osmetaclass_release() { | |
| const char *name = "no_name"; | |
| const OSMetaClass *meta = OSMetaClass::copyMetaClassWithName(name); | |
| if (!meta) { | |
| return; | |
| } else { | |
| meta->releaseMetaClass(); | |
| } | |
| } | |
| OSObject *getRuleViolation() { | |
| return new OSObject; // expected-warning{{Potential leak of an object of type 'OSObject'}} | |
| // expected-note@-1{{Operator 'new' returns an OSObject of type 'OSObject' with a +1 retain count}} | |
| // expected-note@-2{{Object leaked: allocated object of type 'OSObject' is returned from a function whose name ('getRuleViolation') starts with 'get'}} | |
| } | |
| OSObject *createRuleViolation(OSObject *param) { // expected-note{{Parameter 'param' starts at +0}} | |
| return param; // expected-warning{{Object with a +0 retain count returned to caller where a +1 (owning) retain count is expected}} | |
| // expected-note@-1{{Object with a +0 retain count returned to caller where a +1 (owning) retain count is expected}} | |
| } | |
| void test_ostypealloc_correct_diagnostic_name() { | |
| OSArray *arr = OSTypeAlloc(OSArray); // expected-note{{Call to method 'OSMetaClass::alloc' 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}} | |
| } // 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 escape_elsewhere(OSObject *obj); | |
| void test_free_on_escaped_object_diagnostics() { | |
| OSObject *obj = new OSObject; // expected-note{{Operator 'new' returns an OSObject of type 'OSObject' with a +1 retain count}} | |
| escape_elsewhere(obj); // expected-note{{Object is now not exclusively owned}} | |
| obj->free(); // expected-note{{'free' called on an object that may be referenced elsewhere}} | |
| // expected-warning@-1{{'free' called on an object that may be referenced elsewhere}} | |
| } | |
| void test_tagged_retain_no_leak() { | |
| OSObject *obj = new OSObject; | |
| obj->taggedRelease(); | |
| } | |
| void test_tagged_retain_no_uaf() { | |
| OSObject *obj = new OSObject; | |
| obj->taggedRetain(); | |
| obj->release(); | |
| obj->release(); | |
| } | |
| class IOService { | |
| public: | |
| OSObject *somethingMatching(OSObject *table = 0); | |
| }; | |
| OSObject *testSuppressionForMethodsEndingWithMatching(IOService *svc, | |
| OSObject *table = 0) { | |
| // This probably just passes table through. We should probably not make | |
| // ptr1 definitely equal to table, but we should not warn about leaks. | |
| OSObject *ptr1 = svc->somethingMatching(table); // no-warning | |
| // FIXME: This, however, should follow the Create Rule regardless. | |
| // We should warn about the leak here. | |
| OSObject *ptr2 = svc->somethingMatching(); // no-warning | |
| if (!table) | |
| table = OSTypeAlloc(OSArray); | |
| // This function itself ends with "Matching"! Do not warn when we're | |
| // returning from it at +0. | |
| return table; // no-warning | |
| } | |
| namespace weird_result { | |
| struct WeirdResult { | |
| int x, y, z; | |
| }; | |
| WeirdResult outParamWithWeirdResult(OS_RETURNS_RETAINED_ON_ZERO OSObject **obj); | |
| WeirdResult testOutParamWithWeirdResult() { | |
| OSObject *obj; | |
| return outParamWithWeirdResult(&obj); // no-warning | |
| } | |
| } // namespace weird_result | |
| namespace inherited_constructor_crash { | |
| struct a { | |
| a(int); | |
| }; | |
| struct b : a { | |
| // This is an "inherited constructor". | |
| using a::a; | |
| }; | |
| void test() { | |
| // RetainCountChecker used to crash when looking for a summary | |
| // for the inherited constructor invocation. | |
| b(0); | |
| } | |
| } // namespace inherited_constructor_crash |