// UNSUPPORTED: target={{.*}}-zos{{.*}}, target={{.*}}-aix{{.*}}
// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UnretainedCallArgsChecker -verify %s

#include "objc-mock-types.h"

SomeObj *provide();
void consume_obj(SomeObj*);

CFMutableArrayRef provide_cf();
void consume_cf(CFMutableArrayRef);

void some_function();

namespace simple {
  void foo() {
    consume_obj(provide());
    // expected-warning@-1{{Call argument is unretained and unsafe}}
    consume_cf(provide_cf());
    // expected-warning@-1{{Call argument is unretained and unsafe}}
  }

  // Test that the checker works with [[clang::suppress]].
  void foo_suppressed() {
    [[clang::suppress]] consume_obj(provide()); // no-warning
    [[clang::suppress]] consume_cf(provide_cf()); // no-warning
  }

}

namespace multi_arg {
  void consume_retainable(int, SomeObj* foo, CFMutableArrayRef bar, bool);
  void foo() {
    consume_retainable(42, provide(), provide_cf(), true);
    // expected-warning@-1{{Call argument for parameter 'foo' is unretained and unsafe}}
    // expected-warning@-2{{Call argument for parameter 'bar' is unretained and unsafe}}
  }

  void consume_retainable(SomeObj* foo, ...);
  void bar() {
    consume_retainable(provide(), 1, provide_cf(), RetainPtr<CFMutableArrayRef> { provide_cf() }.get());
    // expected-warning@-1{{Call argument for parameter 'foo' is unretained and unsafe}}
    // expected-warning@-2{{Call argument is unretained and unsafe}}
     consume_retainable(RetainPtr<SomeObj> { provide() }.get(), 1, RetainPtr<CFMutableArrayRef> { provide_cf() }.get());
  }
}

namespace retained {
  RetainPtr<SomeObj> provide_obj() { return RetainPtr<SomeObj>{}; }
  void consume_obj(RetainPtr<SomeObj>) {}

  RetainPtr<CFMutableArrayRef> provide_cf() { return CFMutableArrayRef{}; }
  void consume_cf(RetainPtr<CFMutableArrayRef>) {}

  void foo() {
    consume_obj(provide_obj().get()); // no warning
    consume_cf(provide_cf().get()); // no warning
  }
}

namespace methods {
  struct Consumer {
    void consume_obj(SomeObj* ptr);
    void consume_cf(CFMutableArrayRef ref);
  };

  void foo() {
    Consumer c;

    c.consume_obj(provide());
    // expected-warning@-1{{Call argument for parameter 'ptr' is unretained and unsafe}}
    c.consume_cf(provide_cf());
    // expected-warning@-1{{Call argument for parameter 'ref' is unretained and unsafe}}
  }

  void foo2() {
    struct Consumer {
      void consume(SomeObj*) { some_function(); }
      void whatever() {
        consume(provide());
        // expected-warning@-1{{Call argument is unretained and unsafe}}
      }

      void consume_cf(CFMutableArrayRef) { some_function(); }
      void something() {
        consume_cf(provide_cf());
        // expected-warning@-1{{Call argument is unretained and unsafe}}
      }
    };
  }

  void foo3() {
    struct Consumer {
      void consume(SomeObj*) { some_function(); }
      void whatever() {
        this->consume(provide());
        // expected-warning@-1{{Call argument is unretained and unsafe}}
      }

      void consume_cf(CFMutableArrayRef) { some_function(); }
      void something() {
        this->consume_cf(provide_cf());
        // expected-warning@-1{{Call argument is unretained and unsafe}}
      }
    };
  }

}

namespace casts {
  void foo() {
    consume_obj(provide());
    // expected-warning@-1{{Call argument is unretained and unsafe}}

    consume_obj(static_cast<OtherObj*>(provide()));
    // expected-warning@-1{{Call argument is unretained and unsafe}}

    consume_obj(reinterpret_cast<OtherObj*>(provide()));
    // expected-warning@-1{{Call argument is unretained and unsafe}}

    consume_obj(downcast<OtherObj>(provide()));
    // expected-warning@-1{{Call argument is unretained and unsafe}}
  }
}

namespace null_ptr {
  void foo_ref() {
    consume_obj(nullptr);
    consume_obj(0);
    consume_cf(nullptr);
    consume_cf(0);
  }
}

namespace retain_ptr_lookalike {
  struct Decoy {
    SomeObj* get();
  };

  void foo() {
    Decoy D;

    consume_obj(D.get());
    // expected-warning@-1{{Call argument is unretained and unsafe}}
  }

  struct Decoy2 {
    CFMutableArrayRef get();
  };

  void bar() {
    Decoy2 D;

    consume_cf(D.get());
    // expected-warning@-1{{Call argument is unretained and unsafe}}
  }
}

namespace param_formarding_function {
  void consume_more_obj(OtherObj*);
  void consume_more_cf(CFMutableArrayRef);

  namespace objc {
    void foo(SomeObj* param) {
      consume_more_obj(downcast<OtherObj>(param));
    }
  }

  namespace cf {
    void foo(CFMutableArrayRef param) {
      consume_more_cf(param);
    }
  }
}

namespace param_formarding_lambda {
  auto consume_more_obj = [](OtherObj*) { some_function(); };
  auto consume_more_cf = [](CFMutableArrayRef) { some_function(); };

  namespace objc {
    void foo(SomeObj* param) {
      consume_more_obj(downcast<OtherObj>(param));
    }
  }

  namespace cf {
    void foo(CFMutableArrayRef param) {
      consume_more_cf(param);
    }
  }
}

namespace param_forwarding_method {
  struct Consumer {
    void consume_obj(SomeObj*);
    static void consume_obj_s(SomeObj*);
    void consume_cf(CFMutableArrayRef);
    static void consume_cf_s(CFMutableArrayRef);
  };

  void bar(Consumer* consumer, SomeObj* param) {
    consumer->consume_obj(param);
  }

  void foo(SomeObj* param) {
    Consumer::consume_obj_s(param);
  }

  void baz(Consumer* consumer, CFMutableArrayRef param) {
    consumer->consume_cf(param);
    Consumer::consume_cf_s(param);
  }
}


namespace default_arg {
  SomeObj* global;
  CFMutableArrayRef global_cf;

  void function_with_default_arg1(SomeObj* param = global);
  // expected-warning@-1{{Call argument for parameter 'param' is unretained and unsafe}}

  void function_with_default_arg2(CFMutableArrayRef param = global_cf);
  // expected-warning@-1{{Call argument for parameter 'param' is unretained and unsafe}}

  void foo() {
    function_with_default_arg1();
    function_with_default_arg2();
  }
}

namespace cxx_member_func {
  RetainPtr<SomeObj> protectedProvide();
  RetainPtr<CFMutableArrayRef> protectedProvideCF();

  void foo() {
    [provide() doWork];
    // expected-warning@-1{{Reciever is unretained and unsafe}}
    [protectedProvide().get() doWork];

    CFArrayAppendValue(provide_cf(), nullptr);
    // expected-warning@-1{{Call argument for parameter 'theArray' is unretained and unsafe}}
    CFArrayAppendValue(protectedProvideCF(), nullptr);
  };

  void bar() {
    [downcast<OtherObj>(protectedProvide().get()) doMoreWork:downcast<OtherObj>(provide())];
    // expected-warning@-1{{Call argument for parameter 'other' is unretained and unsafe}}
    [protectedProvide().get() doWork];
  };

}

namespace cxx_member_operator_call {
  // The hidden this-pointer argument without a corresponding parameter caused couple bugs in parameter <-> argument attribution.
  struct Foo {
    Foo& operator+(SomeObj* bad);
    friend Foo& operator-(Foo& lhs, SomeObj* bad);
    void operator()(SomeObj* bad);
  };

  SomeObj* global;

  void foo() {
    Foo f;
    f + global;
    // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}}
    f - global;
    // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}}
    f(global);
    // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}}
  }
}

namespace call_with_ptr_on_ref {
  RetainPtr<SomeObj> provideProtected();
  RetainPtr<CFMutableArrayRef> provideProtectedCF();
  void bar(SomeObj* bad);
  void bar_cf(CFMutableArrayRef bad);
  bool baz();
  void foo(bool v) {
    bar(v ? nullptr : provideProtected().get());
    bar(baz() ? provideProtected().get() : nullptr);
    bar(v ? provide() : provideProtected().get());
    // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}}
    bar(v ? provideProtected().get() : provide());
    // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}}

    bar_cf(v ? nullptr : provideProtectedCF().get());
    bar_cf(baz() ? provideProtectedCF().get() : nullptr);
    bar_cf(v ? provide_cf() : provideProtectedCF().get());
    // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}}
    bar_cf(v ? provideProtectedCF().get() : provide_cf());
    // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}}
  }
}

namespace call_with_explicit_temporary_obj {
  void foo() {
    [RetainPtr<SomeObj>(provide()).get() doWork];
    CFArrayAppendValue(RetainPtr<CFMutableArrayRef> { provide_cf() }.get(), nullptr);
  }
  template <typename T>
  void bar() {
    [RetainPtr<SomeObj>(provide()).get() doWork];
    CFArrayAppendValue(RetainPtr<CFMutableArrayRef> { provide_cf() }.get(), nullptr);
  }
  void baz() {
    bar<int>();
  }
}

namespace call_with_adopt_ref {
  void foo() {
    [adoptNS(provide()).get() doWork];
    CFArrayAppendValue(adoptCF(provide_cf()).get(), nullptr);
  }
}

namespace call_with_cf_constant {
  void bar(const NSArray *);
  void baz(const NSDictionary *);
  void foo() {
    CFArrayCreateMutable(kCFAllocatorDefault, 10);
    bar(@[@"hello"]);
    baz(@{@"hello": @3});
  }
}

namespace call_with_cf_string {
  void bar(CFStringRef);
  void foo() {
    bar(CFSTR("hello"));
  }
}

namespace call_with_ns_string {
  void bar(NSString *);
  void foo() {
    bar(@"world");
  }
}

namespace bridge_cast_arg {
  void bar(NSString *);
  void baz(NSString *);
  extern const CFStringRef kCFBundleNameKey;

  NSObject *foo(CFStringRef arg) {
    bar((NSString *)bridge_cast((CFTypeRef)arg));
    auto dict = @{
      @"hello": @1,
    };
    return dict[(__bridge NSString *)kCFBundleNameKey];
  }
}

namespace alloc_init_pair {
  void foo() {
    auto obj = adoptNS([[SomeObj alloc] init]);
    [obj doWork];
  }
}

namespace alloc_class {
  bool foo(NSObject *obj) {
    return [obj isKindOfClass:SomeObj.class] && [obj isKindOfClass:NSClassFromString(@"SomeObj")];
  }

  bool bar(NSObject *obj) {
    return [obj isKindOfClass:[SomeObj class]];
  }

  bool baz(NSObject *obj) {
    return [obj isKindOfClass:[SomeObj superclass]];
  }
}

namespace ptr_conversion {

SomeObj *provide_obj();

void dobjc(SomeObj* obj) {
  [dynamic_objc_cast<OtherObj>(obj) doMoreWork:nil];
}

void cobjc(SomeObj* obj) {
  [checked_objc_cast<OtherObj>(obj) doMoreWork:nil];
}

unsigned dcf(CFTypeRef obj) {
  return CFArrayGetCount(dynamic_cf_cast<CFArrayRef>(obj));
}

unsigned ccf(CFTypeRef obj) {
  return CFArrayGetCount(checked_cf_cast<CFArrayRef>(obj));
}

void some_function(id);
void idcf(CFTypeRef obj) {
  some_function(bridge_id_cast(obj));
}

} // ptr_conversion

@interface TestObject : NSObject
- (void)doWork:(NSString *)msg, ...;
- (void)doWorkOnSelf;
@end

@implementation TestObject

- (void)doWork:(NSString *)msg, ... {
  some_function();
}

- (void)doWorkOnSelf {
  [self doWork:nil];
  [self doWork:@"hello", provide(), provide_cf()];
  // expected-warning@-1{{Call argument is unretained and unsafe}}
  // expected-warning@-2{{Call argument is unretained and unsafe}}
  [self doWork:@"hello", RetainPtr<SomeObj> { provide() }.get(), RetainPtr<CFMutableArrayRef> { provide_cf() }.get()];
}

@end
