// RUN: %clang_analyze_cc1 -analyzer-checker=core,osx.cocoa.SuperDealloc,debug.ExprInspection -analyzer-output=text -verify %s

void clang_analyzer_warnIfReached();

#define nil ((id)0)

typedef unsigned long NSUInteger;
@protocol NSObject
- (instancetype)retain;
- (oneway void)release;
@end

@interface NSObject <NSObject> { }
- (void)dealloc;
- (instancetype)init;
@end

typedef struct objc_selector *SEL;

//===------------------------------------------------------------------------===
//  <rdar://problem/6953275>
//  Check that 'self' is not referenced after calling '[super dealloc]'.

@interface SuperDeallocThenReleaseIvarClass : NSObject {
  NSObject *_ivar;
}
@end

@implementation SuperDeallocThenReleaseIvarClass
- (instancetype)initWithIvar:(NSObject *)ivar {
  self = [super init];
  if (!self)
    return nil;
  _ivar = [ivar retain];
  return self;
}
- (void)dealloc {
  [super dealloc]; // expected-note {{[super dealloc] called here}}
  [_ivar release]; // expected-warning {{Use of instance variable '_ivar' after 'self' has been deallocated}}
  // expected-note@-1 {{Use of instance variable '_ivar' after 'self' has been deallocated}}
}
@end

@interface SuperDeallocThenAssignNilToIvarClass : NSObject {
  NSObject *_delegate;
}
@end

@implementation SuperDeallocThenAssignNilToIvarClass
- (instancetype)initWithDelegate:(NSObject *)delegate {
  self = [super init];
  if (!self)
    return nil;
  _delegate = delegate;
  return self;
}
- (void)dealloc {
  [super dealloc]; // expected-note {{[super dealloc] called here}}
  _delegate = nil; // expected-warning {{Use of instance variable '_delegate' after 'self' has been deallocated}}
      // expected-note@-1 {{Use of instance variable '_delegate' after 'self' has been deallocated}}
}
@end


struct SomeStruct {
  int f;
};

@interface SuperDeallocThenAssignIvarField : NSObject {
  struct SomeStruct _s;
}
@end

@implementation SuperDeallocThenAssignIvarField
- (void)dealloc {
  [super dealloc]; // expected-note {{[super dealloc] called here}}
  _s.f = 7; // expected-warning {{Use of instance variable '_s' after 'self' has been deallocated}}
      // expected-note@-1 {{Use of instance variable '_s' after 'self' has been deallocated}}
}
@end

@interface OtherClassWithIvar {
@public
  int _otherIvar;
}
@end;

@interface SuperDeallocThenAssignIvarIvar : NSObject {
  OtherClassWithIvar *_ivar;
}
@end

@implementation SuperDeallocThenAssignIvarIvar
- (void)dealloc {
  [super dealloc]; // expected-note {{[super dealloc] called here}}
  _ivar->_otherIvar = 7; // expected-warning {{Use of instance variable '_ivar' after 'self' has been deallocated}}
      // expected-note@-1 {{Use of instance variable '_ivar' after 'self' has been deallocated}}
}
@end

@interface SuperDeallocThenAssignSelfIvar : NSObject {
  NSObject *_ivar;
}
@end

@implementation SuperDeallocThenAssignSelfIvar
- (void)dealloc {
  [super dealloc]; // expected-note {{[super dealloc] called here}}
  self->_ivar = nil; // expected-warning {{Use of instance variable '_ivar' after 'self' has been deallocated}}
      // expected-note@-1 {{Use of instance variable '_ivar' after 'self' has been deallocated}}
}
@end

@interface SuperDeallocThenReleasePropertyClass : NSObject { }
@property (retain) NSObject *ivar;
@end

@implementation SuperDeallocThenReleasePropertyClass
- (instancetype)initWithProperty:(NSObject *)ivar {
  self = [super init];
  if (!self)
    return nil;
  self.ivar = ivar;
  return self;
}
- (void)dealloc {
  [super dealloc]; // expected-note {{[super dealloc] called here}}
  self.ivar = nil; // expected-warning {{Use of 'self' after it has been deallocated}}
      // expected-note@-1 {{Use of 'self' after it has been deallocated}}
}
@end

@interface SuperDeallocThenAssignNilToPropertyClass : NSObject { }
@property (assign) NSObject *delegate;
@end

@implementation SuperDeallocThenAssignNilToPropertyClass
- (instancetype)initWithDelegate:(NSObject *)delegate {
  self = [super init];
  if (!self)
    return nil;
  self.delegate = delegate;
  return self;
}
- (void)dealloc {
  [super dealloc]; // expected-note {{[super dealloc] called here}}
  self.delegate = nil; // expected-warning {{Use of 'self' after it has been deallocated}}
      // expected-note@-1 {{Use of 'self' after it has been deallocated}}
}
@end

@interface SuperDeallocThenCallInstanceMethodClass : NSObject { }
- (void)_invalidate;
@end

@implementation SuperDeallocThenCallInstanceMethodClass
- (void)_invalidate {
}
- (void)dealloc {
  [super dealloc]; // expected-note {{[super dealloc] called here}}
  [self _invalidate]; // expected-warning {{Use of 'self' after it has been deallocated}}
      // expected-note@-1 {{Use of 'self' after it has been deallocated}}
}
@end

@interface SuperDeallocThenCallNonObjectiveCMethodClass : NSObject { }
@end

static void _invalidate(NSObject *object) {
  (void)object;
}

@implementation SuperDeallocThenCallNonObjectiveCMethodClass
- (void)dealloc {
  [super dealloc]; // expected-note {{[super dealloc] called here}}
  _invalidate(self); // expected-warning {{Use of 'self' after it has been deallocated}}
      // expected-note@-1 {{Use of 'self' after it has been deallocated}}
}
@end

@interface SuperDeallocThenCallObjectiveClassMethodClass : NSObject { }
@end

@implementation SuperDeallocThenCallObjectiveClassMethodClass
+ (void) invalidate:(id)arg; {
}

- (void)dealloc {
  [super dealloc]; // expected-note {{[super dealloc] called here}}
  [SuperDeallocThenCallObjectiveClassMethodClass invalidate:self]; // expected-warning {{Use of 'self' after it has been deallocated}}
      // expected-note@-1 {{Use of 'self' after it has been deallocated}}
}
@end

@interface TwoSuperDeallocCallsClass : NSObject {
  NSObject *_ivar;
}
- (void)_invalidate;
@end

@implementation TwoSuperDeallocCallsClass
- (void)_invalidate {
}
- (void)dealloc {
  if (_ivar) { // expected-note {{Assuming the condition is false}} expected-note {{Taking false branch}}
    [_ivar release];
    [super dealloc];
    return;
  }
  [super dealloc];    // expected-note {{[super dealloc] called here}}
  [self _invalidate]; // expected-warning {{Use of 'self' after it has been deallocated}}
      // expected-note@-1 {{Use of 'self' after it has been deallocated}}
}
@end

//===------------------------------------------------------------------------===
// Warn about calling [super dealloc] twice due to missing return statement.

@interface MissingReturnCausesDoubleSuperDeallocClass : NSObject {
  NSObject *_ivar;
}
@end

@implementation MissingReturnCausesDoubleSuperDeallocClass
- (void)dealloc {
  if (_ivar) { // expected-note {{Assuming the condition is true}} expected-note {{Taking true branch}}
    [_ivar release];
    [super dealloc]; // expected-note {{[super dealloc] called here}}
    // return;
  }
  [super dealloc]; // expected-warning{{[super dealloc] should not be called multiple times}}
  // expected-note@-1{{[super dealloc] should not be called multiple times}}
}
@end

//===------------------------------------------------------------------------===
// Warn about calling [super dealloc] twice in two different methods.

@interface SuperDeallocInOtherMethodClass : NSObject {
  NSObject *_ivar;
}
- (void)_cleanup;
@end

@implementation SuperDeallocInOtherMethodClass
- (void)_cleanup {
  [_ivar release];
  [super dealloc]; // expected-note {{[super dealloc] called here}}
}
- (void)dealloc {
  [self _cleanup]; // expected-note {{Calling '_cleanup'}}
  //expected-note@-1 {{Returning from '_cleanup'}}
  [super dealloc]; // expected-warning {{[super dealloc] should not be called multiple times}}
  // expected-note@-1 {{[super dealloc] should not be called multiple times}}
}
@end

//===------------------------------------------------------------------------===
// Do not warn about calling [super dealloc] recursively for different objects
// of the same type with custom retain counting.
//
// A class that contains an ivar of itself with custom retain counting (such
// as provided by _OBJC_SUPPORTED_INLINE_REFCNT_WITH_DEALLOC2MAIN) can generate
// a false positive that [super dealloc] is called twice if each object instance
// is not tracked separately by the checker. This test case is just a simple
// approximation to trigger the false positive.

@class ClassWithOwnIvarInstanceClass;
@interface ClassWithOwnIvarInstanceClass : NSObject {
  ClassWithOwnIvarInstanceClass *_ivar;
  NSUInteger _retainCount;
}
@end

@implementation ClassWithOwnIvarInstanceClass
- (instancetype)retain {
  ++_retainCount;
  return self;
}
- (oneway void)release {
  --_retainCount;
  if (!_retainCount)
    [self dealloc];
}
- (void)dealloc {
  [_ivar release];
  [super dealloc]; // no warning: different instances of same class
}
@end

//===------------------------------------------------------------------------===
// Do not warn about calling [super dealloc] twice if +dealloc is a class
// method.

@interface SuperDeallocClassMethodIgnoredClass : NSObject { }
+ (void)dealloc;
@end

@implementation SuperDeallocClassMethodIgnoredClass
+ (void)dealloc { }
@end

@interface SuperDeallocClassMethodIgnoredSubClass : NSObject { }
+ (void)dealloc;
@end

@implementation SuperDeallocClassMethodIgnoredSubClass
+ (void)dealloc {
  [super dealloc];
  [super dealloc]; // no warning: class method
}
@end

//===------------------------------------------------------------------------===
// Do not warn about calling [super dealloc] twice if when the analyzer has
// inlined the call to its super deallocator.

@interface SuperClassCallingSuperDealloc : NSObject {
  NSObject *_ivar;
}
@end

@implementation SuperClassCallingSuperDealloc
- (void)dealloc; {
  [_ivar release]; // no-warning

  [super dealloc];
}
@end

@interface SubclassCallingSuperDealloc : SuperClassCallingSuperDealloc
@end

@implementation SubclassCallingSuperDealloc
- (void)dealloc; {
  [super dealloc];
}
@end

//===------------------------------------------------------------------------===
// Treat calling [super dealloc] twice as as a sink.

@interface CallingSuperDeallocTwiceIsSink : NSObject
@end

@implementation CallingSuperDeallocTwiceIsSink
- (void)dealloc; {
  [super dealloc]; // expected-note {{[super dealloc] called here}}
  [super dealloc]; // expected-warning {{[super dealloc] should not be called multiple times}}
  // expected-note@-1 {{[super dealloc] should not be called multiple times}}

  clang_analyzer_warnIfReached(); // no-warning
}
@end


//===------------------------------------------------------------------------===
// Test path notes with intervening method call on self.

@interface InterveningMethodCallOnSelf : NSObject
@end

@implementation InterveningMethodCallOnSelf
- (void)anotherMethod {
}

- (void)dealloc; {
  [super dealloc]; // expected-note {{[super dealloc] called here}}
  [self anotherMethod]; // expected-warning {{Use of 'self' after it has been deallocated}}
      // expected-note@-1 {{Use of 'self' after it has been deallocated}}
  [super dealloc];
}
@end
