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.meta.types.Qualifiers;
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         assumeSafeAppend(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 {
301     import core.stdc..string: memset;
302     import ocean.meta.types.Typedef;
303 
304 }
305 
306 unittest
307 {
308     mixin(Typedef!(int, "MyInt"));
309 
310     // prepare structures
311     static struct S1
312     {
313         void[] arr;
314         MyInt[2][2] static_arr;
315     }
316 
317     static struct S2
318     {
319         int a, b, c;
320 
321         union
322         {
323             char x;
324             int y;
325         }
326 
327         S1 subs;
328     }
329 
330     // prepare data
331     void[] buffer = new void[100];
332     auto tested = cast(S2*) buffer.ptr;
333     tested.subs.arr = (buffer.ptr + S2.sizeof)[0..2];
334 
335     enforceContiguous(*tested, buffer);
336 
337     tested.subs.arr = new void[2];
338     testThrown!(Exception)(enforceContiguous(*tested, buffer));
339 
340     static struct S4
341     {
342         const(char[])[] str = ["Hello", "World"];
343     }
344 
345     auto tested2 = cast(S4*) memset(buffer.ptr, 0, buffer.length);
346 
347     *tested2 = S4.init;
348     test!("==")(tested2.str.length, 2);
349     testThrown!(Exception)(enforceContiguous(*tested2, buffer));
350 }
351 
352 unittest
353 {
354     static struct S { int x; }
355     Contiguous!(S) s;
356     s.data = new void[42];
357     s.data.length = 0;
358     test!("==")(s.ptr(), null);
359 }