Remove thr->callstack as a monolithic array and replace it with a linked list
of duk_activations. thr->callstack_curr is the current call (or NULL if no
call is in progress), and act->parent chains to a previous call or NULL.
thr->callstack_top is kept because it's needed by some internals at present;
it may be removed in the future.
* Merge duk_api_public.h.in to duktape.h.in.
* Adjust define order so that duk_config.h sees DUK_VERSION which allows
user config fixups to react to it.
Tweak mark-and-sweep so that if finalizers are present (heap->finalize_list
is not NULL), rescue decisions are postponed (free decisions are not).
In concrete terms this means that objects normally rescued keep their
FINALIZED flag so that their finalizer won't be called again if the object
turns out to be unreachable in a later run.
This wasn't necessary before: finalize_list only contained unreachable
objects so nothing could point to them while we marked heap_allocated.
But when duk_push_heapptr() is allowed to push unreachable pointers
(which are pending finalization), it's possible for an object in
heap_allocated to point to an object on finalize_list, which also means
that the latter object can get a TEMPROOT flag.
A pointer to the value stack was obtained before duk_push_bare_object()
and used after the push. If value stack resize happens as a side effect
of the push (mark-and-sweep, finalizers, etc) the 'tv' pointer could be
stale. Found using torture tests.
Avoid pushing anything on the value stack when handling a double error. This
avoids the case where there's no pre-allocated space on the value stack for
pushing the double error. This is rather theoretical however because there
should always be spare for the error -- but in error paths it's best to have
minimal assumptions.
Also reorganize the error throwing a bit for clarity.
Before the change refcounts would be temporarily (and harmlessly) out of sync
when abandoning an object's array part. Change the handling so that refcounts
remain in sync at all times, so that GC refcount assertions agree with them.
Popping without DECREF allows "stealing" of refcounts when temporaries are
first placed on the value stack (useful e.g. in property table realloc when
array part is abandoned).
Add INCREF/DECREF to thr->resumer when resuming and yielding coroutines.
Before this fix mark-and-sweep would consider thr->resumer a strong reference
and marked it, but the refcount was not updated. This caused mark-and-sweep's
computed assert refcount to be off compared to the stored refcount.
This doesn't have a functional effect in practice: when a thread is resumed,
its refcount remains > 0 for the duration of the resume because a reference
exists in the caller's value stack.
* Add a h_assert_refcount field to duk_heaphdr when assertions are enabled.
* When doing mark-and-sweep, clear h_assert_refcount, perform mark-and-sweep
processing normally, and assert for correct refcounts for objects that
remain in heap_allocated after sweeping. (Refcounts for objects prior to
sweeping won't match those computed via reachability roots.)
* Improve FASTREFS asserts for refcounting and mark-and-sweep.
When the flag is set, there is either no subclass C struct for the
duk_hobject, or there is a subclass C struct but there are no references
needing DECREF/marking in the struct.
This allows DECREF and mark-and-sweep to handle duk_hobjects with less
overhead for the common cases of plain objects and arrays (and some other
less commonly collected structs like duk_hnatfunc).
Also change Duktape.Thread.prototype internal class from Thread to Object:
with the other changes internal code now assumes that if an object's class
is Thread, it has the duk_hthread memory layout which wouldn't be the case
for Duktape.Thread.prototype.
The refcount field must be chosen so that there's never any wrapping,
otherwise memory unsafe behavior will happen.
For the default configuration a size_t is used for refcount; it's
guaranteed not to wrap because there can't be enough references to
wrap it. For low memory targets it's possible to configure 16-bit
refcounts. This is fine as long as memory pools are small enough
to never contain more than 2^16-1 references.
One bottleneck in refzero and mark-and-sweep handling is checking whether an
object has an own or inherited _Finalizer property. This check walked the
prototype chain and did a property lookup for every object. Because a
finalizer is usually not present, the prototype chain would almost always be
walked to completion.
Improve this behavior by:
* Adding a DUK_HOBJECT_FLAG_HAVE_FINALIZER flag. The flag is set when the
object has an own _Finalizer property with a callable value, and cleared
otherwise. The flag is *only* set by duk_set_finalizer(), so any other
means of changing the internal _Finalizer property will leave the flag out
of sync (which causes a finalizer run to be skipped).
* Adding duk_hobject_has_finalizer_fast() which checks for finalizer existence
by walking the prototype chain, but only checking the flag, not the property
table.
* Use the fast finalizer check in refzero and mark-and-sweep.
Out-of sync cases:
* If the flag is set but there is no actual finalizer, the object will go
through finalizer processing when garbage collecting. This is harmless:
the finalizer call will fail and the object will be garbage collected, but
with some potential delay (especially for mark-and-sweep).
* If the flag is cleared but there is an actual finalizer, the finalizer will
be ignored.
Related changes:
* When duk_dump_function() is called, zero DUK_HOBJECT_FLAG_HAVE_FINALIZER on
serialization, so it won't be set when the function is loaded back. If this
is not done, the loaded function will (harmlessly) go through finalizer
processing when garbage collected.
* Update debugger artificial properties to include "have_finalizer" flag.
Other changes:
* A few DUK_UNLIKELY() attributes for prototype sanity limits which are
almost never hit.
The Durango platform is used by the Xbox One.
This platform currently differs from the Windows platform only in that
it does not support the SystemTimeToTzSpecificLocalTime API.
Throw notify might be thrown from an empty callstack e.g. if duk_throw()
is called by C code outside of any call. If the throw argument is not an
error instance, the code would attempt to access the topmost activation
even if one didn't exist.
A potentially stale activation pointer was used in pre/post inc/dec handling.
If the callstack got resized, the 'act' could be a stale pointer leading to
memory unsafe behavior.