1 /**
2    A unique pointer.
3  */
4 module automem.unique;
5 
6 import automem.test_utils: TestUtils;
7 import automem.traits: isAllocator;
8 import stdx.allocator: theAllocator;
9 import std.typecons: Flag;
10 
11 version(unittest) {
12     import unit_threaded;
13     import test_allocator: TestAllocator;
14 }
15 
16 mixin TestUtils;
17 
18 /**
19    A unique pointer similar to C++'s std::unique_ptr.
20  */
21 struct Unique(Type, Allocator = typeof(theAllocator()),
22     Flag!"supportGC" supportGC = Flag!"supportGC".yes)
23 if(isAllocator!Allocator) {
24 
25     import std.traits: hasMember;
26     import std.typecons: Proxy;
27 
28     enum isSingleton = hasMember!(Allocator, "instance");
29     enum isTheAllocator = is(Allocator == typeof(theAllocator));
30     enum isGlobal = isSingleton || isTheAllocator;
31 
32     static if(is(Type == class))
33         alias Pointer = Type;
34     else
35         alias Pointer = Type*;
36 
37     static if(isGlobal) {
38 
39         /**
40            The allocator is global, so no need to pass it in to the constructor
41         */
42         this(Args...)(auto ref Args args) {
43             this.makeObject!(supportGC, args)();
44         }
45 
46     } else {
47 
48         /**
49            Non-singleton allocator, must be passed in
50          */
51         this(Args...)(Allocator allocator, auto ref Args args) {
52             _allocator = allocator;
53             this.makeObject!(supportGC, args)();
54         }
55     }
56 
57 
58     static if(isGlobal)
59         /**
60             Factory method so can construct with zero args.
61         */
62         static typeof(this) construct(Args...)(auto ref Args args) {
63             static if (Args.length != 0)
64                 return typeof(return)(args);
65             else {
66                 typeof(return) ret;
67                 ret.makeObject!(supportGC)();
68                 return ret;
69             }
70         }
71     else
72         /**
73             Factory method. Not necessary with non-global allocator
74             but included for symmetry.
75         */
76         static typeof(this) construct(Args...)(auto ref Allocator allocator, auto ref Args args) {
77             return typeof(return)(allocator, args);
78         }
79 
80     ///
81     this(T)(Unique!(T, Allocator) other) if(is(T: Type)) {
82         moveFrom(other);
83     }
84 
85     ///
86     @disable this(this);
87 
88     ///
89     ~this() {
90         deleteObject;
91     }
92 
93     /**
94        Gets the owned pointer. Use with caution.
95      */
96     inout(Pointer) get() inout @system {
97         return _object;
98     }
99 
100     /**
101        Releases ownership and transfers it to the returned
102        Unique object.
103      */
104     Unique unique() {
105         import std.algorithm: move;
106         Unique u;
107         move(this, u);
108         assert(_object is null);
109         return u;
110     }
111 
112     /// Release ownership
113     package Pointer release() {
114         auto ret = _object;
115         _object = null;
116         return ret;
117     }
118 
119     ///
120     package Allocator allocator() {
121         return _allocator;
122     }
123 
124     /**
125        "Truthiness" cast
126      */
127     bool opCast(T)() const if(is(T == bool)) {
128         return _object !is null;
129     }
130 
131     /// Move from another smart pointer
132     void opAssign(T)(Unique!(T, Allocator) other) if(is(T: Type)) {
133         deleteObject;
134         moveFrom(other);
135     }
136 
137     mixin Proxy!_object;
138 
139 private:
140 
141     Pointer _object;
142 
143     static if(isSingleton)
144         alias _allocator = Allocator.instance;
145     else static if(isTheAllocator)
146         alias _allocator = theAllocator;
147     else
148         Allocator _allocator;
149 
150     void deleteObject() @safe {
151         import automem.allocator: dispose;
152         import std.traits: isPointer;
153         import std.traits : hasIndirections;
154         import core.memory : GC;
155 
156         static if(isPointer!Allocator)
157             assert(_object is null || _allocator !is null);
158 
159         if(_object !is null) () @trusted { _allocator.dispose(_object); }();
160         static if (is(Type == class)) {
161             // need to watch the monitor pointer even if supportGC is false.
162             () @trusted {
163                 auto repr = (cast(void*)_object)[0..__traits(classInstanceSize, Type)];
164                 GC.removeRange(&repr[(void*).sizeof]);
165             }();
166         } else static if (supportGC && hasIndirections!Type) {
167             () @trusted {
168                 GC.removeRange(_object);
169             }();
170         }
171     }
172 
173     void moveFrom(T)(ref Unique!(T, Allocator) other) if(is(T: Type)) {
174         _object = other._object;
175         other._object = null;
176 
177         static if(!isGlobal) {
178             import std.algorithm: move;
179             _allocator = other._allocator.move;
180         }
181     }
182 }
183 
184 private template makeObject(Flag!"supportGC" supportGC, args...)
185 {
186     void makeObject(Type,A)(ref Unique!(Type, A) u) {
187         import stdx.allocator: make;
188         import std.functional : forward;
189         import std.traits : hasIndirections;
190         import core.memory : GC;
191 
192         u._object = u._allocator.make!Type(forward!args);
193 
194         static if (is(Type == class)) {
195             () @trusted {
196                 auto repr = (cast(void*)u._object)[0..__traits(classInstanceSize, Type)];
197                 if (supportGC && !(typeid(Type).m_flags & TypeInfo_Class.ClassFlags.noPointers)) {
198                     GC.addRange(&repr[(void*).sizeof],
199                             __traits(classInstanceSize, Type) - (void*).sizeof);
200                 } else {
201                     // need to watch the monitor pointer even if supportGC is false.
202                     GC.addRange(&repr[(void*).sizeof], (void*).sizeof);
203                 }
204             }();
205         } else static if (supportGC && hasIndirections!Type) {
206             () @trusted {
207                 GC.addRange(u._object, Type.sizeof);
208             }();
209         }
210     }
211 }
212 
213 ///
214 @("with struct and test allocator")
215 @system unittest {
216 
217     auto allocator = TestAllocator();
218     {
219         const foo = Unique!(Struct, TestAllocator*)(&allocator, 5);
220         foo.twice.shouldEqual(10);
221         allocator.numAllocations.shouldEqual(1);
222         Struct.numStructs.shouldEqual(1);
223     }
224 
225     Struct.numStructs.shouldEqual(0);
226 }
227 
228 @("with class and test allocator")
229 @system unittest {
230 
231     auto allocator = TestAllocator();
232     {
233         const foo = Unique!(Class, TestAllocator*)(&allocator, 5);
234         foo.twice.shouldEqual(10);
235         allocator.numAllocations.shouldEqual(1);
236         Class.numClasses.shouldEqual(1);
237     }
238 
239     Class.numClasses.shouldEqual(0);
240 }
241 
242 ///
243 @("with struct and mallocator")
244 @system unittest {
245 
246     import stdx.allocator.mallocator: Mallocator;
247     {
248         const foo = Unique!(Struct, Mallocator)(5);
249         foo.twice.shouldEqual(10);
250         Struct.numStructs.shouldEqual(1);
251     }
252 
253     Struct.numStructs.shouldEqual(0);
254 }
255 
256 
257 @("default constructor")
258 @system unittest {
259     auto allocator = TestAllocator();
260 
261     auto ptr = Unique!(Struct, TestAllocator*)();
262     (cast(bool)ptr).shouldBeFalse;
263     ptr.get.shouldBeNull;
264 
265     ptr = Unique!(Struct, TestAllocator*)(&allocator, 5);
266     ptr.get.shouldNotBeNull;
267     ptr.get.twice.shouldEqual(10);
268     (cast(bool)ptr).shouldBeTrue;
269 }
270 
271 @(".init")
272 @system unittest {
273     auto allocator = TestAllocator();
274 
275     Unique!(Struct, TestAllocator*) ptr;
276     (cast(bool)ptr).shouldBeFalse;
277     ptr.get.shouldBeNull;
278 
279     ptr = Unique!(Struct, TestAllocator*)(&allocator, 5);
280     ptr.get.shouldNotBeNull;
281     ptr.get.twice.shouldEqual(10);
282     (cast(bool)ptr).shouldBeTrue;
283 }
284 
285 @("move")
286 @system unittest {
287     import std.algorithm: move;
288 
289     auto allocator = TestAllocator();
290     auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5);
291     Unique!(Struct, TestAllocator*) newPtr = oldPtr.move;
292     oldPtr.shouldBeNull;
293     newPtr.twice.shouldEqual(10);
294     Struct.numStructs.shouldEqual(1);
295 }
296 
297 @("copy")
298 @system unittest {
299     auto allocator = TestAllocator();
300     auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5);
301     Unique!(Struct, TestAllocator*) newPtr;
302     // non-copyable
303     static assert(!__traits(compiles, newPtr = oldPtr));
304 }
305 
306 @("construct base class")
307 @system unittest {
308     auto allocator = TestAllocator();
309     {
310         Unique!(Object, TestAllocator*) bar = Unique!(Class, TestAllocator*)(&allocator, 5);
311         Class.numClasses.shouldEqual(1);
312     }
313 
314     Class.numClasses.shouldEqual(0);
315 }
316 
317 @("assign base class")
318 @system unittest {
319     auto allocator = TestAllocator();
320     {
321         Unique!(Object, TestAllocator*) bar;
322         bar = Unique!(Class, TestAllocator*)(&allocator, 5);
323         Class.numClasses.shouldEqual(1);
324     }
325 
326     Class.numClasses.shouldEqual(0);
327 }
328 
329 @("Return Unique from function")
330 @system unittest {
331     auto allocator = TestAllocator();
332 
333     auto produce(int i) {
334         return Unique!(Struct, TestAllocator*)(&allocator, i);
335     }
336 
337     auto ptr = produce(4);
338     ptr.twice.shouldEqual(8);
339 }
340 
341 @("unique")
342 @system unittest {
343     auto allocator = TestAllocator();
344     auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5);
345     auto newPtr = oldPtr.unique;
346     newPtr.twice.shouldEqual(10);
347     oldPtr.shouldBeNull;
348 }
349 
350 @("@nogc")
351 @system @nogc unittest {
352 
353     import stdx.allocator.mallocator: Mallocator;
354 
355     {
356         const ptr = Unique!(NoGcStruct, Mallocator)(5);
357         // shouldEqual isn't @nogc
358         assert(ptr.i == 5);
359         assert(NoGcStruct.numStructs == 1);
360     }
361 
362     assert(NoGcStruct.numStructs == 0);
363 }
364 
365 @("@nogc @safe")
366 @safe @nogc unittest {
367 
368     auto allocator = SafeAllocator();
369 
370     {
371         const ptr = Unique!(NoGcStruct, SafeAllocator)(SafeAllocator(), 6);
372         // shouldEqual isn't @nogc
373         assert(ptr.i == 6);
374         assert(NoGcStruct.numStructs == 1);
375     }
376 
377     assert(NoGcStruct.numStructs == 0);
378 }
379 
380 @("deref")
381 @system unittest {
382     {
383         auto allocator = TestAllocator();
384         auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 5);
385         *ptr = Struct(13);
386         ptr.twice.shouldEqual(26);
387         Struct.numStructs.shouldEqual(1);
388     }
389     Struct.numStructs.shouldEqual(0);
390 }
391 
392 @("move from populated other unique")
393 @system unittest {
394 
395     import std.algorithm: move;
396 
397     {
398         auto allocator = TestAllocator();
399 
400         auto ptr1 = Unique!(Struct, TestAllocator*)(&allocator, 5);
401         Struct.numStructs.shouldEqual(1);
402 
403         {
404             auto ptr2 = Unique!(Struct, TestAllocator*)(&allocator, 10);
405             Struct.numStructs.shouldEqual(2);
406             ptr1 = ptr2.move;
407             Struct.numStructs.shouldEqual(1);
408             ptr2.shouldBeNull;
409             ptr1.twice.shouldEqual(20);
410         }
411 
412     }
413 
414     Struct.numStructs.shouldEqual(0);
415 }
416 
417 @("assign to rvalue")
418 @system unittest {
419 
420     import std.algorithm: move;
421 
422     {
423         auto allocator = TestAllocator();
424 
425         auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 5);
426         ptr = Unique!(Struct, TestAllocator*)(&allocator, 7);
427 
428         Struct.numStructs.shouldEqual(1);
429         ptr.twice.shouldEqual(14);
430     }
431 
432     Struct.numStructs.shouldEqual(0);
433 }
434 
435 
436 @("theAllocator")
437 @system unittest {
438     with(theTestAllocator){
439         auto ptr = Unique!Struct(42);
440         (*ptr).shouldEqual(Struct(42));
441         Struct.numStructs.shouldEqual(1);
442     }
443 
444     Struct.numStructs.shouldEqual(0);
445 }
446 
447 
448 @("@nogc class destructor")
449 @nogc unittest {
450 
451     auto allocator = SafeAllocator();
452 
453     {
454         const ptr = Unique!(NoGcClass, SafeAllocator)(SafeAllocator(), 6);
455         // shouldEqual isn't @nogc
456         assert(ptr.i == 6);
457         assert(NoGcClass.numClasses == 1);
458     }
459 
460     assert(NoGcClass.numClasses == 0);
461 }
462 
463 @("Construct Unique using global allocator for struct with zero-args ctor")
464 @system unittest {
465     struct S {
466         private ulong zeroArgsCtorTest = 3;
467     }
468     auto s = Unique!S.construct();
469     static assert(is(typeof(s) == Unique!S));
470     assert(s._object !is null);
471     assert(s.zeroArgsCtorTest == 3);
472 }
473 
474 
475 @("release")
476 @system unittest {
477     import stdx.allocator: dispose;
478     import core.exception: AssertError;
479 
480     try {
481         auto allocator = TestAllocator();
482         auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 42);
483         ptr.release;
484         Struct.numStructs.shouldEqual(0);
485     } catch(AssertError _) { // TestAllocator should throw due to memory leak
486         return;
487     }
488 
489     assert(0); // should throw above
490 }