ongoing testconfig work
[tamarin-stm.git] / extensions / ST_mmgc_finalize_uninit.st
blobb988f4446269d5924d1b018698be5277fbc9f4a5
1 // -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*-
2 // vi: set ts=4 sw=4 expandtab: (add to ~/.vimrc: set modeline modelines=5) */
4 // Bugzilla 573737 - a throw from an argument to a constructor can
5 // cause an object to be allocated on the gc-heap before it has been
6 // fully initialized.  (In particular, its vtable could be missing.)
7 // Since finalizers use the virtual destructor method, a missing
8 // vtable is a problem.
9 //
10 // Its a little tricky to test this because the "order of evaluation
11 // to an operator new() to get memory and the evaluation of arguments
12 // to constructors is undefined"; see details below.
14 %%component mmgc
15 %%category finalize_uninit
17 %%prefix
19 using namespace MMgc;
21 class D : public GCFinalizedObject
23 public:
24     D(int dummyArgument) { (void)dummyArgument; }
25     ~D() { ++finalized; }
26     static int finalized_count() { return D::finalized; }
27     static void reset_finalized_count() { finalized = 0; }
28     // "usual" overload of placement new used in mmgc client code
29     void * operator new(size_t size, GC *gc) {
30         return GCFinalizedObject::operator new(size, gc);
31     }
32     // hack to explicitly order new-allocation + ctor-arg eval; see below
33     // (but reallys its just good ol' placement new!)
34     void * operator new(size_t size, GC *gc, void *raw) {
35         (void)size; (void)gc;
36         return raw;
37     }
38 private:
39     static int finalized;
42 /*static*/
43 int D::finalized;
45 int deathValue(AvmCore *core)
47     core->throwAtom(atomFromIntptrValue(1)); /* won't return */
49     return 0; /* silence compiler warnings */
52 // Test checks that finalizers themselves have not broken; here,
53 // constructor argument subexpression evaluates cleanly.
54 %%test check_finalizers_still_work
56     D::reset_finalized_count();
57     D* d;
58     int i;
60     // loop to alloc many (and subsequently reclaim >=expected percentage)
61     for (i = 0; i < 100; i++) {
62         d = new (core->gc) D(1);
63     }
64     (void) d;
66     core->gc->Collect(); // finish any prior incremental work ...
67     core->gc->Collect(); // ... and ensure we got fresh + complete gc.
69     // printf("D::finalized_count(): %d\n", D::finalized_count());
71     %%verify (D::finalized_count() > 90)
74 // Test illustrates of the kind of code that exposed the original bug;
75 // here, constructor argument subexpression throws.
76 %%test original_death
78     D* d;
79     volatile int i;
81     // Here, constructor argument subexpression throws
83     // Just one allocation attempt alone would risk false retention of
84     // intermediate values, so loop a bit to ensure that *some* D's,
85     // if allocated at all, will be considered garbage.
86     for (i = 0; i < 5; i++) {
87         TRY (core, kCatchAction_Ignore) {
88             d = new (core->gc) D(deathValue(core));
89         }  CATCH (Exception *e) {
90             (void)e;
91         }
92         END_CATCH
93         END_TRY
94     }
95     (void) d;
97     // if things go badly, one of the collections below will segfault
98     // during finalization.
99     core->gc->Collect();
100     core->gc->Collect();
102     // (not dying is passing.)
103     %%verify true
106 // C++ standard says "The order of evaluation to an operator new() to
107 // get memory and the evaluation of arguments to constructors is
108 // undefined."
110 // Unfortunately, it is difficult to directly express the particular
111 // order of evaluation that exposes the bug in question, because
112 // allocation and construction are tied together.
114 // So, here we manually decompose the tests above to control
115 // evaluation order of operator new() and constructor arguments, to
116 // express suitable evil (namely: allocation; args eval; construction)
117 // for selftest.
119 // The desugaring iteself is:
120 // desugar[[ new (gc-exp) D(arg-exp) ]]
121 // ==> mem = D::operator new(sizeof(D), arg), new (gc-exp, mem) D(arg-exp)
123 // Test illustrates desugaring is "sound"; keep in sync with
124 // check_finalizers_still_work above.
125 %%test desugared_check_finalizers_still_work
127     D::reset_finalized_count();
128     D* d;
129     int i;
131     // loop to alloc many (and subsequently reclaim >=expected percentage)
132     for (i = 0; i < 100; i++) {
133         // d = new (core->gc) D(s);
134         void *mem = D::operator new(sizeof(D), core->gc);
135         d = new (core->gc, mem) D(1);
136     }
137     (void) d;
139     core->gc->Collect(); // finish any prior incremental work ...
140     core->gc->Collect(); // ... and ensure we got fresh + complete gc.
142     // printf("D::finalized_count(): %d\n", D::finalized_count());
144     %%verify (D::finalized_count() > 90)
148 // Test forces evil order of evaluation via desugaring of
149 // construction; keep in sync with original_death above.
150 %%test desugared_death
152     D* d;
153     volatile int i;
155     // Here, constructor argument subexpression throws
157     // Just one allocation attempt alone would risk false retention of
158     // intermediate values, so loop a bit to ensure that *some* D's,
159     // which are forcibly allocated here, will be considered garbage.
160     for (i = 0; i < 5; i++) {
161         TRY (core, kCatchAction_Ignore) {
162             // d = new (core->gc) D(deathValue());
163             void *mem = D::operator new(sizeof(D), core->gc);
164             d = new (core->gc, mem) D(deathValue(core));
165         }  CATCH (Exception *e) {
166             (void)e;
167         }
168         END_CATCH
169         END_TRY
170     }
171     (void) d;
173     // if things go badly, one of the collections below will segfault
174     // during finalization.
175     core->gc->Collect();
176     core->gc->Collect();
178     // (not dying is passing.)
179     %%verify true