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 }