// Cross-DSO diagnostics.
// The rules are:
// * If the library needs diagnostics, the main executable must request at
//   least some diagnostics as well (to link the diagnostic runtime).
// * -fsanitize-trap on the caller side overrides everything.
// * otherwise, the callee decides between trap/recover/norecover.

// Full-recover.
// RUN: %clangxx_cfi_dso_diag -g -DSHARED_LIB %s -fPIC -shared -o %t-so.so
// RUN: %clangxx_cfi_dso_diag -g %s -o %t %t-so.so

// RUN: %t icv 2>&1 | FileCheck %s --check-prefix=ICALL-DIAG --check-prefix=CAST-DIAG \
// RUN:                            --check-prefix=VCALL-DIAG --check-prefix=ALL-RECOVER

// RUN: %t i_v 2>&1 | FileCheck %s --check-prefix=ICALL-DIAG --check-prefix=CAST-NODIAG \
// RUN:                            --check-prefix=VCALL-DIAG --check-prefix=ALL-RECOVER

// RUN: %t _cv 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-DIAG \
// RUN:                            --check-prefix=VCALL-DIAG --check-prefix=ALL-RECOVER

// RUN: %t ic_ 2>&1 | FileCheck %s --check-prefix=ICALL-DIAG --check-prefix=CAST-DIAG \
// RUN:                            --check-prefix=VCALL-NODIAG --check-prefix=ALL-RECOVER

// Trap on icall, no-recover on cast.
// RUN: %clangxx_cfi_dso_diag -fsanitize-trap=cfi-icall -fno-sanitize-recover=cfi-unrelated-cast \
// RUN:     -g -DSHARED_LIB %s -fPIC -shared -o %t-so.so
// RUN: %clangxx_cfi_dso_diag -fsanitize-trap=cfi-icall -fno-sanitize-recover=cfi-unrelated-cast \
// RUN:     -g %s -o %t %t-so.so

// RUN: %expect_crash %t icv 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-NODIAG \
// RUN:                                          --check-prefix=VCALL-NODIAG --check-prefix=ICALL-FATAL

// RUN: not %t _cv 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-DIAG \
// RUN:                                --check-prefix=VCALL-NODIAG --check-prefix=CAST-FATAL

// RUN: %t __v 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-NODIAG \
// RUN:                            --check-prefix=VCALL-DIAG

// Callee: trap on icall, no-recover on cast.
// Caller: recover on everything.
// The same as in the previous case, behaviour is decided by the callee.
// RUN: %clangxx_cfi_dso_diag -fsanitize-trap=cfi-icall -fno-sanitize-recover=cfi-unrelated-cast \
// RUN:     -g -DSHARED_LIB %s -fPIC -shared -o %t-so.so
// RUN: %clangxx_cfi_dso_diag \
// RUN:     -g %s -o %t %t-so.so

// RUN: %expect_crash %t icv 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-NODIAG \
// RUN:                                          --check-prefix=VCALL-NODIAG --check-prefix=ICALL-FATAL

// RUN: not %t _cv 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-DIAG \
// RUN:                                --check-prefix=VCALL-NODIAG --check-prefix=CAST-FATAL

// RUN: %t __v 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-NODIAG \
// RUN:                            --check-prefix=VCALL-DIAG

// Caller in trapping mode, callee with full diagnostic+recover.
// Caller wins.
// cfi-nvcall is non-trapping in the main executable to link the diagnostic runtime library.
// RUN: %clangxx_cfi_dso_diag \
// RUN:     -g -DSHARED_LIB %s -fPIC -shared -o %t-so.so
// RUN: %clangxx_cfi_dso -fno-sanitize-trap=cfi-nvcall \
// RUN:     -g %s -o %t %t-so.so

// RUN: %expect_crash %t icv 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-NODIAG \
// RUN:                                          --check-prefix=VCALL-NODIAG --check-prefix=ICALL-FATAL

// RUN: %expect_crash %t _cv 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-NODIAG \
// RUN:                                          --check-prefix=VCALL-NODIAG --check-prefix=CAST-FATAL

// RUN: %expect_crash %t __v 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-NODIAG \
// RUN:                                          --check-prefix=VCALL-NODIAG --check-prefix=VCALL-FATAL

// REQUIRES: cxxabi

#include <assert.h>
#include <stdio.h>
#include <string.h>

struct A {
  virtual void f();
};

void *create_B();

#ifdef SHARED_LIB

#include "../../utils.h"
struct B {
  virtual void f();
};
void B::f() {}

void *create_B() {
  create_derivers<B>();
  return (void *)(new B());
}

#else

void A::f() {}

int main(int argc, char *argv[]) {
  assert(argc == 2);
  assert(strlen(argv[1]) == 3);

  // ICALL-FATAL: =0=
  // CAST-FATAL:  =0=
  // VCALL-FATAL: =0=
  // ALL-RECOVER: =0=
  fprintf(stderr, "=0=\n");

  void *p;
  if (argv[1][0] == 'i') {
    // ICALL-DIAG: runtime error: control flow integrity check for type 'void *(int)' failed during indirect function call
    // ICALL-DIAG-NEXT: note: create_B() defined here
    // ICALL-NODIAG-NOT: runtime error: control flow integrity check {{.*}} during indirect function call
    p = ((void *(*)(int))create_B)(42);
  } else {
    p = create_B();
  }

  // ICALL-FATAL-NOT: =1=
  // CAST-FATAL:      =1=
  // VCALL-FATAL:     =1=
  // ALL-RECOVER:     =1=
  fprintf(stderr, "=1=\n");

  A *a;
  if (argv[1][1] == 'c') {
    // CAST-DIAG: runtime error: control flow integrity check for type 'A' failed during cast to unrelated type
    // CAST-DIAG-NEXT: note: vtable is of type '{{(struct )?}}B'
    // CAST-NODIAG-NOT: runtime error: control flow integrity check {{.*}} during cast to unrelated type
    a = (A*)p;
  } else {
    // Invisible to CFI.
    memcpy(&a, &p, sizeof(a));
  }

  // ICALL-FATAL-NOT: =2=
  // CAST-FATAL-NOT:  =2=
  // VCALL-FATAL:     =2=
  // ALL-RECOVER:     =2=
  fprintf(stderr, "=2=\n");

  // VCALL-DIAG: runtime error: control flow integrity check for type 'A' failed during virtual call
  // VCALL-DIAG-NEXT: note: vtable is of type '{{(struct )?}}B'
  // VCALL-NODIAG-NOT: runtime error: control flow integrity check {{.*}} during virtual call
  if (argv[1][2] == 'v') {
    a->f(); // UB here
  }

  // ICALL-FATAL-NOT: =3=
  // CAST-FATAL-NOT:  =3=
  // VCALL-FATAL-NOT: =3=
  // ALL-RECOVER: =3=
  fprintf(stderr, "=3=\n");

}
#endif
