1/**
2 This module contains support for D's postblit feature
3
4  Copyright: Copyright Digital Mars 2000 - 2019.
5  License: Distributed under the
6       $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
7     (See accompanying file LICENSE)
8  Source: $(DRUNTIMESRC core/_internal/_destruction.d)
9*/
10module core.internal.postblit;
11
12// compiler frontend lowers struct array postblitting to this
13void __ArrayPostblit(T)(T[] a)
14{
15    foreach (ref T e; a)
16        e.__xpostblit();
17}
18
19package void postblitRecurse(S)(ref S s)
20    if (is(S == struct))
21{
22    static if (__traits(hasMember, S, "__xpostblit") &&
23               // Bugzilla 14746: Check that it's the exact member of S.
24               __traits(isSame, S, __traits(parent, s.__xpostblit)))
25        s.__xpostblit();
26}
27
28package void postblitRecurse(E, size_t n)(ref E[n] arr)
29{
30    import core.internal.destruction: destructRecurse;
31    import core.internal.traits : hasElaborateCopyConstructor;
32
33    static if (hasElaborateCopyConstructor!E)
34    {
35        size_t i;
36        scope(failure)
37        {
38            for (; i != 0; --i)
39            {
40                destructRecurse(arr[i - 1]); // What to do if this throws?
41            }
42        }
43
44        for (i = 0; i < arr.length; ++i)
45            postblitRecurse(arr[i]);
46    }
47}
48
49// Test destruction/postblit order
50@safe nothrow pure unittest
51{
52    string[] order;
53
54    struct InnerTop
55    {
56        ~this() @safe nothrow pure
57        {
58            order ~= "destroy inner top";
59        }
60
61        this(this) @safe nothrow pure
62        {
63            order ~= "copy inner top";
64        }
65    }
66
67    struct InnerMiddle {}
68
69    version (none) // https://issues.dlang.org/show_bug.cgi?id=14242
70    struct InnerElement
71    {
72        static char counter = '1';
73
74        ~this() @safe nothrow pure
75        {
76            order ~= "destroy inner element #" ~ counter++;
77        }
78
79        this(this) @safe nothrow pure
80        {
81            order ~= "copy inner element #" ~ counter++;
82        }
83    }
84
85    struct InnerBottom
86    {
87        ~this() @safe nothrow pure
88        {
89            order ~= "destroy inner bottom";
90        }
91
92        this(this) @safe nothrow pure
93        {
94            order ~= "copy inner bottom";
95        }
96    }
97
98    struct S
99    {
100        char[] s;
101        InnerTop top;
102        InnerMiddle middle;
103        version (none) InnerElement[3] array; // https://issues.dlang.org/show_bug.cgi?id=14242
104        int a;
105        InnerBottom bottom;
106        ~this() @safe nothrow pure { order ~= "destroy outer"; }
107        this(this) @safe nothrow pure { order ~= "copy outer"; }
108    }
109
110    string[] destructRecurseOrder;
111    {
112        import core.internal.destruction: destructRecurse;
113
114        S s;
115        destructRecurse(s);
116        destructRecurseOrder = order;
117        order = null;
118    }
119
120    assert(order.length);
121    assert(destructRecurseOrder == order);
122    order = null;
123
124    S s;
125    postblitRecurse(s);
126    assert(order.length);
127    auto postblitRecurseOrder = order;
128    order = null;
129    S s2 = s;
130    assert(order.length);
131    assert(postblitRecurseOrder == order);
132}
133
134@safe unittest
135{
136    // Bugzilla 14746
137    static struct HasPostblit
138    {
139        this(this) { assert(0); }
140    }
141    static struct Owner
142    {
143        HasPostblit* ptr;
144        alias ptr this;
145    }
146
147    Owner o;
148    assert(o.ptr is null);
149    postblitRecurse(o);     // must not reach in HasPostblit.__postblit()
150}
151
152// Test handling of fixed-length arrays
153// Separate from first test because of https://issues.dlang.org/show_bug.cgi?id=14242
154@safe unittest
155{
156    string[] order;
157
158    struct S
159    {
160        char id;
161
162        this(this)
163        {
164            order ~= "copy #" ~ id;
165        }
166
167        ~this()
168        {
169            order ~= "destroy #" ~ id;
170        }
171    }
172
173    string[] destructRecurseOrder;
174    {
175        import core.internal.destruction: destructRecurse;
176
177        S[3] arr = [S('1'), S('2'), S('3')];
178        destructRecurse(arr);
179        destructRecurseOrder = order;
180        order = null;
181    }
182    assert(order.length);
183    assert(destructRecurseOrder == order);
184    order = null;
185
186    S[3] arr = [S('1'), S('2'), S('3')];
187    postblitRecurse(arr);
188    assert(order.length);
189    auto postblitRecurseOrder = order;
190    order = null;
191
192    auto arrCopy = arr;
193    assert(order.length);
194    assert(postblitRecurseOrder == order);
195}
196
197// Test handling of failed postblit
198// Not nothrow or @safe because of https://issues.dlang.org/show_bug.cgi?id=14242
199/+ nothrow @safe +/ unittest
200{
201    static class FailedPostblitException : Exception { this() nothrow @safe { super(null); } }
202    static string[] order;
203    static struct Inner
204    {
205        char id;
206
207        @safe:
208        this(this)
209        {
210            order ~= "copy inner #" ~ id;
211            if (id == '2')
212                throw new FailedPostblitException();
213        }
214
215        ~this() nothrow
216        {
217            order ~= "destroy inner #" ~ id;
218        }
219    }
220
221    static struct Outer
222    {
223        Inner inner1, inner2, inner3;
224
225        nothrow @safe:
226        this(char first, char second, char third)
227        {
228            inner1 = Inner(first);
229            inner2 = Inner(second);
230            inner3 = Inner(third);
231        }
232
233        this(this)
234        {
235            order ~= "copy outer";
236        }
237
238        ~this()
239        {
240            order ~= "destroy outer";
241        }
242    }
243
244    auto outer = Outer('1', '2', '3');
245
246    try postblitRecurse(outer);
247    catch (FailedPostblitException) {}
248    catch (Exception) assert(false);
249
250    auto postblitRecurseOrder = order;
251    order = null;
252
253    try auto copy = outer;
254    catch (FailedPostblitException) {}
255    catch (Exception) assert(false);
256
257    assert(postblitRecurseOrder == order);
258    order = null;
259
260    Outer[3] arr = [Outer('1', '1', '1'), Outer('1', '2', '3'), Outer('3', '3', '3')];
261
262    try postblitRecurse(arr);
263    catch (FailedPostblitException) {}
264    catch (Exception) assert(false);
265
266    postblitRecurseOrder = order;
267    order = null;
268
269    try auto arrCopy = arr;
270    catch (FailedPostblitException) {}
271    catch (Exception) assert(false);
272
273    assert(postblitRecurseOrder == order);
274}
275