// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s

void clang_analyzer_eval(bool);

struct A {
  int x;
  void foo() const;
  void bar();

  void testImplicitThisSyntax() {
    x = 3;
    foo();
    clang_analyzer_eval(x == 3); // expected-warning{{TRUE}}
    bar();
    clang_analyzer_eval(x == 3); // expected-warning{{UNKNOWN}}
  }
};

struct B {
  mutable int mut;
  void foo() const;
};

struct C {
  int *p;
  void foo() const;
};

struct MutBase {
  mutable int b_mut;
};

struct MutDerived : MutBase {
  void foo() const;
};

struct PBase {
  int *p;
};

struct PDerived : PBase {
  void foo() const;
};

struct Inner {
  int x;
  int *p;
  void bar() const;
};

struct Outer {
  int x;
  Inner in;
  void foo() const;
};

void checkThatConstMethodWithoutDefinitionDoesNotInvalidateObject() {
  A t;
  t.x = 3;
  t.foo();
  clang_analyzer_eval(t.x == 3); // expected-warning{{TRUE}}
  // Test non-const does invalidate
  t.bar();
  clang_analyzer_eval(t.x); // expected-warning{{UNKNOWN}}
}

void checkThatConstMethodDoesInvalidateMutableFields() {
  B t;
  t.mut = 4;
  t.foo();
  clang_analyzer_eval(t.mut); // expected-warning{{UNKNOWN}}
}

void checkThatConstMethodDoesInvalidatePointedAtMemory() {
  int x = 1;
  C t;
  t.p = &x;
  t.foo();
  clang_analyzer_eval(x); // expected-warning{{UNKNOWN}}
  clang_analyzer_eval(t.p == &x); // expected-warning{{TRUE}}
}

void checkThatConstMethodDoesInvalidateInheritedMutableFields() {
  MutDerived t;
  t.b_mut = 4;
  t.foo();
  clang_analyzer_eval(t.b_mut); // expected-warning{{UNKNOWN}}
}

void checkThatConstMethodDoesInvalidateInheritedPointedAtMemory() {
  int x = 1;
  PDerived t;
  t.p = &x;
  t.foo();
  clang_analyzer_eval(x); // expected-warning{{UNKNOWN}}
  clang_analyzer_eval(t.p == &x); // expected-warning{{TRUE}}
}

void checkThatConstMethodDoesInvalidateContainedPointedAtMemory() {
  int x = 1;
  Outer t;
  t.x = 2;
  t.in.p = &x;
  t.foo();
  clang_analyzer_eval(x); // expected-warning{{UNKNOWN}}
  clang_analyzer_eval(t.x == 2); // expected-warning{{TRUE}}
  clang_analyzer_eval(t.in.p == &x); // expected-warning{{TRUE}}
}

void checkThatContainedConstMethodDoesNotInvalidateObjects() {
  Outer t;
  t.x = 1;
  t.in.x = 2;
  t.in.bar();
  clang_analyzer_eval(t.x == 1); // expected-warning{{TRUE}}
  clang_analyzer_eval(t.in.x == 2); // expected-warning{{TRUE}}
}

void checkPointerTypedThisExpression(A *a) {
  a->x = 3;
  a->foo();
  clang_analyzer_eval(a->x == 3); // expected-warning{{TRUE}}
  a->bar();
  clang_analyzer_eval(a->x == 3); // expected-warning{{UNKNOWN}}
}

void checkReferenceTypedThisExpression(A &a) {
  a.x = 3;
  a.foo();
  clang_analyzer_eval(a.x == 3); // expected-warning{{TRUE}}
  a.bar();
  clang_analyzer_eval(a.x == 3); // expected-warning{{UNKNOWN}}
}

// --- Versions of the above tests where the const method is inherited --- //

struct B1 {
  void foo() const;
};

struct D1 : public B1 {
  int x;
};

struct D2 : public B1 {
  mutable int mut;
};

struct D3 : public B1 {
  int *p;
};

struct DInner : public B1 {
  int x;
  int *p;
};

struct DOuter : public B1 {
  int x;
  DInner in;
};

void checkThatInheritedConstMethodDoesNotInvalidateObject() {
  D1 t;
  t.x = 1;
  t.foo();
  clang_analyzer_eval(t.x == 1); // expected-warning{{TRUE}}
}

void checkThatInheritedConstMethodDoesInvalidateMutableFields() {
  D2 t;
  t.mut = 1;
  t.foo();
  clang_analyzer_eval(t.mut); // expected-warning{{UNKNOWN}}
}

void checkThatInheritedConstMethodDoesInvalidatePointedAtMemory() {
  int x = 1;
  D3 t;
  t.p = &x;
  t.foo();
  clang_analyzer_eval(x); // expected-warning{{UNKNOWN}}
  clang_analyzer_eval(t.p == &x); // expected-warning{{TRUE}}
}

void checkThatInheritedConstMethodDoesInvalidateContainedPointedAtMemory() {
  int x = 1;
  DOuter t;
  t.x = 2;
  t.in.x = 3;
  t.in.p = &x;
  t.foo();
  clang_analyzer_eval(x); // expected-warning{{UNKNOWN}}
  clang_analyzer_eval(t.x == 2); // expected-warning{{TRUE}}
  clang_analyzer_eval(t.in.x == 3); // expected-warning{{TRUE}}
  clang_analyzer_eval(t.in.p == &x); // expected-warning{{TRUE}}
}

void checkThatInheritedContainedConstMethodDoesNotInvalidateObjects() {
  DOuter t;
  t.x = 1;
  t.in.x = 2;
  t.in.foo();
  clang_analyzer_eval(t.x == 1); // expected-warning{{TRUE}}
  clang_analyzer_eval(t.in.x == 2); // expected-warning{{TRUE}}
}

// --- PR21606 --- //

struct s1 {
    void g(const int *i) const;
};

struct s2 {
    void f(int *i) {
        m_i = i;
        m_s.g(m_i);
        if (m_i)
            *i = 42; // no-warning
    }

    int *m_i;
    s1 m_s;
};

void PR21606()
{
    s2().f(0);
}

// --- PR25392 --- //

struct HasConstMemberFunction {
public:
  void constMemberFunction() const;
};

HasConstMemberFunction hasNoReturn() { } // expected-warning {{control reaches end of non-void function}}

void testUnknownWithConstMemberFunction() {
  hasNoReturn().constMemberFunction();
}

void testNonRegionLocWithConstMemberFunction() {
  (*((HasConstMemberFunction *)(&&label))).constMemberFunction();

  label: return;
}

// FIXME
// When there is a circular reference to an object and a const method is called
// the object is not invalidated because TK_PreserveContents has already been
// set.
struct Outer2;

struct InnerWithRef {
  Outer2 *ref;
};

struct Outer2 {
  int x;
  InnerWithRef in;
  void foo() const;
};

void checkThatConstMethodCallDoesInvalidateObjectForCircularReferences() {
  Outer2 t;
  t.x = 1;
  t.in.ref = &t;
  t.foo();
  // FIXME: Should be UNKNOWN.
  clang_analyzer_eval(t.x); // expected-warning{{TRUE}}
}
