// RUN: %clang_analyze_cc1 -analyze -analyzer-checker=core,osx.cocoa.RetainCount -analyzer-config osx.cocoa.RetainCount:CheckOSObject=true -analyzer-output=text -verify %s

struct OSMetaClass;

#define TRUSTED __attribute__((annotate("rc_ownership_trusted_implementation")))
#define OS_CONSUME TRUSTED __attribute__((annotate("rc_ownership_consumed")))
#define OS_RETURNS_RETAINED TRUSTED __attribute__((annotate("rc_ownership_returns_retained")))
#define OS_RETURNS_NOT_RETAINED TRUSTED __attribute__((annotate("rc_ownership_returns_not_retained")))

#define OSTypeID(type)   (type::metaClass)

#define OSDynamicCast(type, inst)   \
    ((type *) OSMetaClassBase::safeMetaCast((inst), OSTypeID(type)))

struct OSObject {
  virtual void retain();
  virtual void release() {};
  virtual ~OSObject(){}

  unsigned int foo() { return 42; }

  static OSObject *generateObject(int);

  static const OSMetaClass * const metaClass;
};

struct OSArray : public OSObject {
  unsigned int getCount();

  static OSArray *withCapacity(unsigned int capacity);
  static void consumeArray(OS_CONSUME OSArray * array);

  static OSArray* consumeArrayHasCode(OS_CONSUME OSArray * array) {
    return nullptr;
  }

  static OS_RETURNS_NOT_RETAINED OSArray *MaskedGetter();
  static OS_RETURNS_RETAINED OSArray *getOoopsActuallyCreate();


  static const OSMetaClass * const metaClass;
};

struct OtherStruct {
  static void doNothingToArray(OSArray *array);
  OtherStruct(OSArray *arr);
};

struct OSMetaClassBase {
  static OSObject *safeMetaCast(const OSObject *inst, const OSMetaClass *meta);
};

void check_no_invalidation() {
  OSArray *arr = OSArray::withCapacity(10); // expected-note{{Call to function 'withCapacity' returns an OSObject of type struct 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 function 'withCapacity' returns an OSObject of type struct OSArray * with a +1 retain count}}
  OtherStruct other(arr); // expected-warning{{Potential leak}}
                          // expected-note@-1{{Object leaked}}
}

void check_rc_consumed() {
  OSArray *arr = OSArray::withCapacity(10);
  OSArray::consumeArray(arr);
}

void check_rc_consume_temporary() {
  OSArray::consumeArray(OSArray::withCapacity(10));
}

void check_rc_getter() {
  OSArray *arr = OSArray::MaskedGetter();
  (void)arr;
}

void check_rc_create() {
  OSArray *arr = OSArray::getOoopsActuallyCreate();
  arr->release();
}


void check_dynamic_cast() {
  OSArray *arr = OSDynamicCast(OSArray, OSObject::generateObject(1));
  arr->release();
}

unsigned int check_dynamic_cast_no_null_on_orig(OSObject *obj) {
  OSArray *arr = OSDynamicCast(OSArray, obj);
  if (arr) {
    return arr->getCount();
  } else {

    // The fact that dynamic cast has failed should not imply that
    // the input object was null.
    return obj->foo(); // no-warning
  }
}

void check_dynamic_cast_null_branch(OSObject *obj) {
  OSArray *arr1 = OSArray::withCapacity(10); // expected-note{{Call to function 'withCapacity' returns an OSObject}}
  OSArray *arr = OSDynamicCast(OSArray, obj);
  if (!arr) // expected-note{{Taking true branch}}
    return; // expected-warning{{Potential leak}}
            // expected-note@-1{{Object leaked}}
  arr1->release();
}

void check_dynamic_cast_null_check() {
  OSArray *arr = OSDynamicCast(OSArray, OSObject::generateObject(1)); // expected-note{{Call to function 'generateObject' returns an OSObject}}
    // expected-warning@-1{{Potential leak of an object}}
    // expected-note@-2{{Object leaked}}
  if (!arr)
    return;
  arr->release();
}

void use_after_release() {
  OSArray *arr = OSArray::withCapacity(10); // expected-note{{Call to function 'withCapacity' returns an OSObject of type struct 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 function 'withCapacity' returns an OSObject of type struct 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
}

struct ArrayOwner {
  OSArray *arr;

  OSArray *getArray() {
    return arr;
  }

  OSArray *createArray() {
    return OSArray::withCapacity(10);
  }

  OSArray *createArraySourceUnknown();

  OSArray *getArraySourceUnknown();
};

unsigned int no_warning_on_getter(ArrayOwner *owner) {
  OSArray *arr = owner->getArray();
  return arr->getCount();
}

unsigned int warn_on_overrelease(ArrayOwner *owner) {
  OSArray *arr = owner->getArray(); // expected-note{{function call returns an OSObject of type struct 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 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{{function call returns an OSObject of type struct 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();
}
