1 /*******************************************************************************
2 
3     Struct template which wraps a void[] with an API allowing it to be safely
4     used as an array of another type.
5 
6     It is, of course, possible to simply cast a void[] to another type of array
7     and use it directly. However, care must be taken when casting to an array
8     type with a different element size. Experience has shown that this is likely
9     to hit undefined behaviour. For example, casting the array then sizing it
10     has been observed to cause segfaults, e.g.:
11 
12     ---
13         void[]* void_array; // acquired from somewhere
14 
15         struct S { int i; hash_t h; }
16         auto s_array = cast(S[]*)void_array;
17         s_array.length = 23;
18     ---
19 
20     The exact reason for the segfaults is not known, but this usage appears to
21     lead to corruption of internal GC data (possibly type metadata associated
22     with the array's pointer).
23 
24     Sizing the array first, then casting is fine, e.g.:
25 
26     ---
27         void[]* void_array; // acquired from somewhere
28 
29         struct S { int i; hash_t h; }
30         (*void_array).length = 23 * S.sizeof;
31         auto s_array = cast(S[])*void_array;
32     ---
33 
34     The helper VoidBufferAsArrayOf simplifies this procedure and removes the
35     risk of undefined behaviour by always handling the void[] as a void[]
36     internally.
37 
38     Copyright:
39         Copyright (c) 2017-2018 dunnhumby Germany GmbH.
40         All rights reserved
41 
42     License:
43         Boost Software License Version 1.0. See LICENSE.txt for details.
44 
45 *******************************************************************************/
46 
47 module ocean.util.container.VoidBufferAsArrayOf;
48 
49 import ocean.transition;
50 
51 /*******************************************************************************
52 
53     Struct template which wraps a void[] with an API allowing it to be safely
54     used as an array of another type.
55 
56     Params:
57         T = element type of array which the API mimics
58 
59 *******************************************************************************/
60 
61 public struct VoidBufferAsArrayOf ( T )
62 {
63     // T == void is not only pointless but is also invalid: it's illegal to pass
64     // a void argument to a function (e.g. opCatAssign).
65     static assert(!is(T == void));
66 
67     /// Pointer to the underlying void buffer. Must be set before use by struct
68     /// construction.
69     private void[]* buffer;
70 
71     // The length of the buffer must always be an even multiple of T.sizeof.
72     invariant ( )
73     {
74         assert((&this).buffer.length % T.sizeof == 0);
75     }
76 
77     /***************************************************************************
78 
79         Returns:
80             a slice of this array
81 
82     ***************************************************************************/
83 
84     public T[] array ( )
85     {
86         return cast(T[])(*(&this).buffer);
87     }
88 
89     /***************************************************************************
90 
91         Returns:
92             the number of T elements in the array
93 
94     ***************************************************************************/
95 
96     public size_t length ( )
97     {
98         return (&this).buffer.length / T.sizeof;
99     }
100 
101     /***************************************************************************
102 
103         Sets the length of the array.
104 
105         Params:
106             len = new length of the array, in terms of the number of T elements
107 
108     ***************************************************************************/
109 
110     public void length ( size_t len )
111     {
112         (&this).buffer.length = len * T.sizeof;
113         enableStomping(*(&this).buffer);
114     }
115 
116     /***************************************************************************
117 
118         Appends an array of elements.
119 
120         Note that mutable copies of appended elements are made internally, but
121         to access them from the outside, the constness of T applies.
122 
123         but
124         are inaccessible from the outside.
125 
126         Params:
127             arr = elements to append
128 
129         Returns:
130             a slice of this array, now with the specified elements appended
131 
132     ***************************************************************************/
133 
134     public T[] opCatAssign ( in T[] arr )
135     {
136         return cast(T[])((*(&this).buffer) ~= cast(void[])arr);
137     }
138 
139     /***************************************************************************
140 
141         Appends an element.
142 
143         Note that a mutable copy of the appended element is made internally, but
144         to access it from the outside, the constness of T applies.
145 
146         Params:
147             element = element to append
148 
149         Returns:
150             a slice of this array, now with the specified element appended
151 
152     ***************************************************************************/
153 
154     public T[] opCatAssign ( in T element )
155     {
156         return (&this).opCatAssign((&element)[0 .. 1]);
157     }
158 }
159 
160 ///
161 unittest
162 {
163     // Backing array.
164     void[] backing;
165 
166     // Wrap the backing array for use as an S[].
167     struct S
168     {
169         ubyte b;
170         hash_t h;
171     }
172 
173     auto s_array = VoidBufferAsArrayOf!(S)(&backing);
174 
175     // Append some elements.
176     s_array ~= S();
177     s_array ~= [S(), S(), S()];
178 
179     // Resize the array.
180     s_array.length = 2;
181 
182     // Iterate over the elements.
183     foreach ( e; s_array.array() ) { }
184 }
185 
186 version ( UnitTest )
187 {
188     import ocean.core.Test;
189 
190     align ( 1 ) struct S
191     {
192         ubyte b;
193         hash_t h;
194     }
195 }
196 
197 unittest
198 {
199     void[] backing;
200 
201     auto s_array = VoidBufferAsArrayOf!(S)(&backing);
202 
203     test!("==")(s_array.length, 0);
204     test!("==")(s_array.buffer.length, 0);
205 
206     s_array ~= S(0, 0);
207     test!("==")(s_array.array(), [S(0, 0)]);
208     test(s_array.length == 1);
209     test(s_array.buffer.length == S.sizeof);
210 
211     s_array ~= [S(1, 1), S(2, 2), S(3, 3)];
212     test!("==")(s_array.array(), [S(0, 0), S(1, 1), S(2, 2), S(3, 3)]);
213     test(s_array.length == 4);
214     test(s_array.buffer.length == S.sizeof * 4);
215 
216     s_array.length = 2;
217     test!("==")(s_array.array(), [S(0, 0), S(1, 1)]);
218     test(s_array.length == 2);
219     test(s_array.buffer.length == S.sizeof * 2);
220 }