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.
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.
15 %%category finalize_uninit
21 class D : public GCFinalizedObject
24 D(int dummyArgument) { (void)dummyArgument; }
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);
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) {
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();
60 // loop to alloc many (and subsequently reclaim >=expected percentage)
61 for (i = 0; i < 100; i++) {
62 d = new (core->gc) D(1);
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.
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) {
97 // if things go badly, one of the collections below will segfault
98 // during finalization.
102 // (not dying is passing.)
106 // C++ standard says "The order of evaluation to an operator new() to
107 // get memory and the evaluation of arguments to constructors is
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)
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();
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);
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
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) {
173 // if things go badly, one of the collections below will segfault
174 // during finalization.
178 // (not dying is passing.)