767 lines
31 KiB
C++
767 lines
31 KiB
C++
|
// RUN: %clang_analyze_cc1 -std=c++14 -fblocks -analyze -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;
|
||
|
};
|
||
|
|
||
|
// These are never refcounted.
|
||
|
struct OSSymbol : OSObject {};
|
||
|
|
||
|
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 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
|
||
|
}
|
||
|
|
||
|
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@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 '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 test_smart_ptr_no_leak() {
|
||
|
OSObject *obj = new OSObject;
|
||
|
{
|
||
|
OSObjectPtr p(obj);
|
||
|
}
|
||
|
obj->release();
|
||
|
}
|
||
|
|
||
|
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
|
||
|
|
||
|
namespace ossymbol_suppression {
|
||
|
OSSymbol *createSymbol();
|
||
|
void test() {
|
||
|
OSSymbol *sym = createSymbol(); // no-warning
|
||
|
}
|
||
|
} // namespace ossymbol_suppression
|