// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core\
// RUN:                    -analyzer-checker=debug.ExprInspection -verify\
// RUN:                    -x c %s
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core\
// RUN:                    -analyzer-checker=debug.ExprInspection -verify\
// RUN:                    -x c++ -std=c++14 %s
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core\
// RUN:                    -analyzer-checker=debug.ExprInspection -verify\
// RUN:                    -x c++ -std=c++17 %s
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core\
// RUN:                    -analyzer-checker=debug.ExprInspection -verify\
// RUN:                    -DINLINE -x c %s
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core\
// RUN:                    -analyzer-checker=debug.ExprInspection -verify\
// RUN:                    -DINLINE -x c++ -std=c++14 %s
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core\
// RUN:                    -analyzer-checker=debug.ExprInspection -verify\
// RUN:                    -DINLINE -x c++ -std=c++17 %s

void clang_analyzer_eval(int);

struct S {
  int field;

#if __cplusplus
  const struct S *getThis() const { return this; }
  const struct S *operator +() const { return this; }

  bool check() const { return this == this; }
  bool operator !() const { return this != this; }

  int operator *() const { return field; }
#endif
};

#if __cplusplus
const struct S *operator -(const struct S &s) { return &s; }
bool operator ~(const struct S &s) { return (&s) != &s; }
#endif


#ifdef INLINE
struct S getS() {
  struct S s = { 42 };
  return s;
}
#else
struct S getS();
#endif


void testAssignment() {
  struct S s = getS();

  if (s.field != 42) return;
  clang_analyzer_eval(s.field == 42); // expected-warning{{TRUE}}

  s.field = 0;
  clang_analyzer_eval(s.field == 0); // expected-warning{{TRUE}}

#if __cplusplus
  clang_analyzer_eval(s.getThis() == &s); // expected-warning{{TRUE}}
  clang_analyzer_eval(+s == &s); // expected-warning{{TRUE}}
  clang_analyzer_eval(-s == &s); // expected-warning{{TRUE}}

  clang_analyzer_eval(s.check()); // expected-warning{{TRUE}}
  clang_analyzer_eval(!s); // expected-warning{{FALSE}}
  clang_analyzer_eval(~s); // expected-warning{{FALSE}}

  clang_analyzer_eval(*s == 0); // expected-warning{{TRUE}}
#endif
}


void testImmediateUse() {
  int x = getS().field;

  if (x != 42) return;
  clang_analyzer_eval(x == 42); // expected-warning{{TRUE}}

#if __cplusplus
  clang_analyzer_eval((void *)getS().getThis() == (void *)&x); // expected-warning{{FALSE}}
  clang_analyzer_eval((void *)+getS() == (void *)&x); // expected-warning{{FALSE}}
  clang_analyzer_eval((void *)-getS() == (void *)&x); // expected-warning{{FALSE}}

  clang_analyzer_eval(getS().check()); // expected-warning{{TRUE}}
  clang_analyzer_eval(!getS()); // expected-warning{{FALSE}}
  clang_analyzer_eval(~getS()); // expected-warning{{FALSE}}
#endif
}

int getConstrainedField(struct S s) {
  if (s.field != 42) return 42;
  return s.field;
}

int getAssignedField(struct S s) {
  s.field = 42;
  return s.field;
}

void testArgument() {
  clang_analyzer_eval(getConstrainedField(getS()) == 42); // expected-warning{{TRUE}}
  clang_analyzer_eval(getAssignedField(getS()) == 42); // expected-warning{{TRUE}}
}

void testImmediateUseParens() {
  int x = ((getS())).field;

  if (x != 42) return;
  clang_analyzer_eval(x == 42); // expected-warning{{TRUE}}

  clang_analyzer_eval(getConstrainedField(((getS()))) == 42); // expected-warning{{TRUE}}
  clang_analyzer_eval(getAssignedField(((getS()))) == 42); // expected-warning{{TRUE}}

#if __cplusplus
  clang_analyzer_eval(((getS())).check()); // expected-warning{{TRUE}}
  clang_analyzer_eval(!((getS()))); // expected-warning{{FALSE}}
  clang_analyzer_eval(~((getS()))); // expected-warning{{FALSE}}
#endif
}


//--------------------
// C++-only tests
//--------------------

#if __cplusplus
void testReferenceAssignment() {
  const S &s = getS();

  if (s.field != 42) return;
  clang_analyzer_eval(s.field == 42); // expected-warning{{TRUE}}

  clang_analyzer_eval(s.getThis() == &s); // expected-warning{{TRUE}}
  clang_analyzer_eval(+s == &s); // expected-warning{{TRUE}}

  clang_analyzer_eval(s.check()); // expected-warning{{TRUE}}
  clang_analyzer_eval(!s); // expected-warning{{FALSE}}
  clang_analyzer_eval(~s); // expected-warning{{FALSE}}

  clang_analyzer_eval(*s == 42); // expected-warning{{TRUE}}
}


int getConstrainedFieldRef(const S &s) {
  if (s.field != 42) return 42;
  return s.field;
}

bool checkThis(const S &s) {
  return s.getThis() == &s;
}

bool checkThisOp(const S &s) {
  return +s == &s;
}

bool checkThisStaticOp(const S &s) {
  return -s == &s;
}

void testReferenceArgument() {
  clang_analyzer_eval(getConstrainedFieldRef(getS()) == 42); // expected-warning{{TRUE}}
  clang_analyzer_eval(checkThis(getS())); // expected-warning{{TRUE}}
  clang_analyzer_eval(checkThisOp(getS())); // expected-warning{{TRUE}}
  clang_analyzer_eval(checkThisStaticOp(getS())); // expected-warning{{TRUE}}
}


int getConstrainedFieldOp(S s) {
  if (*s != 42) return 42;
  return *s;
}

int getConstrainedFieldRefOp(const S &s) {
  if (*s != 42) return 42;
  return *s;
}

void testImmediateUseOp() {
  int x = *getS();
  if (x != 42) return;
  clang_analyzer_eval(x == 42); // expected-warning{{TRUE}}

  clang_analyzer_eval(getConstrainedFieldOp(getS()) == 42); // expected-warning{{TRUE}}
  clang_analyzer_eval(getConstrainedFieldRefOp(getS()) == 42); // expected-warning{{TRUE}}
}

namespace EmptyClass {
  struct Base {
    int& x;

    Base(int& x) : x(x) {}
  };

  struct Derived : public Base {
    Derived(int& x) : Base(x) {}

    void operator=(int a) { x = a; }
  };

  Derived ref(int& a) { return Derived(a); }

  // There used to be a warning here, because analyzer treated Derived as empty.
  int test() {
    int a;
    ref(a) = 42;
    return a; // no warning
  }
}

#if __cplusplus >= 201703L
namespace aggregate_inheritance_cxx17 {
struct A {
  int x;
};

struct B {
  int y;
};

struct C: B {
  int z;
};

struct D: A, C {
  int w;
};

void foo() {
  D d{1, 2, 3, 4};
  clang_analyzer_eval(d.x == 1); // expected-warning{{TRUE}}
  clang_analyzer_eval(d.y == 2); // expected-warning{{TRUE}}
  clang_analyzer_eval(d.z == 3); // expected-warning{{TRUE}}
  clang_analyzer_eval(d.w == 4); // expected-warning{{TRUE}}
}
} // namespace aggregate_inheritance_cxx17
#endif

namespace flex_array_inheritance_cxx17 {
struct A {
  int flexible_array[];
};

struct B {
  long cookie;
};

struct C : B {
  A a;
};

void foo() {
  C c{}; // no-crash
}
} // namespace flex_array_inheritance_cxx17
#endif
