Remember all the fun we had last year when I failed to heed the warning in the toStringz documentation about retaining a reference to a char * passed into C? It took a long time to find that one, with a lot of help from Steve Schveighoffer and others.
Well, I've got another one. Consider this:
// Get number of children of the parent account
auto gc_protect = bind_text(n_children_stmt, 1, parent.guid);
parent.children.length = one_row!(int)(n_children_stmt, &get_int);
auto gc_protect2 = bind_text(account_child_stmt, 1, parent.guid);
for (int i = 0; next_row_available_p(account_child_stmt, &sqlite3_reset); i++) {
parent.children[i] = new Account;
parent.children[i].name = fromStringz(sqlite3_column_text(account_child_stmt, 0)).idup;
parent.children[i].guid = fromStringz(sqlite3_column_text(account_child_stmt, 1)).idup;
parent.children[i].flags = sqlite3_column_int(account_child_stmt, 2);
parent.children[i].value = get_account_value(parent.children[i]);
}
bind_text takes a D string, turns it into a C string with toStringz, uses that to call sqlite3_bind_text and returns the C string, which I store as you can see with the intention of protecting it from the gc. The code as written above does not work. At some point, I get an index-out-of-bounds error, because the loop is seeing too many children. If I turn off the GC, the code works correctly and the application completes normally.
With the GC on, if I put a debugging writeln inside the loop, right after the 'for', that prints, among other things, the value of gc_protect2 (I wanted to convince myself that the GC wasn't moving what it points to; yes, I know the documentation says the current GC won't do that), the problem goes away. A Heisenbug!
My theory: because gc_protect2 is never referenced, I'm guessing that the compiler is optimizing away the storage of the returned pointer, the supporting evidence being what I said in the previous paragraph. Anyone have a better idea?
By the way, I get the same error compiling this with dmd or ldc.
/Don Allen