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

void clang_analyzer_eval(bool);

void array_init() {
  int arr[] = {1, 2, 3, 4, 5};

  auto [a, b, c, d, e] = arr;

  clang_analyzer_eval(a == 1); // expected-warning{{TRUE}}
  clang_analyzer_eval(b == 2); // expected-warning{{TRUE}}
  clang_analyzer_eval(c == 3); // expected-warning{{TRUE}}
  clang_analyzer_eval(d == 4); // expected-warning{{TRUE}}
  clang_analyzer_eval(e == 5); // expected-warning{{TRUE}}
}

void array_uninit() {
  int arr[5];

  auto [a, b, c, d, e] = arr;

  int x = e; // expected-warning{{Assigned value is garbage or undefined}}
}

void lambda_init() {
  int arr[] = {1, 2, 3, 4, 5};

  auto l = [arr] { return arr[0]; }();
  clang_analyzer_eval(l == 1); // expected-warning{{TRUE}}

  l = [arr] { return arr[1]; }();
  clang_analyzer_eval(l == 2); // expected-warning{{TRUE}}

  l = [arr] { return arr[2]; }();
  clang_analyzer_eval(l == 3); // expected-warning{{TRUE}}

  l = [arr] { return arr[3]; }();
  clang_analyzer_eval(l == 4); // expected-warning{{TRUE}}

  l = [arr] { return arr[4]; }();
  clang_analyzer_eval(l == 5); // expected-warning{{TRUE}}
}

void lambda_uninit() {
  int arr[5];

  // FIXME: These should be Undefined, but we fail to read Undefined from a lazyCompoundVal
  int l = [arr] { return arr[0]; }();
  clang_analyzer_eval(l); // expected-warning{{UNKNOWN}}

  l = [arr] { return arr[1]; }();
  clang_analyzer_eval(l); // expected-warning{{UNKNOWN}}

  l = [arr] { return arr[2]; }();
  clang_analyzer_eval(l); // expected-warning{{UNKNOWN}}

  l = [arr] { return arr[3]; }();
  clang_analyzer_eval(l); // expected-warning{{UNKNOWN}}

  l = [arr] { return arr[4]; }();
  clang_analyzer_eval(l); // expected-warning{{UNKNOWN}}
}

struct S {
  int arr[5];
};

void copy_ctor_init() {
  S orig;
  orig.arr[0] = 1;
  orig.arr[1] = 2;
  orig.arr[2] = 3;
  orig.arr[3] = 4;
  orig.arr[4] = 5;

  S copy = orig;
  clang_analyzer_eval(copy.arr[0] == 1); // expected-warning{{TRUE}}
  clang_analyzer_eval(copy.arr[1] == 2); // expected-warning{{TRUE}}
  clang_analyzer_eval(copy.arr[2] == 3); // expected-warning{{TRUE}}
  clang_analyzer_eval(copy.arr[3] == 4); // expected-warning{{TRUE}}
  clang_analyzer_eval(copy.arr[4] == 5); // expected-warning{{TRUE}}
}

void copy_ctor_uninit() {
  S orig;

  S copy = orig;

  // FIXME: These should be Undefined, but we fail to read Undefined from a lazyCompoundVal.
  // If the struct is not considered a small struct, instead of a copy, we store a lazy compound value.
  // As the struct has an array data member, it is not considered small.
  clang_analyzer_eval(copy.arr[0]); // expected-warning{{UNKNOWN}}
  clang_analyzer_eval(copy.arr[1]); // expected-warning{{UNKNOWN}}
  clang_analyzer_eval(copy.arr[2]); // expected-warning{{UNKNOWN}}
  clang_analyzer_eval(copy.arr[3]); // expected-warning{{UNKNOWN}}
  clang_analyzer_eval(copy.arr[4]); // expected-warning{{UNKNOWN}}
}

void move_ctor_init() {
  S orig;
  orig.arr[0] = 1;
  orig.arr[1] = 2;
  orig.arr[2] = 3;
  orig.arr[3] = 4;
  orig.arr[4] = 5;

  S moved = (S &&) orig;

  clang_analyzer_eval(moved.arr[0] == 1); // expected-warning{{TRUE}}
  clang_analyzer_eval(moved.arr[1] == 2); // expected-warning{{TRUE}}
  clang_analyzer_eval(moved.arr[2] == 3); // expected-warning{{TRUE}}
  clang_analyzer_eval(moved.arr[3] == 4); // expected-warning{{TRUE}}
  clang_analyzer_eval(moved.arr[4] == 5); // expected-warning{{TRUE}}
}

void move_ctor_uninit() {
  S orig;

  S moved = (S &&) orig;

  // FIXME: These should be Undefined, but we fail to read Undefined from a lazyCompoundVal.
  clang_analyzer_eval(moved.arr[0]); // expected-warning{{UNKNOWN}}
  clang_analyzer_eval(moved.arr[1]); // expected-warning{{UNKNOWN}}
  clang_analyzer_eval(moved.arr[2]); // expected-warning{{UNKNOWN}}
  clang_analyzer_eval(moved.arr[3]); // expected-warning{{UNKNOWN}}
  clang_analyzer_eval(moved.arr[4]); // expected-warning{{UNKNOWN}}
}

// The struct has a user defined copy and move ctor, which allow us to
// track the values more precisely when an array of this struct is being
// copy/move initialized by ArrayInitLoopExpr.
struct S2 {
  inline static int c = 0;
  int i;

  S2() : i(++c) {}

  S2(const S2 &copy) {
    i = copy.i + 1;
  }

  S2(S2 &&move) {
    i = move.i + 2;
  }
};

void array_init_non_pod() {
  S2::c = 0;
  S2 arr[4];

  auto [a, b, c, d] = arr;

  clang_analyzer_eval(a.i == 2); // expected-warning{{TRUE}}
  clang_analyzer_eval(b.i == 3); // expected-warning{{TRUE}}
  clang_analyzer_eval(c.i == 4); // expected-warning{{TRUE}}
  clang_analyzer_eval(d.i == 5); // expected-warning{{TRUE}}
}

struct S3 {
  int i;
};

// The duplicate is required to emit a warning at 2 different places. 
struct S3_duplicate {
  int i;
};

void array_uninit_non_pod() {
  S3 arr[1];

  auto [a] = arr; // expected-warning@159{{ in implicit constructor is garbage or undefined }}
}

void lambda_init_non_pod() {
  S2::c = 0;
  S2 arr[4];

  auto l = [arr] { return arr[0].i; }();
  clang_analyzer_eval(l == 2); // expected-warning{{TRUE}}

  l = [arr] { return arr[1].i; }();
  clang_analyzer_eval(l == 3); // expected-warning{{TRUE}}

  l = [arr] { return arr[2].i; }();
  clang_analyzer_eval(l == 4); // expected-warning{{TRUE}}

  l = [arr] { return arr[3].i; }();
  clang_analyzer_eval(l == 5); // expected-warning{{TRUE}}
}

void lambda_uninit_non_pod() {
  S3_duplicate arr[4];

  int l = [arr] { return arr[3].i; }(); // expected-warning@164{{ in implicit constructor is garbage or undefined }}
}

// If this struct is being copy/move constructed by the implicit ctors, ArrayInitLoopExpr
// is responsible for the initialization of 'arr' by copy/move constructing each of the
// elements.
struct S5 {
  S2 arr[4];
};

void copy_ctor_init_non_pod() {
  S2::c = 0;
  S5 orig;

  S5 copy = orig;
  clang_analyzer_eval(copy.arr[0].i == 2); // expected-warning{{TRUE}}
  clang_analyzer_eval(copy.arr[1].i == 3); // expected-warning{{TRUE}}
  clang_analyzer_eval(copy.arr[2].i == 4); // expected-warning{{TRUE}}
  clang_analyzer_eval(copy.arr[3].i == 5); // expected-warning{{TRUE}}
}

void move_ctor_init_non_pod() {
  S2::c = 0;
  S5 orig;

  S5 moved = (S5 &&) orig;

  clang_analyzer_eval(moved.arr[0].i == 3); // expected-warning{{TRUE}}
  clang_analyzer_eval(moved.arr[1].i == 4); // expected-warning{{TRUE}}
  clang_analyzer_eval(moved.arr[2].i == 5); // expected-warning{{TRUE}}
  clang_analyzer_eval(moved.arr[3].i == 6); // expected-warning{{TRUE}}
}
