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 }