Hi all.

I can't seem to get this to work properly.  I'm trying to write a
copy-on-write proxy type.  I'm using postblit and dtors along with the
usual ctors and opAssign to manage the refcount of the shared memory.
However, testing with dmd-2.026 (and a few previous versions a little
while ago), the attached program gives me the following output:

(NB: ">" means entering, "<" means leaving; these messages are printed
from in- and out-contracts.  Printed values are the array being stored
and the ref count (or zero if the ref count pointer is null.))

> opAssign(T[]);            COWArray(0x    0000[0..0]) @ 0
> ~this();                  COWArray(0x  12FE88[0..4424652]) @ 1245120
< ~this();                  COWArray(0x  12FE88[0..4424652]) @ 1245119
< opAssign(T[]);            COWArray(0x  AA2E40[0..4]) @ 1
> this(this);               COWArray(0x  AA2E40[0..4]) @ 1
< this(this);               COWArray(0x  AA2E40[0..4]) @ 2
a: [1,2,3,4]
> ~this();                  COWArray(0x  AA2E40[0..4]) @ 2
< ~this();                  COWArray(0x  AA2E40[0..4]) @ 1

For the following code:

void main()
{
    COWArray!(int) a;
    a = [1,2,3,4];

    // This shouldn't affect ref counts
    auto a_str = a.toString;
    writefln("a: %s", a_str);
}

As you can see, it correctly enters opAssign and leaves with the correct
array reference and ref count.  However, it also spuriously calls the
destructor with what appears to be a random pointer.  It then calls the
postblit when it shouldn't be doing so, resulting in the refcount being
one too high.

I've attached the full source (compile with -debug); does anyone know
what I'm doing wrong, or whether this is a compiler bug?

  -- Daniel

module cow_bug;

import std.string : format;
import std.stdio : writefln, writef;

struct COWArray(T)
{
    private
    {
        T[] arr = null;
        size_t* ctr = null;
    }

    /*
     * Returns cowarray definition as a string for debugging
     */
    debug private string stat()
    {
        return format("COWArray(0x%08x[0..%d]) @ %d",
                arr.ptr, arr.length,
                ctr ? *ctr : 0);
    }

    /*
     * Post-blit function; increment reference count since we've just been
     * copied.
     */
    this(this)
    in{debug  writefln("> this(this);               ",stat);}
    out{debug writefln("< this(this);               ",stat);}
    body
    {
        ++ *(this.ctr);
    }

    /*
     * Dtor; decrement ref count, destroy memory if the counter is zero.
     */
    ~this()
    in{debug  writefln("> ~this();                  ",stat);}
    out{debug writefln("< ~this();                  ",stat);}
    body
    {
        // Accounts for empty cowarrays.
        if( this.ctr )
        {
            -- *(this.ctr);
    
            if( *(this.ctr) == 0 )
                delete this.ctr;
        }
    }

    const size_t length()
    {
        return arr.length;
    }

    COWArray opAssign(T[] arr)
    in{debug  writefln("> opAssign(T[]);            ",stat);}
    out{debug writefln("< opAssign(T[]);            ",stat);}
    body
    {
        // If we already have an array, dec ref, destroy if counter == 0.
        if( this.ctr !is null )
        {
            -- *(this.ctr);
    
            if( *(this.ctr) == 0 )
                delete this.ctr;
        }

        // Attach to new array, assume no aliasing.
        this.arr = arr;
        this.ctr = new size_t;
        *(this.ctr) = 1;

        return this;
    }

    string toString()
    {
        return format("%s", this.arr);
    }
}

void main()
{
    // New array, no ctor, so it's not pointing at anything, and has zero
    // length.
    COWArray!(int) a;

    // opAssign array; should have a single reference.
    a = [1,2,3,4];

    // This shouldn't affect ref counts
    auto a_str = a.toString;
    writefln("a: %s", a_str);

    // Should have one reference, zero after the dtor, and the memory should
    // be deleted.
}

Reply via email to