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.meta.types.Qualifiers;
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. opOpAssign!("~")).
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         assumeSafeAppend(*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         Params:
124             arr = elements to append
125 
126         Returns:
127             a slice of this array, now with the specified elements appended
128 
129     ***************************************************************************/
130 
131     public T[] opOpAssign ( string op : "~" ) ( in T[] arr )
132     {
133         return cast(T[])((*this.buffer) ~= cast(void[])arr);
134     }
135 
136     /***************************************************************************
137 
138         Appends an element.
139 
140         Note that a mutable copy of the appended element is made internally, but
141         to access it from the outside, the constness of T applies.
142 
143         Params:
144             element = element to append
145 
146         Returns:
147             a slice of this array, now with the specified element appended
148 
149     ***************************************************************************/
150 
151     public T[] opOpAssign ( string op : "~" ) ( in T element )
152     {
153         return this.opOpAssign!("~")((&element)[0 .. 1]);
154     }
155 }
156 
157 ///
158 unittest
159 {
160     // Backing array.
161     void[] backing;
162 
163     // Wrap the backing array for use as an S[].
164     struct S
165     {
166         ubyte b;
167         hash_t h;
168     }
169 
170     auto s_array = VoidBufferAsArrayOf!(S)(&backing);
171 
172     // Append some elements.
173     s_array ~= S();
174     s_array ~= [S(), S(), S()];
175 
176     // Resize the array.
177     s_array.length = 2;
178 
179     // Iterate over the elements.
180     foreach ( e; s_array.array() ) { }
181 }
182 
183 version (unittest)
184 {
185     import ocean.core.Test;
186 
187     align ( 1 ) struct S
188     {
189         ubyte b;
190         hash_t h;
191     }
192 }
193 
194 unittest
195 {
196     void[] backing;
197 
198     auto s_array = VoidBufferAsArrayOf!(S)(&backing);
199 
200     test!("==")(s_array.length, 0);
201     test!("==")(s_array.buffer.length, 0);
202 
203     s_array ~= S(0, 0);
204     test!("==")(s_array.array(), [S(0, 0)]);
205     test(s_array.length == 1);
206     test(s_array.buffer.length == S.sizeof);
207 
208     s_array ~= [S(1, 1), S(2, 2), S(3, 3)];
209     test!("==")(s_array.array(), [S(0, 0), S(1, 1), S(2, 2), S(3, 3)]);
210     test(s_array.length == 4);
211     test(s_array.buffer.length == S.sizeof * 4);
212 
213     s_array.length = 2;
214     test!("==")(s_array.array(), [S(0, 0), S(1, 1)]);
215     test(s_array.length == 2);
216     test(s_array.buffer.length == S.sizeof * 2);
217 }