1 /******************************************************************************
2 
3     Defines struct type that is guaranteed to be stored in a contiguous byte
4     buffer including all referenced arrays / pointers.
5 
6     Copyright:
7         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
8         All rights reserved.
9 
10     License:
11         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
12         Alternatively, this file may be distributed under the terms of the Tango
13         3-Clause BSD License (see LICENSE_BSD.txt for details).
14 
15 *******************************************************************************/
16 
17 module ocean.util.serialize.contiguous.Contiguous;
18 
19 
20 import ocean.transition;
21 import ocean.core.Verify;
22 import ocean.meta.traits.Indirections;
23 import ocean.core.Enforce;
24 
25 version(UnitTest)
26 {
27     import ocean.core.Test;
28     debug = ContiguousIntegrity;
29 }
30 
31 
32 /*******************************************************************************
33 
34     "Tag" struct that wraps a void[] buffer with deserialized contents for
35     the struct of type S. Intended as type-safe tool to guarantee that any
36     operations on such structs preserve the contiguous data layout
37 
38     Params:
39         S = type of the wrapped struct
40 
41 *******************************************************************************/
42 
43 struct Contiguous( S )
44 {
45     /***************************************************************************
46 
47         Data buffer that stores deserialized struct data together with all
48         referenced arrays in a single contiguous chunk
49 
50         `package` protection is used so that contiguous (de)serializer can
51         access it directly
52 
53     ***************************************************************************/
54 
55     package void[] data;
56 
57     /***************************************************************************
58 
59         Used to work with `Contiguous!(S)` as if it was S*:
60 
61         --------
62         struct S { int a; }
63         Contiguous!(S) s = getS();
64         s.ptr.a = 42;
65         --------
66 
67         (replace it with "alias this" in D2)
68 
69         NB! You may only modify value types accessed via .ptr or elements
70         of stored dynamic arrays. Modifying actual arrays (i.e. appending
71         new elements) is strictly prohibited and can result in very hard to
72         debug memory corruptions. When in doubt consult one of this module
73         developers.
74 
75         Returns:
76             Pointer to stored data cast to struct type
77 
78     /**************************************************************************/
79 
80     public S* ptr ( )
81     {
82         verify(((&this).data.length == 0) || ((&this).data.length >= S.sizeof));
83 
84         if ((&this).data.length == 0)
85             return null;
86 
87         return cast(S*) (&this).data.ptr;
88     }
89 
90     /***************************************************************************
91 
92         Recursively iterates `this` and all referenced pointers / arrays and
93         verifies that data is indeed contiguous.
94 
95         Throws:
96             Exception if assumption is not verified
97 
98     ***************************************************************************/
99 
100     public void enforceIntegrity()
101     {
102         if ((&this).data.ptr)
103         {
104             enforceContiguous(*cast(S*) (&this).data.ptr, (&this).data);
105         }
106     }
107 
108     /***************************************************************************
109 
110         Length getter.
111 
112         Returns:
113             length of underlying data buffer
114 
115     ***************************************************************************/
116 
117     public size_t length()
118     {
119         return (&this).data.length;
120     }
121 
122     /***************************************************************************
123 
124         Resets length to 0 allowing same buffer to be used as null indicator
125         without creating new GC allocation later
126 
127     ***************************************************************************/
128 
129     public Contiguous!(S) reset()
130     {
131         this.data.length = 0;
132         enableStomping(this.data);
133         return this;
134     }
135 
136     debug(ContiguousIntegrity)
137     {
138         invariant()
139         {
140             // can't call this.enforceIntegrity because it will trigger
141             // invariant recursively being a public method
142 
143             if ((&this).data.length)
144             {
145                 enforceContiguous(*cast(S*) (&this).data.ptr, (&this).data);
146             }
147         }
148     }
149 }
150 
151 unittest
152 {
153     struct S { int x; }
154 
155     Contiguous!(S) instance;
156     instance.data = (cast(void*) new S)[0..S.sizeof];
157     instance.ptr.x = 42;
158 
159     instance.enforceIntegrity();
160 
161     test!("==")(
162         instance.data,
163         [ cast(ubyte)42, cast(ubyte) 0, cast(ubyte) 0, cast(ubyte) 0 ][]
164     );
165 
166     test!("==")(instance.length, 4);
167     instance.reset();
168     test!("==")(instance.length, 0);
169 }
170 
171 /*******************************************************************************
172 
173     Iterates over S members recursively and verifies that it only refers
174     to data inside of contiguous data chunk
175 
176     Params:
177         input = struct instance to verify
178         allowed_range = data buffer it must fit into
179 
180     Throws:
181         Exception if assumption is not verified
182 
183 *******************************************************************************/
184 
185 private void enforceContiguous (S) ( ref S input, in void[] allowed_range )
186 {
187     static assert (
188         is(S == struct),
189         "can't verify integrity of non-struct types"
190     );
191 
192     foreach (i, ref member; input.tupleof)
193     {
194         alias typeof(member) Member;
195 
196         static if (hasIndirections!(Member))
197         {
198             static if (is(Member U : U[]))
199             {
200                 // static + dynamic arrays
201 
202                 static if (is(Unqual!(Member) == U[]))
203                 {
204                     if (member.ptr)
205                     {
206                         enforceRange(member, allowed_range);
207                     }
208                 }
209 
210                 static if (is(U == struct))
211                 {
212                     foreach (ref element; member)
213                     {
214                         enforceContiguous(element, allowed_range);
215                     }
216                 }
217             }
218             else static if (is(Member == struct))
219             {
220                // member structs
221 
222                 enforceContiguous(member, allowed_range);
223             }
224             else
225             {
226                 alias ensureValueTypeMember!(S, i) evt;
227             }
228         }
229     }
230 }
231 
232 /*******************************************************************************
233 
234     Verifies that `slice` only refers to data inside `allowed_range`
235 
236     Params:
237         slice = array slice to verify
238         allowed_range = data buffer it must fit into
239 
240     Throws:
241         Exception if assumption is not verified
242 
243 *******************************************************************************/
244 
245 private void enforceRange(in void[] slice, in void[] allowed_range)
246 {
247     auto upper_limit = allowed_range.ptr + allowed_range.length;
248     enforce!(">=")(slice.ptr, allowed_range.ptr);
249     enforce!("<=")(slice.ptr, upper_limit);
250     enforce!("<=")(slice.ptr + slice.length, upper_limit);
251 }
252 
253 /*******************************************************************************
254 
255     Ensures that the type of the `i`th member of `S` (i.e. `S.tupleof[i]`) is a
256     value type; that is, it contains no references.
257 
258     Params:
259         S = an aggregate type (usually a struct)
260         i = the index of the aggregate member to check
261 
262 *******************************************************************************/
263 
264 package template ensureValueTypeMember ( S, size_t i )
265 {
266     alias ensureValueTypeMember!(S, i, typeof(S.tupleof)[i]) ensureValueTypeMember;
267 }
268 
269 /*******************************************************************************
270 
271     Ensures that `T`, which is a the nested type of the type of the `i`th member
272     of `S` (i.e. `S.tupleof[i]`),  is a value type; that is, it contains no
273     references.
274 
275     Params:
276         S = an aggregate type (usually a struct), for the message
277         i = the index of the aggregate member to check, for the message
278         T = the type that is expected to be a value type
279 
280 *******************************************************************************/
281 
282 package template ensureValueTypeMember ( S, size_t i, T )
283 {
284     alias typeof(S.tupleof)[i] M;
285 
286     static if (is (T == union))
287     {
288         static assert (!containsDynamicArray!(T),
289                        M.stringof ~ " " ~ S.tupleof[i].stringof ~
290                        " - unions containing dynamic arrays are not " ~
291                        "allowed, sorry");
292     }
293 
294     static assert(!hasIndirections!(T),
295                   M.stringof ~ " " ~ S.tupleof[i].stringof ~
296                   " is a or contains an unsupported reference type");
297 }
298 
299 version (UnitTest)
300     import core.stdc.string: memset;
301 
302 unittest
303 {
304     mixin(Typedef!(int, "MyInt"));
305 
306     // prepare structures
307     static struct S1
308     {
309         void[] arr;
310         MyInt[2][2] static_arr;
311     }
312 
313     static struct S2
314     {
315         int a, b, c;
316 
317         union
318         {
319             char x;
320             int y;
321         }
322 
323         S1 subs;
324     }
325 
326     // prepare data
327     void[] buffer = new void[100];
328     auto tested = cast(S2*) buffer.ptr;
329     tested.subs.arr = (buffer.ptr + S2.sizeof)[0..2];
330 
331     enforceContiguous(*tested, buffer);
332 
333     tested.subs.arr = new void[2];
334     testThrown!(Exception)(enforceContiguous(*tested, buffer));
335 
336     static struct S4
337     {
338         Const!(char[])[] str = ["Hello", "World"];
339     }
340 
341     auto tested2 = cast(S4*) memset(buffer.ptr, 0, buffer.length);
342 
343     *tested2 = S4.init;
344     test!("==")(tested2.str.length, 2);
345     testThrown!(Exception)(enforceContiguous(*tested2, buffer));
346 }
347 
348 unittest
349 {
350     static struct S { int x; }
351     Contiguous!(S) s;
352     s.data = new void[42];
353     s.data.length = 0;
354     test!("==")(s.ptr(), null);
355 }