// RUN: %clang_analyze_cc1 -std=c++11 -analyzer-checker=core,cplusplus.NewDelete,cplusplus.NewDeleteLeaks,debug.ExprInspection -analyzer-config c++-allocator-inlining=true -std=c++11 -verify -analyzer-config eagerly-assume=false %s

void clang_analyzer_eval(bool);
void clang_analyzer_dump(int);

typedef __typeof__(sizeof(int)) size_t;

void *conjure();
void exit(int);

struct S;

S *global_s;

// Recursive operator kinda placement new.
void *operator new(size_t size, S *place);

enum class ConstructionKind : char {
  Garbage,
  Recursive
};

struct S {
public:
  int x;
  S(): x(1) {}
  S(int y): x(y) {}

  S(ConstructionKind k) {
    switch (k) {
    case ConstructionKind::Recursive: { // Call one more operator new 'r'ecursively.
      S *s = new (nullptr) S(5);
      x = s->x + 1;
      global_s = s;
      return;
    }
    case ConstructionKind::Garbage: {
      // Leaves garbage in 'x'.
    }
    }
  }
  ~S() {}
};

// Do not try this at home!
void *operator new(size_t size, S *place) {
  if (!place)
    return new S();
  return place;
}

void testThatCharConstructorIndeedYieldsGarbage() {
  S *s = new S(ConstructionKind::Garbage);
  clang_analyzer_eval(s->x == 0); // expected-warning{{UNKNOWN}}
  clang_analyzer_eval(s->x == 1); // expected-warning{{UNKNOWN}}
  // FIXME: This should warn, but MallocChecker doesn't default-bind regions
  // returned by standard operator new to garbage.
  s->x += 1; // no-warning
  delete s;
}


void testChainedOperatorNew() {
  S *s;
  // * Evaluate standard new.
  // * Evaluate constructor S(3).
  // * Bind value for standard new.
  // * Evaluate our custom new.
  // * Evaluate constructor S(Garbage).
  // * Bind value for our custom new.
  s = new (new S(3)) S(ConstructionKind::Garbage);
  clang_analyzer_eval(s->x == 3); // expected-warning{{TRUE}}
  // expected-warning@+9{{Potential leak of memory pointed to by 's'}}

  // * Evaluate standard new.
  // * Evaluate constructor S(Garbage).
  // * Bind value for standard new.
  // * Evaluate our custom new.
  // * Evaluate constructor S(4).
  // * Bind value for our custom new.
  s = new (new S(ConstructionKind::Garbage)) S(4);
  clang_analyzer_eval(s->x == 4); // expected-warning{{TRUE}}
  delete s;

  // -> Enter our custom new (nullptr).
  //   * Evaluate standard new.
  //   * Inline constructor S().
  //   * Bind value for standard new.
  // <- Exit our custom new (nullptr).
  // * Evaluate constructor S(Garbage).
  // * Bind value for our custom new.
  s = new (nullptr) S(ConstructionKind::Garbage);
  clang_analyzer_eval(s->x == 1); // expected-warning{{TRUE}}
  delete s;

  // -> Enter our custom new (nullptr).
  //   * Evaluate standard new.
  //   * Inline constructor S().
  //   * Bind value for standard new.
  // <- Exit our custom new (nullptr).
  // -> Enter constructor S(Recursive).
  //   -> Enter our custom new (nullptr).
  //     * Evaluate standard new.
  //     * Inline constructor S().
  //     * Bind value for standard new.
  //   <- Exit our custom new (nullptr).
  //   * Evaluate constructor S(5).
  //   * Bind value for our custom new (nullptr).
  //   * Assign that value to global_s.
  // <- Exit constructor S(Recursive).
  // * Bind value for our custom new (nullptr).
  global_s = nullptr;
  s = new (nullptr) S(ConstructionKind::Recursive);
  clang_analyzer_eval(global_s); // expected-warning{{TRUE}}
  clang_analyzer_eval(global_s->x == 5); // expected-warning{{TRUE}}
  clang_analyzer_eval(s->x == 6); // expected-warning{{TRUE}}
  delete s;
}
