not shooting oneself in the foot

[email protected] (Nicholas Clark) Thu, 28 Apr 2005 18:07:24 +0100
Newsgroups perl.ponie.dev
Message-ID <[email protected]>
For reasons hinted at in in the commit message of
http://www.nntp.perl.org/group/perl.ponie.changes/104

until we have proper stack tracing GC, ponie has to have a hack to cope with
some assumptions that (classic) perl makes about SV memory.

Specifically perl never actually free()s back to the system memory used for SV
heads, and on returning ("plant"ing) SV heads back in its stock for re-use,
sets the type to SvTYPEMASK. Other parts of the perl code are lazy, arguably
buggy, and sometimes still hold pointers to these now-free heads, so contain
work around checks for SvTYPEMASK and silently skip the scalar in that case.
This means that things go wrong if you immediately free scalars, so the code
added last June uses PL_sv_root to hold them until freetmps() time at the
end of each scope.

However, I've realised now why I think that it's biting me. There is code
to go through all the active scalars and free them. It ends up calling
S_ptr_table_visit, which iterates over the internal hash used to track the
active pointers

In the middle is this call:

        if (entry) {
	    SV *sv = entry->oldval;
	    entry = entry->next;
	    /* This call may well delete the current entry.  */
	    if (SvTYPE(sv) != SVTYPEMASK
		&& (((U32)Parrot_PMC_get_intval_intkey(PL_Parrot,MUMBLE(sv), Ponie_I_SV_FLAGS)) & mask) == flags
		&& SvREFCNT(sv)) {
		(FCALL)(aTHX_ sv);
		++visited;
	    }
	}


that comment is perfectly correct - the call FCALL might delete entry, hence
the C<entry = entry->next;> being ahead of it, rather than beyond it as I
originally wrote. So the obvious trap is avoided.

*However*, there is, I believe, still a bug, a far more subtle bug. The
FCALL can cause other actions, which can include a call to SvREFCNT_dec() on
a different SV, which can call S_del_SV, which in turn can call
ptr_table_delete on something else somewhere in this hash. And I believe that
the problem is when it just happens to delete the "next" entry, the one
carefully already put into "entry"

The bug manifests itself as a SEGV with the pointer sv having the value 17.
And absolutely no clue as to why. The actual answer seems to be that 17 is a
value written into the memory when it's passed to free() - the structure
pointed to by C<entry> in the above code has been free() within FCALL.

So my question is, is there a good standard defensive technique to iterate
over a linked list, where that linked list might also have elements deleted
from underneath you?

I guess I could restart the list every time a deletion is made, tracking
deletions with a global generation count, but I keep thinking that this might
hit an infinite loop if FCALL does something unfortunate that keeps causing
deletes. (One possibility being calls to -DDEBUGGING code, which I know can
allocate temporary SVs for use as buffers)

Suggestions?

Nicholas Clark