www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Presenting the Turducken Type Technique: Certified

reply FeepingCreature <feepingcreature gmail.de> writes:
// So I was working on Nullable,
// and I thought: how could this be made to work better?
// The following code runs on DMD master,
// and it is, technically, `safe`;
// but it REALLY REALLY shouldn't be.
module turducken;

import std.algorithm;
import std.datetime;

// The Turducken Type Technique is the answer to a challenge.
// How do we perform late binding and early unbinding
// on a type that's really, really evil?

// First of all, let's set the stage.
// We're going to construct the Type from Hell.
// It's a struct value type, giving us the
// greatest number of avenues to be horrible.
struct Satan
{
     // It has an immutable value field, referencing immutable 
data.
     immutable int[] i;

     // It has a SysTime: it's caused issues for
     // me in the past and I hold a grudge.
     SysTime st;

     // It has no default constructor.
      disable this();

     // It has an .init that violates its invariant.
     bool properlyInitialized = false;

     invariant { assert(properlyInitialized); }

     // It has a copy assignment overload,
     // specifically so we can forbid copy assignment.
     void opAssign(Satan) { assert(false); }

     // To confirm that every constructor call matches one 
destructor call,
     // it counts references.
     int *refs;

     this(int* refs)
     pure  safe  nogc
     {
         this.properlyInitialized = true;
         this.refs = refs;
         (*refs)++;
     }

     this(this)
     pure  safe  nogc
     { (*refs)++; }

     // Since a destructor is defined, we will definitely
     // assert out if the .init value is ever destructed.
     ~this()
     pure  safe  nogc
     in(refs)
     out(; *refs >= 0)
     do { (*refs)--; }
}

// Now, this is the challenge.

// In a function that is
pure // pure,
 safe // safe,
 nogc // and nogc:
unittest
{
     // perform late binding and early unbinding of the Type from 
Hell.

     // Let's do some prep.

     // (for validation)
     int refs;

     // (cheat a bit)
     int* refsp = ()  trusted { return &refs; }();

     {
         // We start with a default initialized variable.
         Turducken!Satan magic;

         // we bind it to a constructed value
         magic.bind(Satan(refsp));

         // There's now exactly one copy of Satan in the world:
         assert(refs == 1);

         // Just for laughs, bind over it:
         magic.bind(Satan(refsp));

         // Still only one copy around.
         assert(refs == 1);

         // all is well with the contained value
         assert(magic.value.properlyInitialized);

         // Now we unbind it before scope end
         magic.unbind;

         // Satan is gone now.
         assert(refs == 0);
     }
     // And he was destroyed exactly once, as is proper.
     assert(refs == 0);
}

// How did we do this?
// Turducken!
struct Turducken(T)
{
     // The delicious center.
     alias Chicken = T;

     // Union ensures that the T destructor is not called.
     union Duck
     {
         Chicken chicken; // chicken chicken? chicken!
     }

     // Struct ensures that moveEmplace and its magic
     // constness "tolerant" (violating) memcpy can be used.
     struct Turkey
     {
         Duck duck;
     }

     Turkey store = Turkey.init; // take you to the turkey store

     bool set = false;

     // Stick a straw in it to get at the delicious gooey center.
      property ref T value() in(set) { return store.duck.chicken; }

     // And the special sauce:
     void bind(T value)
     {
         // clean up in case we're back for seconds
         unbind;

         // Make a destructor-protected copy to stick in our store
         Turkey wrapper = Turkey(Duck(value));

         // Plow over the existing data in total disregard of 
constness
         ()  trusted { moveEmplace(wrapper, store); }();

         set = true;
     }

     // Various condiments:
     void unbind()
     {
         if (set)
         {
             static if (is(T == struct)) {
                 destroy(value);
             }
             set = false;
         }
     }

     // Since we have shielded our value from D's watchful eyes,
     // we have to remember to clean up manually
     // Clean your dishes!
     ~this()
     {
         unbind;
     }
}

// And that's the Turducken Type Technique. Deliciously evil!
Jul 11 2018
next sibling parent FeepingCreature <feepingcreature gmail.de> writes:
Oh yeah, two notes.

First: you might say "it's easy to be un safe if you use 
 trusted." However, the  trusted part is both well-defined and 
positively pedestrian, since we are either targeting default 
initialized memory or just ran destroy() on it (which by the way 
is  safe) so really, if you're holding a reference past that 
*you're* the one doing undefined things.

Second: since I forgot, credit  n8sh on github for pointing me at 
the union technique, though the blame for the current incarnation 
of the trick is all mine.
Jul 11 2018
prev sibling parent Anton Fediushin <fediushin.anton yandex.com> writes:
On Wednesday, 11 July 2018 at 18:36:24 UTC, FeepingCreature wrote:
 // So I was working on Nullable,
 // and I thought: how could this be made to work better?
 // The following code runs on DMD master,
 // and it is, technically, `safe`;
 // but it REALLY REALLY shouldn't be.
[...]
 // And that's the Turducken Type Technique. Deliciously evil!
That's a thing of beauty. I really want to try this in one of my projects.
Jul 11 2018