CCTLib
Calling-context and data-object attribution library for Intel Pin
exc_ctor_throw.cpp
Go to the documentation of this file.
1 // Throw from a constructor mid-initialization. The already-constructed
2 // members and base subobjects must be destroyed in reverse order as the
3 // exception propagates. Every constructor and destructor writes to a
4 // per-instance byte in `buf`, so cctlib sees a clear pattern:
5 // * Members constructed before the throw: 1 store from ctor + 1 store from dtor
6 // * The throwing member: 1 store from ctor (then throws)
7 // * Members never constructed: 0 stores
8 // If cctlib's CCT walking on the unwind through the partially-constructed
9 // object gets confused, dead-write attribution would silently misalign
10 // (the dtor stores would appear under the wrong CCT node). This is a
11 // tp-shape victim: the tool must not crash and the byte pattern in `buf`
12 // must match the expected shape at the end.
13 //
14 // ctorthrow_try_marker / ctorthrow_catch_marker verify catch-body
15 // attribution when the ctor-throw path is taken.
16 #include <cstdint>
17 #include <cstdio>
18 #include <stdexcept>
19 #define ITERS 200
20 static volatile uint64_t sink;
21 static uint8_t buf[ITERS * 4]; // 4 bytes per iter: ctor+dtor for members A,B
22 
23 extern "C" __attribute__((noinline)) void ctorthrow_try_marker(int i) {
24  __asm__ __volatile__("" ::: "memory"); sink ^= (uint64_t)i;
25 }
26 extern "C" __attribute__((noinline)) void ctorthrow_catch_marker(int i) {
27  __asm__ __volatile__("" ::: "memory"); sink ^= (uint64_t)i << 8;
28 }
29 
30 struct A {
31  uint8_t* slot;
32  A(uint8_t* s) : slot(s) { *slot = 0x11; }
33  ~A() { *slot ^= 0xEE; }
34 };
35 
36 struct B {
37  uint8_t* slot;
38  B(uint8_t* s, bool doThrow) : slot(s) {
39  *slot = 0x22;
40  if (doThrow) throw std::runtime_error("boom");
41  }
42  ~B() { *slot ^= 0xDD; } // never reached in this test
43 };
44 
45 struct Wrap {
46  A a;
47  B b; // if b's ctor throws, a's dtor is called but Wrap's dtor is not
48  Wrap(uint8_t* baseSlot)
49  : a(&baseSlot[0]),
50  b(&baseSlot[2], true) // throws
51  {
52  baseSlot[3] = 0xFF; // unreachable
53  }
54  ~Wrap() { /* unreached */ }
55 };
56 
57 int main(int argc, char** argv) {
58  (void)argc; (void)argv;
59  int caught = 0;
60  for (int i = 0; i < ITERS; ++i) {
61  uint8_t* base = &buf[i * 4];
62  try {
63  ctorthrow_try_marker(i);
64  Wrap w(base);
65  (void)w;
66  } catch (const std::exception&) {
67  ctorthrow_catch_marker(i);
68  ++caught;
69  base[1] = 0xCC;
70  }
71  }
72  // Validate the write pattern:
73  // buf[i*4 + 0] = 0x11 ^ 0xEE = 0xFF (A ctor then dtor)
74  // buf[i*4 + 1] = 0xCC (post-catch marker)
75  // buf[i*4 + 2] = 0x22 (B ctor, B dtor never ran)
76  // buf[i*4 + 3] = 0 (unreached)
77  int ok = 0;
78  for (int i = 0; i < ITERS; ++i) {
79  uint8_t* p = &buf[i * 4];
80  if (p[0] == 0xFF && p[1] == 0xCC && p[2] == 0x22 && p[3] == 0) ++ok;
81  sink ^= p[0]; sink ^= p[1]; sink ^= p[2]; sink ^= p[3];
82  }
83  fprintf(stderr, "exc_ctor_throw: caught=%d ok_pattern=%d/%d sink=%llx\n",
84  caught, ok, ITERS, (unsigned long long)sink);
85  return (caught == ITERS && ok == ITERS) ? 0 : 1;
86 }
#define ITERS
int main(int argc, char **argv)
static uint8_t buf[ITERS *4]
__attribute__((noinline)) void ctorthrow_try_marker(int i)
static volatile uint64_t sink
A(uint8_t *s)
uint8_t * slot
B(uint8_t *s, bool doThrow)
uint8_t * slot
Wrap(uint8_t *baseSlot)