// 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
