// RUN: %clang_cc1 -analyze -analyzer-checker=core,alpha.osx.cocoa.InstanceVariableInvalidation -DRUN_IVAR_INVALIDATION -verify %s
// RUN: %clang_cc1 -analyze -analyzer-checker=core,alpha.osx.cocoa.MissingInvalidationMethod -DRUN_MISSING_INVALIDATION_METHOD -verify %s
extern void __assert_fail (__const char *__assertion, __const char *__file,
    unsigned int __line, __const char *__function)
     __attribute__ ((__noreturn__));

#define assert(expr) \
  ((expr)  ? (void)(0)  : __assert_fail (#expr, __FILE__, __LINE__, __func__))

@protocol NSObject
@end
@interface NSObject <NSObject> {}
+(id)alloc;
+(id)new;
-(id)init;
-(id)autorelease;
-(id)copy;
- (Class)class;
-(id)retain;
-(id)description;
@end
@class NSString;

extern void NSLog(NSString *format, ...) __attribute__((format(__NSString__, 1, 2)));

@protocol Invalidation1 <NSObject> 
- (void) invalidate __attribute__((annotate("objc_instance_variable_invalidator")));
@end 

@protocol Invalidation2 <NSObject> 
- (void) invalidate __attribute__((annotate("objc_instance_variable_invalidator")));
@end 

@protocol Invalidation3 <NSObject>
- (void) invalidate __attribute__((annotate("objc_instance_variable_invalidator")));
- (void) invalidate2 __attribute__((annotate("objc_instance_variable_invalidator")));
@end

@protocol Invalidation3;
@protocol Invalidation2;

@interface Invalidation2Class <Invalidation2>
@end

@interface Invalidation1Class <Invalidation1>
@end

@interface ClassWithInvalidationMethodInCategory <NSObject>
@end

@interface ClassWithInvalidationMethodInCategory ()
- (void) invalidate __attribute__((annotate("objc_instance_variable_invalidator")));
@end

@interface SomeInvalidationImplementingObject: NSObject <Invalidation3, Invalidation2> {
  SomeInvalidationImplementingObject *ObjA; // invalidation in the parent
}
@end

@implementation SomeInvalidationImplementingObject
- (void)invalidate{
  ObjA = 0;
}
- (void)invalidate2 {
  [self invalidate];
}
@end

@interface SomeSubclassInvalidatableObject : SomeInvalidationImplementingObject {
  SomeInvalidationImplementingObject *Ivar1; // regular ivar
  SomeInvalidationImplementingObject *Ivar2; // regular ivar, sending invalidate message
  SomeInvalidationImplementingObject *_Ivar3; // no property, call -description
  SomeInvalidationImplementingObject *_Ivar4; // no property, provide as argument to NSLog()

  SomeInvalidationImplementingObject *_Prop1; // partially implemented property, set to 0 with dot syntax
  SomeInvalidationImplementingObject *_Prop2; // fully implemented prop, set to 0 with dot syntax
  SomeInvalidationImplementingObject *_propIvar; // property with custom named ivar, set to 0 via setter
  Invalidation1Class *MultipleProtocols; // regular ivar belonging to a different class
  Invalidation2Class *MultInheritance; // regular ivar belonging to a different class
  SomeInvalidationImplementingObject *_Prop3; // property, invalidate via sending a message to a getter method
  SomeInvalidationImplementingObject *_Prop4; // property with @synthesize, invalidate via property
  SomeInvalidationImplementingObject *_Prop5; // property with @synthesize, invalidate via getter method
  SomeInvalidationImplementingObject *_Prop8;
  
  // Ivars invalidated by the partial invalidator. 
  SomeInvalidationImplementingObject *Ivar9;
  SomeInvalidationImplementingObject *_Prop10;
  SomeInvalidationImplementingObject *Ivar11;

  // No warnings on these as they are not invalidatable.
  NSObject *NIvar1;
  NSObject *NObj2;
  NSObject *_NProp1;
  NSObject *_NpropIvar;
}

@property (assign) SomeInvalidationImplementingObject* Prop0;
@property (nonatomic, assign) SomeInvalidationImplementingObject* Prop1;
@property (assign) SomeInvalidationImplementingObject* Prop2;
@property (assign) SomeInvalidationImplementingObject* Prop3;
@property (assign) SomeInvalidationImplementingObject *Prop5;
@property (assign) SomeInvalidationImplementingObject *Prop4;

@property (assign) SomeInvalidationImplementingObject* Prop6; // automatically synthesized prop
@property (assign) SomeInvalidationImplementingObject* Prop7; // automatically synthesized prop
@property (assign) SomeInvalidationImplementingObject *SynthIvarProp;

@property (assign) NSObject* NProp0;
@property (nonatomic, assign) NSObject* NProp1;
@property (assign) NSObject* NProp2;

-(void)setProp1: (SomeInvalidationImplementingObject*) InO;
-(void)setNProp1: (NSObject*) InO;

-(void)invalidate;

// Partial invalidators invalidate only some ivars. They are guaranteed to be 
// called before the invalidation methods.
-(void)partialInvalidator1 __attribute__((annotate("objc_instance_variable_invalidator_partial")));
-(void)partialInvalidator2 __attribute__((annotate("objc_instance_variable_invalidator_partial")));
@end

@interface SomeSubclassInvalidatableObject()
@property (assign) SomeInvalidationImplementingObject* Prop8;
@property (assign) SomeInvalidationImplementingObject* Prop10;
@end

@implementation SomeSubclassInvalidatableObject{
  @private
  SomeInvalidationImplementingObject *Ivar5;
  ClassWithInvalidationMethodInCategory *Ivar13;
}

@synthesize Prop7 = _propIvar;
@synthesize Prop3 = _Prop3;
@synthesize Prop5 = _Prop5;
@synthesize Prop4 = _Prop4;
@synthesize Prop8 = _Prop8;
@synthesize Prop10 = _Prop10;


- (void) setProp1: (SomeInvalidationImplementingObject*) InObj {
  _Prop1 = InObj;
}

- (void) setProp2: (SomeInvalidationImplementingObject*) InObj {
  _Prop2 = InObj;
}
- (SomeInvalidationImplementingObject*) Prop2 {
  return _Prop2;
}

@synthesize NProp2 = _NpropIvar;

- (void) setNProp1: (NSObject*) InObj {
  _NProp1 = InObj;
}

- (void) invalidate {
   [Ivar2 invalidate];
   self.Prop0 = 0;
   self.Prop1 = 0;
   [self setProp2:0];
   [self setProp3:0];
   [[self Prop5] invalidate2];
   [self.Prop4 invalidate];
   [self.Prop8 invalidate];
   self.Prop6 = 0;
   [[self Prop7] invalidate];

   [_Ivar3 description]; 
   NSLog(@"%@", _Ivar4);
   [super invalidate];
}
#if RUN_IVAR_INVALIDATION
// expected-warning@-2 {{Instance variable Ivar1 needs to be invalidated}}
// expected-warning@-3 {{Instance variable MultipleProtocols needs to be invalidated}}
// expected-warning@-4 {{Instance variable MultInheritance needs to be invalidated}}
// expected-warning@-5 {{Property SynthIvarProp needs to be invalidated or set to nil}}
// expected-warning@-6 {{Instance variable _Ivar3 needs to be invalidated}}
// expected-warning@-7 {{Instance variable _Ivar4 needs to be invalidated}}
// expected-warning@-8 {{Instance variable Ivar5 needs to be invalidated or set to nil}}
// expected-warning@-9 {{Instance variable Ivar13 needs to be invalidated or set to nil}}
#endif

-(void)partialInvalidator1 {
  [Ivar9 invalidate];
  [_Prop10 invalidate];
}

-(void)partialInvalidator2 {
  [Ivar11 invalidate];
}

@end

// Example, where the same property is inherited through 
// the parent and directly through a protocol. If a property backing ivar is 
// synthesized in the parent, let the parent invalidate it.

@protocol IDEBuildable <NSObject>
@property (readonly, strong) id <Invalidation2> ObjB;
@end

@interface Parent : NSObject <IDEBuildable, Invalidation2> {
  Invalidation2Class *_ObjB; // Invalidation of ObjB happens in the parent.
}
@end

@interface Child: Parent <Invalidation2, IDEBuildable> 
@end

@implementation Parent{
  @private
  Invalidation2Class *Ivar10;
  Invalidation2Class *Ivar11;
  Invalidation2Class *Ivar12;
}

@synthesize ObjB = _ObjB;
- (void)invalidate{
  _ObjB = ((void*)0);
  
  assert(Ivar10 == 0);

  if (__builtin_expect(!(Ivar11 == ((void*)0)), 0))
    assert(0);

  assert(0 == Ivar12);

}
@end

@implementation Child
- (void)invalidate{ 
  // no-warning
} 
@end

@protocol Invalidation <NSObject>
- (void)invalidate __attribute__((annotate("objc_instance_variable_invalidator")));
@end

@interface Foo : NSObject <Invalidation>
@end

@class FooBar;
@protocol FooBar_Protocol <NSObject>
@end

@interface MissingInvalidationMethod : Foo <FooBar_Protocol>
@property (assign) MissingInvalidationMethod *foobar15_warn;
#if RUN_IVAR_INVALIDATION
// expected-warning@-2 {{Property foobar15_warn needs to be invalidated; no invalidation method is defined in the @implementation for MissingInvalidationMethod}}
#endif
@end
@implementation MissingInvalidationMethod
@end

@interface MissingInvalidationMethod2 : Foo <FooBar_Protocol> {
  Foo *Ivar1;
#if RUN_IVAR_INVALIDATION
// expected-warning@-2 {{Instance variable Ivar1 needs to be invalidated; no invalidation method is defined in the @implementation for MissingInvalidationMethod2}}
#endif
}
@end
@implementation MissingInvalidationMethod2
@end

@interface MissingInvalidationMethodDecl : NSObject {
  Foo *Ivar1;
#if RUN_MISSING_INVALIDATION_METHOD
// expected-warning@-2 {{Instance variable Ivar1 needs to be invalidated; no invalidation method is declared for MissingInvalidationMethodDecl}}
#endif
}
@end
@implementation MissingInvalidationMethodDecl
@end

@interface MissingInvalidationMethodDecl2 : NSObject {
@private
    Foo *_foo1;
#if RUN_MISSING_INVALIDATION_METHOD
// expected-warning@-2 {{Instance variable _foo1 needs to be invalidated; no invalidation method is declared for MissingInvalidationMethodDecl2}}
#endif
}
@property (strong) Foo *bar1; 
@end
@implementation MissingInvalidationMethodDecl2
@end

@interface InvalidatedInPartial : SomeInvalidationImplementingObject {
  SomeInvalidationImplementingObject *Ivar1; 
  SomeInvalidationImplementingObject *Ivar2; 
}
-(void)partialInvalidator __attribute__((annotate("objc_instance_variable_invalidator_partial")));
@end
@implementation InvalidatedInPartial
-(void)partialInvalidator {
  [Ivar1 invalidate];
  Ivar2 = 0;
}
@end

@interface NotInvalidatedInPartial : SomeInvalidationImplementingObject {
  SomeInvalidationImplementingObject *Ivar1; 
}
-(void)partialInvalidator __attribute__((annotate("objc_instance_variable_invalidator_partial")));
-(void)partialInvalidatorCallsPartial __attribute__((annotate("objc_instance_variable_invalidator_partial")));
@end
@implementation NotInvalidatedInPartial
-(void)partialInvalidator {
}
-(void)partialInvalidatorCallsPartial {
  [self partialInvalidator];
}

-(void)invalidate {
} 
#if RUN_IVAR_INVALIDATION
// expected-warning@-2 {{Instance variable Ivar1 needs to be invalidated or set to nil}}
#endif
@end

@interface SomeNotInvalidatedInPartial : SomeInvalidationImplementingObject {
  SomeInvalidationImplementingObject *Ivar1;
  SomeInvalidationImplementingObject *Ivar2;
#if RUN_IVAR_INVALIDATION
  // expected-warning@-2 {{Instance variable Ivar2 needs to be invalidated or set to nil}}
#endif
}
-(void)partialInvalidator __attribute__((annotate("objc_instance_variable_invalidator_partial")));
-(void)partialInvalidatorCallsPartial __attribute__((annotate("objc_instance_variable_invalidator_partial")));
@end
@implementation SomeNotInvalidatedInPartial {
  SomeInvalidationImplementingObject *Ivar3;
#if RUN_IVAR_INVALIDATION
  // expected-warning@-2 {{Instance variable Ivar3 needs to be invalidated or set to nil}}
#endif
}
-(void)partialInvalidator {
  Ivar1 = 0;
}
-(void)partialInvalidatorCallsPartial {
  [self partialInvalidator];
}
@end

@interface OnlyPartialDeclsBase : NSObject
-(void)partialInvalidator __attribute__((annotate("objc_instance_variable_invalidator_partial")));
@end
@implementation OnlyPartialDeclsBase
-(void)partialInvalidator {}
@end

@interface OnlyPartialDecls : OnlyPartialDeclsBase {
  SomeInvalidationImplementingObject *Ivar1;
#if RUN_IVAR_INVALIDATION
  // expected-warning@-2 {{Instance variable Ivar1 needs to be invalidated; no invalidation method is defined in the @implementation for OnlyPartialDecls}}
#endif
}
@end
@implementation OnlyPartialDecls
@end

// False negative.
@interface PartialCallsFull : SomeInvalidationImplementingObject {
  SomeInvalidationImplementingObject *Ivar1;
}
-(void)partialInvalidator __attribute__((annotate("objc_instance_variable_invalidator_partial")));
@end
@implementation PartialCallsFull
-(void)partialInvalidator {
 [self invalidate];
} // TODO: It would be nice to check that the full invalidation method actually invalidates the ivar. 
@end