DUK_INTERNAL void duk_heaphdr_refcount_finalize(duk_hthread *thr, duk_heaphdr *hdr) {
	DUK_ASSERT(hdr);

	switch (DUK_HEAPHDR_GET_TYPE(hdr)) {
	case DUK_HTYPE_OBJECT:
		duk__refcount_finalize_hobject(thr, (duk_hobject *) hdr);
		break;
	case DUK_HTYPE_BUFFER:
		/* nothing to finalize */
		break;
	case DUK_HTYPE_STRING:
		/* cannot happen: strings are not put into refzero list (they don't even have the next/prev pointers) */
	default:
		DUK_UNREACHABLE();
	}
}
Example #2
0
DUK_LOCAL void duk__free_run_finalizers(duk_heap *heap) {
	duk_hthread *thr;
	duk_heaphdr *curr;
#ifdef DUK_USE_DEBUG
	duk_size_t count_obj = 0;
#endif

	DUK_ASSERT(heap != NULL);
	DUK_ASSERT(heap->heap_thread != NULL);
#ifdef DUK_USE_REFERENCE_COUNTING
	DUK_ASSERT(heap->refzero_list == NULL);  /* refzero not running -> must be empty */
#endif
#ifdef DUK_USE_MARK_AND_SWEEP
	DUK_ASSERT(heap->finalize_list == NULL);  /* mark-and-sweep not running -> must be empty */
#endif

	/* XXX: here again finalizer thread is the heap_thread which needs
	 * to be coordinated with finalizer thread fixes.
	 */
	thr = heap->heap_thread;
	DUK_ASSERT(thr != NULL);

	curr = heap->heap_allocated;
	while (curr) {
		if (DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT) {
			/* Only objects in heap_allocated may have finalizers.  Check that
			 * the object itself has a _Finalizer property so that we don't
			 * execute finalizers for e.g. Proxy objects.
			 */
			DUK_ASSERT(thr != NULL);
			DUK_ASSERT(curr != NULL);

			if (duk_hobject_hasprop_raw(thr, (duk_hobject *) curr, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) {
				duk_hobject_run_finalizer(thr, (duk_hobject *) curr);
			}
#ifdef DUK_USE_DEBUG
			count_obj++;
#endif
		}
		curr = DUK_HEAPHDR_GET_NEXT(curr);
	}

	/* Note: count includes all objects, not only those with an actual finalizer. */
#ifdef DUK_USE_DEBUG
	DUK_D(DUK_DPRINT("checked %ld objects for finalizers before freeing heap", (long) count_obj));
#endif
}
Example #3
0
static void duk__compact_object_list(duk_heap *heap, duk_hthread *thr, duk_heaphdr *start, int *p_count_check, int *p_count_compact, int *p_count_bytes_saved) {
#else
static void duk__compact_object_list(duk_heap *heap, duk_hthread *thr, duk_heaphdr *start) {
#endif
	duk_heaphdr *curr;
#ifdef DUK_USE_DEBUG
	size_t old_size, new_size;
#endif
	duk_hobject *obj;

	DUK_UNREF(heap);

	curr = start;
	while (curr) {
		DUK_DDD(DUK_DDDPRINT("mark-and-sweep compact: %p", (void *) curr));

		if (DUK_HEAPHDR_GET_TYPE(curr) != DUK_HTYPE_OBJECT) {
			goto next;	
		}
		obj = (duk_hobject *) curr;

#ifdef DUK_USE_DEBUG
		old_size = DUK_HOBJECT_P_COMPUTE_SIZE(obj->e_size, obj->a_size, obj->h_size);
#endif

		DUK_DD(DUK_DDPRINT("compact object: %p", (void *) obj));
		duk_push_hobject((duk_context *) thr, obj);
		/* XXX: disable error handlers for duration of compaction? */
		duk_safe_call((duk_context *) thr, duk__protected_compact_object, 1, 0);

#ifdef DUK_USE_DEBUG
		new_size = DUK_HOBJECT_P_COMPUTE_SIZE(obj->e_size, obj->a_size, obj->h_size);
#endif

#ifdef DUK_USE_DEBUG
		(*p_count_compact)++;
		(*p_count_bytes_saved) += old_size - new_size;
#endif

	 next:
		curr = DUK_HEAPHDR_GET_NEXT(curr);
#ifdef DUK_USE_DEBUG
		(*p_count_check)++;
#endif
	}
}
Example #4
0
static void duk__free_run_finalizers(duk_heap *heap) {
	duk_heaphdr *curr;
#ifdef DUK_USE_DEBUG
	duk_size_t count_obj = 0;
#endif

	DUK_ASSERT(heap != NULL);
	DUK_ASSERT(heap->heap_thread != NULL);
#ifdef DUK_USE_REFERENCE_COUNTING
	DUK_ASSERT(heap->refzero_list == NULL);  /* refzero not running -> must be empty */
#endif
#ifdef DUK_USE_MARK_AND_SWEEP
	DUK_ASSERT(heap->finalize_list == NULL);  /* mark-and-sweep not running -> must be empty */
#endif

	/* FIXME: here again finalizer thread is the heap_thread which needs
	 * to be coordinated with finalizer thread fixes.
	 */

	curr = heap->heap_allocated;
	while (curr) {
		if (DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT) {
			/* Only objects in heap_allocated may have finalizers. */
			DUK_ASSERT(heap->heap_thread != NULL);
			DUK_ASSERT(curr != NULL);
			duk_hobject_run_finalizer(heap->heap_thread, (duk_hobject *) curr);
#ifdef DUK_USE_DEBUG
			count_obj++;
#endif
		}
		curr = DUK_HEAPHDR_GET_NEXT(curr);
	}

	/* Note: count includes all objects, not only those with an actual finalizer. */
#ifdef DUK_USE_DEBUG
	DUK_D(DUK_DPRINT("checked %d objects for finalizers before freeing heap", (int) count_obj));
#endif
}
Example #5
0
DUK_LOCAL void duk__sweep_stringtable(duk_heap *heap, duk_size_t *out_count_keep) {
	duk_hstring *h;
	duk_hstring *prev;
	duk_uint32_t i;
#if defined(DUK_USE_DEBUG)
	duk_size_t count_free = 0;
#endif
	duk_size_t count_keep = 0;

	DUK_DD(DUK_DDPRINT("duk__sweep_stringtable: %p", (void *) heap));

#if defined(DUK_USE_STRTAB_PTRCOMP)
	if (heap->strtable16 == NULL) {
#else
	if (heap->strtable == NULL) {
#endif
		goto done;
	}

	for (i = 0; i < heap->st_size; i++) {
#if defined(DUK_USE_STRTAB_PTRCOMP)
		h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]);
#else
		h = heap->strtable[i];
#endif
		prev = NULL;
		while (h != NULL) {
			duk_hstring *next;
			next = h->hdr.h_next;

			if (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h)) {
				DUK_HEAPHDR_CLEAR_REACHABLE((duk_heaphdr *) h);
				count_keep++;
				prev = h;
			} else {
#if defined(DUK_USE_DEBUG)
				count_free++;
#endif

#if defined(DUK_USE_REFERENCE_COUNTING)
				/* Non-zero refcounts should not happen for unreachable strings,
				 * because we refcount finalize all unreachable objects which
				 * should have decreased unreachable string refcounts to zero
				 * (even for cycles).
				 */
				DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) == 0);
#endif

				/* deal with weak references first */
				duk_heap_strcache_string_remove(heap, (duk_hstring *) h);

				/* remove the string from the string table */
				duk_heap_strtable_unlink_prev(heap, (duk_hstring *) h, (duk_hstring *) prev);

				/* free inner references (these exist e.g. when external
				 * strings are enabled) and the struct itself.
				 */
				duk_free_hstring(heap, (duk_hstring *) h);

				/* don't update 'prev'; it should be last string kept */
			}

			h = next;
		}
	}

 done:
#if defined(DUK_USE_DEBUG)
	DUK_D(DUK_DPRINT("mark-and-sweep sweep stringtable: %ld freed, %ld kept",
	                 (long) count_free, (long) count_keep));
#endif
	*out_count_keep = count_keep;
}

/*
 *  Sweep heap
 */

DUK_LOCAL void duk__sweep_heap(duk_heap *heap, duk_int_t flags, duk_size_t *out_count_keep) {
	duk_heaphdr *prev;  /* last element that was left in the heap */
	duk_heaphdr *curr;
	duk_heaphdr *next;
#if defined(DUK_USE_DEBUG)
	duk_size_t count_free = 0;
	duk_size_t count_finalize = 0;
	duk_size_t count_rescue = 0;
#endif
	duk_size_t count_keep = 0;

	DUK_UNREF(flags);
	DUK_DD(DUK_DDPRINT("duk__sweep_heap: %p", (void *) heap));

	prev = NULL;
	curr = heap->heap_allocated;
	heap->heap_allocated = NULL;
	while (curr) {
		/* Strings and ROM objects are never placed on the heap allocated list. */
		DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) != DUK_HTYPE_STRING);
		DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(curr));

		next = DUK_HEAPHDR_GET_NEXT(heap, curr);

		if (DUK_HEAPHDR_HAS_REACHABLE(curr)) {
			/*
			 *  Reachable object, keep
			 */

			DUK_DDD(DUK_DDDPRINT("sweep, reachable: %p", (void *) curr));

			if (DUK_HEAPHDR_HAS_FINALIZABLE(curr)) {
				/*
				 *  If object has been marked finalizable, move it to the
				 *  "to be finalized" work list.  It will be collected on
				 *  the next mark-and-sweep if it is still unreachable
				 *  after running the finalizer.
				 */

				DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
				DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);
				DUK_DDD(DUK_DDDPRINT("object has finalizer, move to finalization work list: %p", (void *) curr));

#if defined(DUK_USE_DOUBLE_LINKED_HEAP)
				if (heap->finalize_list) {
					DUK_HEAPHDR_SET_PREV(heap, heap->finalize_list, curr);
				}
				DUK_HEAPHDR_SET_PREV(heap, curr, NULL);
#endif
				DUK_HEAPHDR_SET_NEXT(heap, curr, heap->finalize_list);
				DUK_ASSERT_HEAPHDR_LINKS(heap, curr);
				heap->finalize_list = curr;
#if defined(DUK_USE_DEBUG)
				count_finalize++;
#endif
			} else {
				/*
				 *  Object will be kept; queue object back to heap_allocated (to tail)
				 */

				if (DUK_HEAPHDR_HAS_FINALIZED(curr)) {
					/*
					 *  Object's finalizer was executed on last round, and
					 *  object has been happily rescued.
					 */

					DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));
					DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);
					DUK_DD(DUK_DDPRINT("object rescued during mark-and-sweep finalization: %p", (void *) curr));
#if defined(DUK_USE_DEBUG)
					count_rescue++;
#endif
				} else {
					/*
					 *  Plain, boring reachable object.
					 */
					DUK_DD(DUK_DDPRINT("keep object: %!iO", curr));
					count_keep++;
				}

				if (!heap->heap_allocated) {
					heap->heap_allocated = curr;
				}
				if (prev) {
					DUK_HEAPHDR_SET_NEXT(heap, prev, curr);
				}
#if defined(DUK_USE_DOUBLE_LINKED_HEAP)
				DUK_HEAPHDR_SET_PREV(heap, curr, prev);
#endif
				DUK_ASSERT_HEAPHDR_LINKS(heap, prev);
				DUK_ASSERT_HEAPHDR_LINKS(heap, curr);
				prev = curr;
			}

			DUK_HEAPHDR_CLEAR_REACHABLE(curr);
			DUK_HEAPHDR_CLEAR_FINALIZED(curr);
			DUK_HEAPHDR_CLEAR_FINALIZABLE(curr);

			DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr));
			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));

			curr = next;
		} else {
			/*
			 *  Unreachable object, free
			 */

			DUK_DDD(DUK_DDDPRINT("sweep, not reachable: %p", (void *) curr));

#if defined(DUK_USE_REFERENCE_COUNTING)
			/* Non-zero refcounts should not happen because we refcount
			 * finalize all unreachable objects which should cancel out
			 * refcounts (even for cycles).
			 */
			DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(curr) == 0);
#endif
			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));

			if (DUK_HEAPHDR_HAS_FINALIZED(curr)) {
				DUK_DDD(DUK_DDDPRINT("finalized object not rescued: %p", (void *) curr));
			}

			/* Note: object cannot be a finalizable unreachable object, as
			 * they have been marked temporarily reachable for this round,
			 * and are handled above.
			 */

#if defined(DUK_USE_DEBUG)
			count_free++;
#endif

			/* weak refs should be handled here, but no weak refs for
			 * any non-string objects exist right now.
			 */

			/* free object and all auxiliary (non-heap) allocs */
			duk_heap_free_heaphdr_raw(heap, curr);

			curr = next;
		}
	}
	if (prev) {
		DUK_HEAPHDR_SET_NEXT(heap, prev, NULL);
	}
	DUK_ASSERT_HEAPHDR_LINKS(heap, prev);

#if defined(DUK_USE_DEBUG)
	DUK_D(DUK_DPRINT("mark-and-sweep sweep objects (non-string): %ld freed, %ld kept, %ld rescued, %ld queued for finalization",
	                 (long) count_free, (long) count_keep, (long) count_rescue, (long) count_finalize));
#endif
	*out_count_keep = count_keep;
}
Example #6
0
static void duk__refcount_finalize_hobject(duk_hthread *thr, duk_hobject *h) {
	duk_uint_fast32_t i;

	DUK_ASSERT(h);
	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h) == DUK_HTYPE_OBJECT);

	/* XXX: better to get base and walk forwards? */

	for (i = 0; i < (duk_uint_fast32_t) h->e_next; i++) {
		duk_hstring *key = DUK_HOBJECT_E_GET_KEY(h, i);
		if (!key) {
			continue;
		}
		duk_heap_heaphdr_decref(thr, (duk_heaphdr *) key);
		if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(h, i)) {
			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_GETTER(h, i));
			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_SETTER(h, i));
		} else {
			duk_heap_tval_decref(thr, DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(h, i));
		}
	}

	for (i = 0; i < (duk_uint_fast32_t) h->a_size; i++) {
		duk_heap_tval_decref(thr, DUK_HOBJECT_A_GET_VALUE_PTR(h, i));
	}

	/* hash part is a 'weak reference' and does not contribute */

	duk_heap_heaphdr_decref(thr, (duk_heaphdr *) h->prototype);

	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
		duk_hcompiledfunction *f = (duk_hcompiledfunction *) h;
		duk_tval *tv, *tv_end;
		duk_hobject **funcs, **funcs_end;

		DUK_ASSERT(f->data != NULL);  /* compiled functions must be created 'atomically' */

		tv = DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(f);
		tv_end = DUK_HCOMPILEDFUNCTION_GET_CONSTS_END(f);
		while (tv < tv_end) {
			duk_heap_tval_decref(thr, tv);
			tv++;
		}

		funcs = DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(f);
		funcs_end = DUK_HCOMPILEDFUNCTION_GET_FUNCS_END(f);
		while (funcs < funcs_end) {
			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) *funcs);
			funcs++;
		}

		duk_heap_heaphdr_decref(thr, (duk_heaphdr *) f->data);
	} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
		duk_hnativefunction *f = (duk_hnativefunction *) h;
		DUK_UNREF(f);
		/* nothing to finalize */
	} else if (DUK_HOBJECT_IS_THREAD(h)) {
		duk_hthread *t = (duk_hthread *) h;
		duk_tval *tv;

		tv = t->valstack;
		while (tv < t->valstack_end) {
			duk_heap_tval_decref(thr, tv);
			tv++;
		}

		for (i = 0; i < (duk_uint_fast32_t) t->callstack_top; i++) {
			duk_activation *act = t->callstack + i;
			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) act->func);
			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) act->var_env);
			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) act->lex_env);
#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) act->prev_caller);
#endif
		}

#if 0  /* nothing now */
		for (i = 0; i < (duk_uint_fast32_t) t->catchstack_top; i++) {
			duk_catcher *cat = t->catchstack + i;
		}
#endif

		for (i = 0; i < DUK_NUM_BUILTINS; i++) {
			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) t->builtins[i]);
		}

		duk_heap_heaphdr_decref(thr, (duk_heaphdr *) t->resumer);
	}
}
Example #7
0
void duk_heap_heaphdr_decref(duk_hthread *thr, duk_heaphdr *h) {
	duk_heap *heap;

#if 0
	DUK_DDD(DUK_DDDPRINT("heaphdr decref %p (%ld->%ld): %!O",
	                     (void *) h,
	                     (h != NULL ? (long) h->h_refcount : (long) 0),
	                     (h != NULL ? (long) (h->h_refcount - 1) : (long) 0),
	                     (duk_heaphdr *) h));
#endif

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);

	if (!h) {
		return;
	}
	DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h));
	DUK_ASSERT(h->h_refcount >= 1);

	if (--h->h_refcount != 0) {
		return;
	}

	heap = thr->heap;
	DUK_DDD(DUK_DDDPRINT("refzero %p: %!O", (void *) h, (duk_heaphdr *) h));

#ifdef DUK_USE_MARK_AND_SWEEP
	/*
	 *  If mark-and-sweep is running, don't process 'refzero' situations at all.
	 *  They may happen because mark-and-sweep needs to finalize refcounts for
	 *  each object it sweeps.  Otherwise the target objects of swept objects
	 *  would have incorrect refcounts.
	 *
	 *  Note: mark-and-sweep could use a separate decref handler to avoid coming
	 *  here at all.  However, mark-and-sweep may also call finalizers, which
	 *  can do arbitrary operations and would use this decref variant anyway.
	 */
	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_DDD(DUK_DDDPRINT("refzero handling suppressed when mark-and-sweep running, object: %p", (void *) h));
		return;
	}
#endif

	switch ((duk_small_int_t) DUK_HEAPHDR_GET_TYPE(h)) {
	case DUK_HTYPE_STRING:
		/*
		 *  Strings have no internal references but do have "weak"
		 *  references in the string cache.  Also note that strings
		 *  are not on the heap_allocated list like other heap
		 *  elements.
		 */

		duk_heap_strcache_string_remove(heap, (duk_hstring *) h);
		duk_heap_string_remove(heap, (duk_hstring *) h);
		duk_heap_free_heaphdr_raw(heap, h);
		break;

	case DUK_HTYPE_OBJECT:
		/*
		 *  Objects have internal references.  Must finalize through
		 *  the "refzero" work list.
		 */

		duk_heap_remove_any_from_heap_allocated(heap, h);
		duk__queue_refzero(heap, h);
		duk__refzero_free_pending(thr);
		break;

	case DUK_HTYPE_BUFFER:
		/*
		 *  Buffers have no internal references.  However, a dynamic
		 *  buffer has a separate allocation for the buffer.  This is
		 *  freed by duk_heap_free_heaphdr_raw().
		 */

		duk_heap_remove_any_from_heap_allocated(heap, h);
		duk_heap_free_heaphdr_raw(heap, h);
		break;

	default:
		DUK_D(DUK_DPRINT("invalid heap type in decref: %ld", (long) DUK_HEAPHDR_GET_TYPE(h)));
		DUK_UNREACHABLE();
	}
}
Example #8
0
static void duk__refzero_free_pending(duk_hthread *thr) {
	duk_heaphdr *h1, *h2;
	duk_heap *heap;
	duk_int_t count = 0;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);
	heap = thr->heap;
	DUK_ASSERT(heap != NULL);

	/*
	 *  Detect recursive invocation
	 */

	if (DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap)) {
		DUK_DDD(DUK_DDDPRINT("refzero free running, skip run"));
		return;
	}

	/*
	 *  Churn refzero_list until empty
	 */

	DUK_HEAP_SET_REFZERO_FREE_RUNNING(heap);
	while (heap->refzero_list) {
		duk_hobject *obj;
		duk_bool_t rescued = 0;

		/*
		 *  Pick an object from the head (don't remove yet).
		 */

		h1 = heap->refzero_list;
		obj = (duk_hobject *) h1;
		DUK_DD(DUK_DDPRINT("refzero processing %p: %!O", (void *) h1, (duk_heaphdr *) h1));
		DUK_ASSERT(DUK_HEAPHDR_GET_PREV(h1) == NULL);
		DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(h1) == DUK_HTYPE_OBJECT);  /* currently, always the case */

		/*
		 *  Finalizer check.
		 *
		 *  Note: running a finalizer may have arbitrary side effects, e.g.
		 *  queue more objects on refzero_list (tail), or even trigger a
		 *  mark-and-sweep.
		 *
		 *  Note: quick reject check should match vast majority of
		 *  objects and must be safe (not throw any errors, ever).
		 */

		/* XXX: If object has FINALIZED, it was finalized by mark-and-sweep on
		 * its previous run.  Any point in running finalizer again here?  If
		 * finalization semantics is changed so that finalizer is only run once,
		 * checking for FINALIZED would happen here.
		 */

		/* A finalizer is looked up from the object and up its prototype chain
		 * (which allows inherited finalizers).
		 */
		if (duk_hobject_hasprop_raw(thr, obj, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) {
			DUK_DDD(DUK_DDDPRINT("object has a finalizer, run it"));

			DUK_ASSERT(h1->h_refcount == 0);
			h1->h_refcount++;  /* bump refcount to prevent refzero during finalizer processing */

			duk_hobject_run_finalizer(thr, obj);  /* must never longjmp */

			h1->h_refcount--;  /* remove artificial bump */
			DUK_ASSERT_DISABLE(h1->h_refcount >= 0);  /* refcount is unsigned, so always true */

			if (h1->h_refcount != 0) {
				DUK_DDD(DUK_DDDPRINT("-> object refcount after finalization non-zero, object will be rescued"));
				rescued = 1;
			} else {
				DUK_DDD(DUK_DDDPRINT("-> object refcount still zero after finalization, object will be freed"));
			}
		}

  		/* Refzero head is still the same.  This is the case even if finalizer
		 * inserted more refzero objects; they are inserted to the tail.
		 */
		DUK_ASSERT(h1 == heap->refzero_list);

		/*
		 *  Remove the object from the refzero list.  This cannot be done
		 *  before a possible finalizer has been executed; the finalizer
		 *  may trigger a mark-and-sweep, and mark-and-sweep must be able
		 *  to traverse a complete refzero_list.
		 */

		h2 = DUK_HEAPHDR_GET_NEXT(h1);
		if (h2) {
			DUK_HEAPHDR_SET_PREV(h2, NULL);  /* not strictly necessary */
			heap->refzero_list = h2;
		} else {
			heap->refzero_list = NULL;
			heap->refzero_list_tail = NULL;
		}

		/*
		 *  Rescue or free.
		 */

		if (rescued) {
			/* yes -> move back to heap allocated */
			DUK_DD(DUK_DDPRINT("object rescued during refcount finalization: %p", (void *) h1));
			DUK_HEAPHDR_SET_PREV(h1, NULL);
			DUK_HEAPHDR_SET_NEXT(h1, heap->heap_allocated);
			heap->heap_allocated = h1;
		} else {
			/* no -> decref members, then free */
			duk__refcount_finalize_hobject(thr, obj);
			duk_heap_free_heaphdr_raw(heap, h1);
		}

		count++;
	}
	DUK_HEAP_CLEAR_REFZERO_FREE_RUNNING(heap);

	DUK_DDD(DUK_DDDPRINT("refzero processed %ld objects", (long) count));

	/*
	 *  Once the whole refzero cascade has been freed, check for
	 *  a voluntary mark-and-sweep.
	 */

#if defined(DUK_USE_MARK_AND_SWEEP) && defined(DUK_USE_VOLUNTARY_GC)
	/* 'count' is more or less comparable to normal trigger counter update
	 * which happens in memory block (re)allocation.
	 */
	heap->mark_and_sweep_trigger_counter -= count;
	if (heap->mark_and_sweep_trigger_counter <= 0) {
		duk_bool_t rc;
		duk_small_uint_t flags = 0;  /* not emergency */
		DUK_D(DUK_DPRINT("refcount triggering mark-and-sweep"));
		rc = duk_heap_mark_and_sweep(heap, flags);
		DUK_UNREF(rc);
		DUK_D(DUK_DPRINT("refcount triggered mark-and-sweep => rc %ld", (long) rc));
	}
#endif  /* DUK_USE_MARK_AND_SWEEP && DUK_USE_VOLUNTARY_GC */
}
Example #9
0
DUK_INTERNAL void duk_heaphdr_refzero(duk_hthread *thr, duk_heaphdr *h) {
	duk_heap *heap;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(h != NULL);

	heap = thr->heap;
	DUK_DDD(DUK_DDDPRINT("refzero %p: %!O", (void *) h, (duk_heaphdr *) h));

	/*
	 *  Refzero handling is skipped entirely if (1) mark-and-sweep is
	 *  running or (2) execution is paused in the debugger.  The objects
	 *  are left in the heap, and will be freed by mark-and-sweep or
	 *  eventual heap destruction.
	 *
	 *  This is necessary during mark-and-sweep because refcounts are also
	 *  updated during the sweep phase (otherwise objects referenced by a
	 *  swept object would have incorrect refcounts) which then calls here.
	 *  This could be avoided by using separate decref macros in
	 *  mark-and-sweep; however, mark-and-sweep also calls finalizers which
	 *  would use the ordinary decref macros anyway and still call this
	 *  function.
	 *
	 *  This check must be enabled also when mark-and-sweep support has been
	 *  disabled: the flag is also used in heap destruction when running
	 *  finalizers for remaining objects, and the flag prevents objects from
	 *  being moved around in heap linked lists.
	 */

	/* XXX: ideally this would be just one flag (maybe a derived one) so
	 * that a single bit test is sufficient to check the condition.
	 */
#if defined(DUK_USE_DEBUGGER_SUPPORT)
	if (DUK_UNLIKELY(DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap) || DUK_HEAP_IS_PAUSED(heap))) {
#else
	if (DUK_UNLIKELY(DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap))) {
#endif
		DUK_DDD(DUK_DDDPRINT("refzero handling suppressed when mark-and-sweep running, object: %p", (void *) h));
		return;
	}

	switch ((duk_small_int_t) DUK_HEAPHDR_GET_TYPE(h)) {
	case DUK_HTYPE_STRING:
		/*
		 *  Strings have no internal references but do have "weak"
		 *  references in the string cache.  Also note that strings
		 *  are not on the heap_allocated list like other heap
		 *  elements.
		 */

		duk_heap_strcache_string_remove(heap, (duk_hstring *) h);
		duk_heap_string_remove(heap, (duk_hstring *) h);
		duk_heap_free_heaphdr_raw(heap, h);
		break;

	case DUK_HTYPE_OBJECT:
		/*
		 *  Objects have internal references.  Must finalize through
		 *  the "refzero" work list.
		 */

		duk_heap_remove_any_from_heap_allocated(heap, h);
		duk__queue_refzero(heap, h);
		duk__refzero_free_pending(thr);
		break;

	case DUK_HTYPE_BUFFER:
		/*
		 *  Buffers have no internal references.  However, a dynamic
		 *  buffer has a separate allocation for the buffer.  This is
		 *  freed by duk_heap_free_heaphdr_raw().
		 */

		duk_heap_remove_any_from_heap_allocated(heap, h);
		duk_heap_free_heaphdr_raw(heap, h);
		break;

	default:
		DUK_D(DUK_DPRINT("invalid heap type in decref: %ld", (long) DUK_HEAPHDR_GET_TYPE(h)));
		DUK_UNREACHABLE();
	}
}

#if !defined(DUK_USE_FAST_REFCOUNT_DEFAULT)
DUK_INTERNAL void duk_tval_incref(duk_tval *tv) {
	DUK_ASSERT(tv != NULL);

	if (DUK_TVAL_NEEDS_REFCOUNT_UPDATE(tv)) {
		duk_heaphdr *h = DUK_TVAL_GET_HEAPHDR(tv);
		DUK_ASSERT(h != NULL);
		DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h));
		DUK_ASSERT_DISABLE(h->h_refcount >= 0);
		DUK_HEAPHDR_PREINC_REFCOUNT(h);
	}
}
Example #10
0
DUK_LOCAL void duk__free_stringtable(duk_heap *heap) {
	duk_uint_fast32_t i;

	/* strings are only tracked by stringtable */
#if defined(DUK_USE_HEAPPTR16)
	if (heap->strtable16) {
#else
	if (heap->strtable) {
#endif
		for (i = 0; i < (duk_uint_fast32_t) heap->st_size; i++) {
			duk_hstring *e;
#if defined(DUK_USE_HEAPPTR16)
			e = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->strtable16[i]);
#else
			e = heap->strtable[i];
#endif
			if (e == NULL || e == DUK_STRTAB_DELETED_MARKER(heap)) {
				continue;
			}
			DUK_ASSERT(e != NULL);

			/* strings may have inner refs (extdata) in some cases */
			duk_free_hstring_inner(heap, (duk_hstring *) e);

			DUK_DDD(DUK_DDDPRINT("FINALFREE (string): %!iO",
			                     (duk_heaphdr *) e));
			DUK_FREE(heap, e);
#if 0  /* not strictly necessary */
			heap->strtable[i] = NULL;
#endif
		}
#if defined(DUK_USE_HEAPPTR16)
		DUK_FREE(heap, heap->strtable16);
#else
		DUK_FREE(heap, heap->strtable);
#endif
#if 0  /* not strictly necessary */
		heap->strtable = NULL;
#endif
	}
}

DUK_LOCAL void duk__free_run_finalizers(duk_heap *heap) {
	duk_hthread *thr;
	duk_heaphdr *curr;
#ifdef DUK_USE_DEBUG
	duk_size_t count_obj = 0;
#endif

	DUK_ASSERT(heap != NULL);
	DUK_ASSERT(heap->heap_thread != NULL);
#ifdef DUK_USE_REFERENCE_COUNTING
	DUK_ASSERT(heap->refzero_list == NULL);  /* refzero not running -> must be empty */
#endif
#ifdef DUK_USE_MARK_AND_SWEEP
	DUK_ASSERT(heap->finalize_list == NULL);  /* mark-and-sweep not running -> must be empty */
#endif

	/* XXX: here again finalizer thread is the heap_thread which needs
	 * to be coordinated with finalizer thread fixes.
	 */
	thr = heap->heap_thread;
	DUK_ASSERT(thr != NULL);

	curr = heap->heap_allocated;
	while (curr) {
		if (DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT) {
			/* Only objects in heap_allocated may have finalizers.  Check that
			 * the object itself has a _Finalizer property so that we don't
			 * execute finalizers for e.g. Proxy objects.
			 */
			DUK_ASSERT(thr != NULL);
			DUK_ASSERT(curr != NULL);

			if (duk_hobject_hasprop_raw(thr, (duk_hobject *) curr, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) {
				duk_hobject_run_finalizer(thr, (duk_hobject *) curr);
			}
#ifdef DUK_USE_DEBUG
			count_obj++;
#endif
		}
		curr = DUK_HEAPHDR_GET_NEXT(curr);
	}

	/* Note: count includes all objects, not only those with an actual finalizer. */
#ifdef DUK_USE_DEBUG
	DUK_D(DUK_DPRINT("checked %ld objects for finalizers before freeing heap", (long) count_obj));
#endif
}
Example #11
0
DUK_LOCAL void duk__free_run_finalizers(duk_heap *heap) {
	duk_hthread *thr;
	duk_heaphdr *curr;
	duk_uint_t round_no;
	duk_size_t count_all;
	duk_size_t count_finalized;
	duk_size_t curr_limit;

	DUK_ASSERT(heap != NULL);
	DUK_ASSERT(heap->heap_thread != NULL);

#if defined(DUK_USE_REFERENCE_COUNTING)
	DUK_ASSERT(heap->refzero_list == NULL);  /* refzero not running -> must be empty */
#endif
#if defined(DUK_USE_MARK_AND_SWEEP)
	DUK_ASSERT(heap->finalize_list == NULL);  /* mark-and-sweep not running -> must be empty */
#endif

	/* XXX: here again finalizer thread is the heap_thread which needs
	 * to be coordinated with finalizer thread fixes.
	 */
	thr = heap->heap_thread;
	DUK_ASSERT(thr != NULL);

	/* Prevent mark-and-sweep for the pending finalizers, also prevents
	 * refzero handling from moving objects away from the heap_allocated
	 * list.  (The flag meaning is slightly abused here.)
	 */
	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap));
	DUK_HEAP_SET_MARKANDSWEEP_RUNNING(heap);

	curr_limit = 0;  /* suppress warning, not used */
	for (round_no = 0; ; round_no++) {
		curr = heap->heap_allocated;
		count_all = 0;
		count_finalized = 0;
		while (curr) {
			count_all++;
			if (DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT) {
				/* Only objects in heap_allocated may have finalizers.  Check that
				 * the object itself has a _Finalizer property (own or inherited)
				 * so that we don't execute finalizers for e.g. Proxy objects.
				 */
				DUK_ASSERT(thr != NULL);
				DUK_ASSERT(curr != NULL);

				if (duk_hobject_hasprop_raw(thr, (duk_hobject *) curr, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) {
					if (!DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) curr)) {
						DUK_ASSERT(DUK_HEAP_HAS_FINALIZER_NORESCUE(heap));  /* maps to finalizer 2nd argument */
						duk_hobject_run_finalizer(thr, (duk_hobject *) curr);
						count_finalized++;
					}
				}
			}
			curr = DUK_HEAPHDR_GET_NEXT(heap, curr);
		}

		/* Each round of finalizer execution may spawn new finalizable objects
		 * which is normal behavior for some applications.  Allow multiple
		 * rounds of finalization, but use a shrinking limit based on the
		 * first round to detect the case where a runaway finalizer creates
		 * an unbounded amount of new finalizable objects.  Finalizer rescue
		 * is not supported: the semantics are unclear because most of the
		 * objects being finalized here are already reachable.  The finalizer
		 * is given a boolean to indicate that rescue is not possible.
		 *
		 * See discussion in: https://github.com/svaarala/duktape/pull/473
		 */

		if (round_no == 0) {
			/* Cannot wrap: each object is at least 8 bytes so count is
			 * at most 1/8 of that.
			 */
			curr_limit = count_all * 2;
		} else {
			curr_limit = (curr_limit * 3) / 4;   /* Decrease by 25% every round */
		}
		DUK_D(DUK_DPRINT("finalizer round %ld complete, %ld objects, tried to execute %ld finalizers, current limit is %ld",
		                 (long) round_no, (long) count_all, (long) count_finalized, (long) curr_limit));

		if (count_finalized == 0) {
			DUK_D(DUK_DPRINT("no more finalizable objects, forced finalization finished"));
			break;
		}
		if (count_finalized >= curr_limit) {
			DUK_D(DUK_DPRINT("finalizer count above limit, potentially runaway finalizer; skip remaining finalizers"));
			break;
		}
	}

	DUK_ASSERT(DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap));
	DUK_HEAP_CLEAR_MARKANDSWEEP_RUNNING(heap);
}
Example #12
0
/* Raw helper to extract internal information / statistics about a value.
 * The return values are version specific and must not expose anything
 * that would lead to security issues (e.g. exposing compiled function
 * 'data' buffer might be an issue).  Currently only counts and sizes and
 * such are given so there should not be a security impact.
 */
DUK_INTERNAL duk_ret_t duk_bi_duktape_object_info(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;
	duk_heaphdr *h;
	duk_int_t i, n;

	DUK_UNREF(thr);

	/* result array */
	duk_push_array(ctx);  /* -> [ val arr ] */

	/* type tag (public) */
	duk_push_int(ctx, duk_get_type(ctx, 0));

	/* address */
	tv = duk_get_tval(ctx, 0);
	DUK_ASSERT(tv != NULL);  /* because arg count is 1 */
	if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
		h = DUK_TVAL_GET_HEAPHDR(tv);
		duk_push_pointer(ctx, (void *) h);
	} else {
		/* internal type tag */
		duk_push_int(ctx, (duk_int_t) DUK_TVAL_GET_TAG(tv));
		goto done;
	}
	DUK_ASSERT(h != NULL);

	/* refcount */
#ifdef DUK_USE_REFERENCE_COUNTING
	duk_push_size_t(ctx, DUK_HEAPHDR_GET_REFCOUNT(h));
#else
	duk_push_undefined(ctx);
#endif

	/* heaphdr size and additional allocation size, followed by
	 * type specific stuff (with varying value count)
	 */
	switch ((duk_small_int_t) DUK_HEAPHDR_GET_TYPE(h)) {
	case DUK_HTYPE_STRING: {
		duk_hstring *h_str = (duk_hstring *) h;
		duk_push_uint(ctx, (duk_uint_t) (sizeof(duk_hstring) + DUK_HSTRING_GET_BYTELEN(h_str) + 1));
		break;
	}
	case DUK_HTYPE_OBJECT: {
		duk_hobject *h_obj = (duk_hobject *) h;
		duk_small_uint_t hdr_size;
		if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h_obj)) {
			hdr_size = (duk_small_uint_t) sizeof(duk_hcompiledfunction);
		} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h_obj)) {
			hdr_size = (duk_small_uint_t) sizeof(duk_hnativefunction);
		} else if (DUK_HOBJECT_IS_THREAD(h_obj)) {
			hdr_size = (duk_small_uint_t) sizeof(duk_hthread);
#if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
		} else if (DUK_HOBJECT_IS_BUFFEROBJECT(h_obj)) {
			hdr_size = (duk_small_uint_t) sizeof(duk_hbufferobject);
#endif
		} else {
			hdr_size = (duk_small_uint_t) sizeof(duk_hobject);
		}
		duk_push_uint(ctx, (duk_uint_t) hdr_size);
		duk_push_uint(ctx, (duk_uint_t) DUK_HOBJECT_P_ALLOC_SIZE(h_obj));
		duk_push_uint(ctx, (duk_uint_t) DUK_HOBJECT_GET_ESIZE(h_obj));
		/* Note: e_next indicates the number of gc-reachable entries
		 * in the entry part, and also indicates the index where the
		 * next new property would be inserted.  It does *not* indicate
		 * the number of non-NULL keys present in the object.  That
		 * value could be counted separately but requires a pass through
		 * the key list.
		 */
		duk_push_uint(ctx, (duk_uint_t) DUK_HOBJECT_GET_ENEXT(h_obj));
		duk_push_uint(ctx, (duk_uint_t) DUK_HOBJECT_GET_ASIZE(h_obj));
		duk_push_uint(ctx, (duk_uint_t) DUK_HOBJECT_GET_HSIZE(h_obj));
		if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h_obj)) {
			duk_hbuffer *h_data = (duk_hbuffer *) DUK_HCOMPILEDFUNCTION_GET_DATA(thr->heap, (duk_hcompiledfunction *) h_obj);
			if (h_data) {
				duk_push_uint(ctx, (duk_uint_t) DUK_HBUFFER_GET_SIZE(h_data));
			} else {
				duk_push_uint(ctx, 0);
			}
		}
		break;
	}
	case DUK_HTYPE_BUFFER: {
		duk_hbuffer *h_buf = (duk_hbuffer *) h;
		if (DUK_HBUFFER_HAS_DYNAMIC(h_buf)) {
			if (DUK_HBUFFER_HAS_EXTERNAL(h_buf)) {
				duk_push_uint(ctx, (duk_uint_t) (sizeof(duk_hbuffer_external)));
			} else {
				/* When alloc_size == 0 the second allocation may not
				 * actually exist.
				 */
				duk_push_uint(ctx, (duk_uint_t) (sizeof(duk_hbuffer_dynamic)));
			}
			duk_push_uint(ctx, (duk_uint_t) (DUK_HBUFFER_GET_SIZE(h_buf)));
		} else {
			duk_push_uint(ctx, (duk_uint_t) (sizeof(duk_hbuffer_fixed) + DUK_HBUFFER_GET_SIZE(h_buf) + 1));
		}
		break;

	}
	}

 done:
	/* set values into ret array */
	/* XXX: primitive to make array from valstack slice */
	n = duk_get_top(ctx);
	for (i = 2; i < n; i++) {
		duk_dup(ctx, i);
		duk_put_prop_index(ctx, 1, i - 2);
	}
	duk_dup(ctx, 1);
	return 1;
}
Example #13
0
static duk_uint8_t *duk__load_func(duk_context *ctx, duk_uint8_t *p, duk_uint8_t *p_end) {
	duk_hthread *thr;
	duk_hcompiledfunction *h_fun;
	duk_hbuffer *h_data;
	duk_size_t data_size;
	duk_uint32_t count_instr, count_const, count_funcs;
	duk_uint32_t n;
	duk_uint32_t tmp32;
	duk_small_uint_t const_type;
	duk_uint8_t *fun_data;
	duk_uint8_t *q;
	duk_idx_t idx_base;
	duk_tval *tv;
	duk_uarridx_t arr_idx;

	/* XXX: There's some overlap with duk_js_closure() here, but
	 * seems difficult to share code.  Ensure that the final function
	 * looks the same as created by duk_js_closure().
	 */

	DUK_ASSERT(ctx != NULL);
	thr = (duk_hthread *) ctx;

	DUK_DD(DUK_DDPRINT("loading function, p=%p, p_end=%p", (void *) p, (void *) p_end));

	DUK__ASSERT_LEFT(3 * 4);
	count_instr = DUK_RAW_READ_U32_BE(p);
	count_const = DUK_RAW_READ_U32_BE(p);
	count_funcs = DUK_RAW_READ_U32_BE(p);

	data_size = sizeof(duk_tval) * count_const +
	            sizeof(duk_hobject *) * count_funcs +
	            sizeof(duk_instr_t) * count_instr;

	DUK_DD(DUK_DDPRINT("instr=%ld, const=%ld, funcs=%ld, data_size=%ld",
	                   (long) count_instr, (long) count_const,
	                   (long) count_const, (long) data_size));

	/* Value stack is used to ensure reachability of constants and
	 * inner functions being loaded.  Require enough space to handle
	 * large functions correctly.
	 */
	duk_require_stack(ctx, 2 + count_const + count_funcs);
	idx_base = duk_get_top(ctx);

	/* Push function object, init flags etc.  This must match
	 * duk_js_push_closure() quite carefully.
	 */
	duk_push_compiledfunction(ctx);
	h_fun = duk_get_hcompiledfunction(ctx, -1);
	DUK_ASSERT(h_fun != NULL);
	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((duk_hobject *) h_fun));
	DUK_ASSERT(DUK_HCOMPILEDFUNCTION_GET_DATA(thr->heap, h_fun) == NULL);
	DUK_ASSERT(DUK_HCOMPILEDFUNCTION_GET_FUNCS(thr->heap, h_fun) == NULL);
	DUK_ASSERT(DUK_HCOMPILEDFUNCTION_GET_BYTECODE(thr->heap, h_fun) == NULL);

	h_fun->nregs = DUK_RAW_READ_U16_BE(p);
	h_fun->nargs = DUK_RAW_READ_U16_BE(p);
#if defined(DUK_USE_DEBUGGER_SUPPORT)
	h_fun->start_line = DUK_RAW_READ_U32_BE(p);
	h_fun->end_line = DUK_RAW_READ_U32_BE(p);
#else
	p += 8;  /* skip line info */
#endif

	/* duk_hcompiledfunction flags; quite version specific */
	tmp32 = DUK_RAW_READ_U32_BE(p);
	DUK_HEAPHDR_SET_FLAGS((duk_heaphdr *) h_fun, tmp32);

	/* standard prototype */
	DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, &h_fun->obj, thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]);

	/* assert just a few critical flags */
	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h_fun) == DUK_HTYPE_OBJECT);
	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(&h_fun->obj));
	DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(&h_fun->obj));
	DUK_ASSERT(!DUK_HOBJECT_HAS_NATIVEFUNCTION(&h_fun->obj));
	DUK_ASSERT(!DUK_HOBJECT_HAS_THREAD(&h_fun->obj));
	DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARRAY(&h_fun->obj));
	DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(&h_fun->obj));
	DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(&h_fun->obj));

	/* Create function 'data' buffer but don't attach it yet. */
	fun_data = (duk_uint8_t *) duk_push_fixed_buffer(ctx, data_size);
	DUK_ASSERT(fun_data != NULL);

	/* Load bytecode instructions. */
	DUK_ASSERT(sizeof(duk_instr_t) == 4);
	DUK__ASSERT_LEFT(count_instr * sizeof(duk_instr_t));
#if defined(DUK_USE_INTEGER_BE)
	q = fun_data + sizeof(duk_tval) * count_const + sizeof(duk_hobject *) * count_funcs;
	DUK_MEMCPY((void *) q,
	           (const void *) p,
	           sizeof(duk_instr_t) * count_instr);
	p += sizeof(duk_instr_t) * count_instr;
#else
	q = fun_data + sizeof(duk_tval) * count_const + sizeof(duk_hobject *) * count_funcs;
	for (n = count_instr; n > 0; n--) {
		*((duk_instr_t *) (void *) q) = DUK_RAW_READ_U32_BE(p);
		q += sizeof(duk_instr_t);
	}
#endif

	/* Load constants onto value stack but don't yet copy to buffer. */
	for (n = count_const; n > 0; n--) {
		DUK__ASSERT_LEFT(1);
		const_type = DUK_RAW_READ_U8(p);
		switch (const_type) {
		case DUK__SER_STRING: {
			p = duk__load_string_raw(ctx, p);
			break;
		}
		case DUK__SER_NUMBER: {
			/* Important to do a fastint check so that constants are
			 * properly read back as fastints.
			 */
			duk_tval tv_tmp;
			duk_double_t val;
			DUK__ASSERT_LEFT(8);
			val = DUK_RAW_READ_DOUBLE_BE(p);
			DUK_TVAL_SET_NUMBER_CHKFAST(&tv_tmp, val);
			duk_push_tval(ctx, &tv_tmp);
			break;
		}
		default: {
			goto format_error;
		}
		}
	}

	/* Load inner functions to value stack, but don't yet copy to buffer. */
	for (n = count_funcs; n > 0; n--) {
		p = duk__load_func(ctx, p, p_end);
		if (p == NULL) {
			goto format_error;
		}
	}

	/* With constants and inner functions on value stack, we can now
	 * atomically finish the function 'data' buffer, bump refcounts,
	 * etc.
	 *
	 * Here we take advantage of the value stack being just a duk_tval
	 * array: we can just memcpy() the constants as long as we incref
	 * them afterwards.
	 */

	h_data = (duk_hbuffer *) duk_get_hbuffer(ctx, idx_base + 1);
	DUK_ASSERT(h_data != NULL);
	DUK_ASSERT(!DUK_HBUFFER_HAS_DYNAMIC(h_data));
	DUK_HCOMPILEDFUNCTION_SET_DATA(thr->heap, h_fun, h_data);
	DUK_HBUFFER_INCREF(thr, h_data);

	tv = duk_get_tval(ctx, idx_base + 2);  /* may be NULL if no constants or inner funcs */
	DUK_ASSERT((count_const == 0 && count_funcs == 0) || tv != NULL);

	q = fun_data;
	if (count_const > 0) {
		/* Explicit zero size check to avoid NULL 'tv'. */
		DUK_MEMCPY((void *) q, (const void *) tv, sizeof(duk_tval) * count_const);
		for (n = count_const; n > 0; n--) {
			DUK_TVAL_INCREF_FAST(thr, (duk_tval *) (void *) q);  /* no side effects */
			q += sizeof(duk_tval);
		}
		tv += count_const;
	}

	DUK_HCOMPILEDFUNCTION_SET_FUNCS(thr->heap, h_fun, (duk_hobject **) (void *) q);
	for (n = count_funcs; n > 0; n--) {
		duk_hobject *h_obj;

		DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
		h_obj = DUK_TVAL_GET_OBJECT(tv);
		DUK_ASSERT(h_obj != NULL);
		tv++;
		DUK_HOBJECT_INCREF(thr, h_obj);

		*((duk_hobject **) (void *) q) = h_obj;
		q += sizeof(duk_hobject *);
	}

	DUK_HCOMPILEDFUNCTION_SET_BYTECODE(thr->heap, h_fun, (duk_instr_t *) (void *) q);

	/* The function object is now reachable and refcounts are fine,
	 * so we can pop off all the temporaries.
	 */
	DUK_DDD(DUK_DDDPRINT("function is reachable, reset top; func: %!iT", duk_get_tval(ctx, idx_base)));
	duk_set_top(ctx, idx_base + 1);

	/* Setup function properties. */
	tmp32 = DUK_RAW_READ_U32_BE(p);
	duk_push_u32(ctx, tmp32);
	duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE);

	p = duk__load_string_raw(ctx, p);
	if (DUK_HOBJECT_HAS_NAMEBINDING((duk_hobject *) h_fun)) {
		/* Original function instance/template had NAMEBINDING.
		 * Must create a lexical environment on loading to allow
		 * recursive functions like 'function foo() { foo(); }'.
		 */
		duk_hobject *proto;

		proto = thr->builtins[DUK_BIDX_GLOBAL_ENV];
		(void) duk_push_object_helper_proto(ctx,
		                                    DUK_HOBJECT_FLAG_EXTENSIBLE |
		                                    DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV),
		                                    proto);
		duk_dup(ctx, -2);                                 /* -> [ func funcname env funcname ] */
		duk_dup(ctx, idx_base);                           /* -> [ func funcname env funcname func ] */
		duk_xdef_prop(ctx, -3, DUK_PROPDESC_FLAGS_NONE);  /* -> [ func funcname env ] */
		duk_xdef_prop_stridx(ctx, idx_base, DUK_STRIDX_INT_LEXENV, DUK_PROPDESC_FLAGS_WC);
		/* since closure has NEWENV, never define DUK_STRIDX_INT_VARENV, as it
		 * will be ignored anyway
		 */
	}
	duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE);

	p = duk__load_string_raw(ctx, p);
	duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC);

	duk_push_object(ctx);
	duk_dup(ctx, -2);
	duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_CONSTRUCTOR, DUK_PROPDESC_FLAGS_WC);  /* func.prototype.constructor = func */
	duk_compact(ctx, -1);
	duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_PROTOTYPE, DUK_PROPDESC_FLAGS_W);

	p = duk__load_buffer_raw(ctx, p);
	duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_PC2LINE, DUK_PROPDESC_FLAGS_WC);

	duk_push_object(ctx);  /* _Varmap */
	for (;;) {
		/* XXX: awkward */
		p = duk__load_string_raw(ctx, p);
		if (duk_get_length(ctx, -1) == 0) {
			duk_pop(ctx);
			break;
		}
		tmp32 = DUK_RAW_READ_U32_BE(p);
		duk_push_u32(ctx, tmp32);
		duk_put_prop(ctx, -3);
	}
	duk_compact(ctx, -1);
	duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_VARMAP, DUK_PROPDESC_FLAGS_NONE);

	duk_push_array(ctx);  /* _Formals */
	for (arr_idx = 0; ; arr_idx++) {
		/* XXX: awkward */
		p = duk__load_string_raw(ctx, p);
		if (duk_get_length(ctx, -1) == 0) {
			duk_pop(ctx);
			break;
		}
		duk_put_prop_index(ctx, -2, arr_idx);
	}
	duk_compact(ctx, -1);
	duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_FORMALS, DUK_PROPDESC_FLAGS_NONE);

	/* Return with final function pushed on stack top. */
	DUK_DD(DUK_DDPRINT("final loaded function: %!iT", duk_get_tval(ctx, -1)));
	DUK_ASSERT_TOP(ctx, idx_base + 1);
	return p;

 format_error:
	return NULL;
}
Example #14
0
DUK_INTERNAL void duk_heaphdr_refzero(duk_hthread *thr, duk_heaphdr *h) {
	duk_heap *heap;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(h != NULL);

	heap = thr->heap;
	DUK_DDD(DUK_DDDPRINT("refzero %p: %!O", (void *) h, (duk_heaphdr *) h));

	/*
	 *  If mark-and-sweep is running, don't process 'refzero' situations at all.
	 *  They may happen because mark-and-sweep needs to finalize refcounts for
	 *  each object it sweeps.  Otherwise the target objects of swept objects
	 *  would have incorrect refcounts.
	 *
	 *  This check must be enabled also when mark-and-sweep support has been
	 *  disabled: the flag is also used in heap destruction when running
	 *  finalizers for remaining objects, and the flag prevents objects from
	 *  being moved around in heap linked lists.
	 *
	 *  Note: mark-and-sweep could use a separate decref handler to avoid coming
	 *  here at all.  However, mark-and-sweep may also call finalizers, which
	 *  can do arbitrary operations and would use this decref variant anyway.
	 */
	if (DUK_UNLIKELY(DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap))) {
		DUK_DDD(DUK_DDDPRINT("refzero handling suppressed when mark-and-sweep running, object: %p", (void *) h));
		return;
	}

	switch ((duk_small_int_t) DUK_HEAPHDR_GET_TYPE(h)) {
	case DUK_HTYPE_STRING:
		/*
		 *  Strings have no internal references but do have "weak"
		 *  references in the string cache.  Also note that strings
		 *  are not on the heap_allocated list like other heap
		 *  elements.
		 */

		duk_heap_strcache_string_remove(heap, (duk_hstring *) h);
		duk_heap_string_remove(heap, (duk_hstring *) h);
		duk_heap_free_heaphdr_raw(heap, h);
		break;

	case DUK_HTYPE_OBJECT:
		/*
		 *  Objects have internal references.  Must finalize through
		 *  the "refzero" work list.
		 */

		duk_heap_remove_any_from_heap_allocated(heap, h);
		duk__queue_refzero(heap, h);
		duk__refzero_free_pending(thr);
		break;

	case DUK_HTYPE_BUFFER:
		/*
		 *  Buffers have no internal references.  However, a dynamic
		 *  buffer has a separate allocation for the buffer.  This is
		 *  freed by duk_heap_free_heaphdr_raw().
		 */

		duk_heap_remove_any_from_heap_allocated(heap, h);
		duk_heap_free_heaphdr_raw(heap, h);
		break;

	default:
		DUK_D(DUK_DPRINT("invalid heap type in decref: %ld", (long) DUK_HEAPHDR_GET_TYPE(h)));
		DUK_UNREACHABLE();
	}
}
Example #15
0
DUK_LOCAL void duk__refcount_finalize_hobject(duk_hthread *thr, duk_hobject *h) {
	duk_uint_fast32_t i;

	DUK_ASSERT(h);
	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h) == DUK_HTYPE_OBJECT);

	/* XXX: better to get base and walk forwards? */

	for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(h); i++) {
		duk_hstring *key = DUK_HOBJECT_E_GET_KEY(thr->heap, h, i);
		if (!key) {
			continue;
		}
		duk_heaphdr_decref(thr, (duk_heaphdr *) key);
		if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, h, i)) {
			duk_heaphdr_decref_allownull(thr, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_GETTER(thr->heap, h, i));
			duk_heaphdr_decref_allownull(thr, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_SETTER(thr->heap, h, i));
		} else {
			duk_tval_decref(thr, DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, h, i));
		}
	}

	for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ASIZE(h); i++) {
		duk_tval_decref(thr, DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, h, i));
	}

	/* hash part is a 'weak reference' and does not contribute */

	duk_heaphdr_decref_allownull(thr, (duk_heaphdr *) DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h));

	/* XXX: rearrange bits to allow a switch case to be used here? */
	/* XXX: add a fast path for objects (and arrays)? */

	/* DUK_HOBJECT_IS_ARRAY(h): needs no special handling now as there are
	 * no extra fields in need of decref.
	 */
	if (DUK_HOBJECT_IS_COMPFUNC(h)) {
		duk_hcompfunc *f = (duk_hcompfunc *) h;
		duk_tval *tv, *tv_end;
		duk_hobject **funcs, **funcs_end;

		DUK_ASSERT(DUK_HCOMPFUNC_GET_DATA(thr->heap, f) != NULL);  /* compiled functions must be created 'atomically' */

		tv = DUK_HCOMPFUNC_GET_CONSTS_BASE(thr->heap, f);
		tv_end = DUK_HCOMPFUNC_GET_CONSTS_END(thr->heap, f);
		while (tv < tv_end) {
			duk_tval_decref(thr, tv);
			tv++;
		}

		funcs = DUK_HCOMPFUNC_GET_FUNCS_BASE(thr->heap, f);
		funcs_end = DUK_HCOMPFUNC_GET_FUNCS_END(thr->heap, f);
		while (funcs < funcs_end) {
			duk_heaphdr_decref(thr, (duk_heaphdr *) *funcs);
			funcs++;
		}

		duk_heaphdr_decref(thr, (duk_heaphdr *) DUK_HCOMPFUNC_GET_DATA(thr->heap, f));
	} else if (DUK_HOBJECT_IS_NATFUNC(h)) {
		duk_hnatfunc *f = (duk_hnatfunc *) h;
		DUK_UNREF(f);
		/* nothing to finalize */
	} else if (DUK_HOBJECT_IS_BUFOBJ(h)) {
		duk_hbufobj *b = (duk_hbufobj *) h;
		if (b->buf) {
			duk_heaphdr_decref(thr, (duk_heaphdr *) b->buf);
		}
	} else if (DUK_HOBJECT_IS_THREAD(h)) {
		duk_hthread *t = (duk_hthread *) h;
		duk_tval *tv;

		tv = t->valstack;
		while (tv < t->valstack_top) {
			duk_tval_decref(thr, tv);
			tv++;
		}

		for (i = 0; i < (duk_uint_fast32_t) t->callstack_top; i++) {
			duk_activation *act = t->callstack + i;
			duk_heaphdr_decref_allownull(thr, (duk_heaphdr *) DUK_ACT_GET_FUNC(act));
			duk_heaphdr_decref_allownull(thr, (duk_heaphdr *) act->var_env);
			duk_heaphdr_decref_allownull(thr, (duk_heaphdr *) act->lex_env);
#ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY
			duk_heaphdr_decref_allownull(thr, (duk_heaphdr *) act->prev_caller);
#endif
		}

#if 0  /* nothing now */
		for (i = 0; i < (duk_uint_fast32_t) t->catchstack_top; i++) {
			duk_catcher *cat = t->catchstack + i;
		}
#endif

		for (i = 0; i < DUK_NUM_BUILTINS; i++) {
			duk_heaphdr_decref_allownull(thr, (duk_heaphdr *) t->builtins[i]);
		}

		duk_heaphdr_decref_allownull(thr, (duk_heaphdr *) t->resumer);
	}
}
Example #16
0
/* Raw helper to extract internal information / statistics about a value.
 * The return values are version specific and must not expose anything
 * that would lead to security issues (e.g. exposing compiled function
 * 'data' buffer might be an issue).  Currently only counts and sizes and
 * such are given so there should not be a security impact.
 */
duk_ret_t duk_bi_duktape_object_info(duk_context *ctx) {
	duk_tval *tv;
	duk_heaphdr *h;
	duk_int_t i, n;

	tv = duk_get_tval(ctx, 0);
	DUK_ASSERT(tv != NULL);  /* because arg count is 1 */

	duk_push_array(ctx);  /* -> [ val arr ] */

	/* type tag (public) */
	duk_push_int(ctx, duk_get_type(ctx, 0));

	/* address */
	if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
		h = DUK_TVAL_GET_HEAPHDR(tv);
		duk_push_pointer(ctx, (void *) h);
	} else {
		goto done;
	}
	DUK_ASSERT(h != NULL);

	/* refcount */
#ifdef DUK_USE_REFERENCE_COUNTING
	duk_push_int(ctx, DUK_HEAPHDR_GET_REFCOUNT(h));
#else
	duk_push_undefined(ctx);
#endif

	/* heaphdr size and additional allocation size, followed by
	 * type specific stuff (with varying value count)
	 */
	switch ((duk_small_int_t) DUK_HEAPHDR_GET_TYPE(h)) {
	case DUK_HTYPE_STRING: {
		duk_hstring *h_str = (duk_hstring *) h;
		duk_push_int(ctx, (int) (sizeof(duk_hstring) + DUK_HSTRING_GET_BYTELEN(h_str) + 1));
		break;
	}
	case DUK_HTYPE_OBJECT: {
		duk_hobject *h_obj = (duk_hobject *) h;
		duk_int_t hdr_size;
		if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h_obj)) {
			hdr_size = (duk_int_t) sizeof(duk_hcompiledfunction);
		} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h_obj)) {
			hdr_size = (duk_int_t) sizeof(duk_hnativefunction);
		} else if (DUK_HOBJECT_IS_THREAD(h_obj)) {
			hdr_size = (duk_int_t) sizeof(duk_hthread);
		} else {
			hdr_size = (duk_int_t) sizeof(duk_hobject);
		}
		duk_push_int(ctx, (int) hdr_size);
		duk_push_int(ctx, (int) DUK_HOBJECT_E_ALLOC_SIZE(h_obj));
		duk_push_int(ctx, (int) h_obj->e_size);
		duk_push_int(ctx, (int) h_obj->e_used);
		duk_push_int(ctx, (int) h_obj->a_size);
		duk_push_int(ctx, (int) h_obj->h_size);
		if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h_obj)) {
			duk_hbuffer *h_data = ((duk_hcompiledfunction *) h_obj)->data;
			if (h_data) {
				duk_push_int(ctx, DUK_HBUFFER_GET_SIZE(h_data));
			} else {
				duk_push_int(ctx, 0);
			}
		}
		break;
	}
	case DUK_HTYPE_BUFFER: {
		duk_hbuffer *h_buf = (duk_hbuffer *) h;
		if (DUK_HBUFFER_HAS_DYNAMIC(h_buf)) {
			/* XXX: when usable_size == 0, dynamic buf ptr may now be NULL, in which case
			 * the second allocation does not exist.
			 */
			duk_hbuffer_dynamic *h_dyn = (duk_hbuffer_dynamic *) h;
			duk_push_int(ctx, (int) (sizeof(duk_hbuffer_dynamic)));
			duk_push_int(ctx, (int) (DUK_HBUFFER_DYNAMIC_GET_ALLOC_SIZE(h_dyn)));
		} else {
			duk_push_int(ctx, (int) (sizeof(duk_hbuffer_fixed) + DUK_HBUFFER_GET_SIZE(h_buf) + 1));
		}
		break;

	}
	}

 done:
	/* set values into ret array */
	/* FIXME: primitive to make array from valstack slice */
	n = duk_get_top(ctx);
	for (i = 2; i < n; i++) {
		duk_dup(ctx, i);
		duk_put_prop_index(ctx, 1, i - 2);
	}
	duk_dup(ctx, 1);
	return 1;
}
Example #17
0
static void duk__sweep_heap(duk_heap *heap, duk_int_t flags, duk_size_t *out_count_keep) {
	duk_heaphdr *prev;  /* last element that was left in the heap */
	duk_heaphdr *curr;
	duk_heaphdr *next;
#ifdef DUK_USE_DEBUG
	duk_size_t count_free = 0;
	duk_size_t count_finalize = 0;
	duk_size_t count_rescue = 0;
#endif
	duk_size_t count_keep = 0;

	DUK_UNREF(flags);
	DUK_DD(DUK_DDPRINT("duk__sweep_heap: %p", (void *) heap));

	prev = NULL;
	curr = heap->heap_allocated;
	heap->heap_allocated = NULL;
	while (curr) {
		/* strings are never placed on the heap allocated list */
		DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) != DUK_HTYPE_STRING);

		next = DUK_HEAPHDR_GET_NEXT(curr);

		if (DUK_HEAPHDR_HAS_REACHABLE(curr)) {
			/*
			 *  Reachable object, keep
			 */

			DUK_DDD(DUK_DDDPRINT("sweep, reachable: %p", (void *) curr));

			if (DUK_HEAPHDR_HAS_FINALIZABLE(curr)) {
				/*
				 *  If object has been marked finalizable, move it to the
				 *  "to be finalized" work list.  It will be collected on
				 *  the next mark-and-sweep if it is still unreachable
				 *  after running the finalizer.
				 */

				DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
				DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);
				DUK_DDD(DUK_DDDPRINT("object has finalizer, move to finalization work list: %p", (void *) curr));

#ifdef DUK_USE_DOUBLE_LINKED_HEAP
				if (heap->finalize_list) {
					DUK_HEAPHDR_SET_PREV(heap->finalize_list, curr);
				}
				DUK_HEAPHDR_SET_PREV(curr, NULL);
#endif
				DUK_HEAPHDR_SET_NEXT(curr, heap->finalize_list);
				heap->finalize_list = curr;
#ifdef DUK_USE_DEBUG
				count_finalize++;
#endif
			} else {
				/*
				 *  Object will be kept; queue object back to heap_allocated (to tail)
				 */

				if (DUK_HEAPHDR_HAS_FINALIZED(curr)) {
					/*
					 *  Object's finalizer was executed on last round, and
					 *  object has been happily rescued.
					 */

					DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));
					DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);
					DUK_DD(DUK_DDPRINT("object rescued during mark-and-sweep finalization: %p", (void *) curr));
#ifdef DUK_USE_DEBUG
					count_rescue++;
#endif
				} else {
					/*
					 *  Plain, boring reachable object.
					 */
					count_keep++;
				}

				if (!heap->heap_allocated) {
					heap->heap_allocated = curr;
				}
				if (prev) {
					DUK_HEAPHDR_SET_NEXT(prev, curr);
				}
#ifdef DUK_USE_DOUBLE_LINKED_HEAP
				DUK_HEAPHDR_SET_PREV(curr, prev);
#endif
				prev = curr;
			}

			DUK_HEAPHDR_CLEAR_REACHABLE(curr);
			DUK_HEAPHDR_CLEAR_FINALIZED(curr);
			DUK_HEAPHDR_CLEAR_FINALIZABLE(curr);

			DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr));
			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));

			curr = next;
		} else {
			/*
			 *  Unreachable object, free
			 */

			DUK_DDD(DUK_DDDPRINT("sweep, not reachable: %p", (void *) curr));

#if defined(DUK_USE_REFERENCE_COUNTING)
			/* Non-zero refcounts should not happen because we refcount
			 * finalize all unreachable objects which should cancel out
			 * refcounts (even for cycles).
			 */
			DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(curr) == 0);
#endif
			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));

			if (DUK_HEAPHDR_HAS_FINALIZED(curr)) {
				DUK_DDD(DUK_DDDPRINT("finalized object not rescued: %p", (void *) curr));
			}

			/* Note: object cannot be a finalizable unreachable object, as
			 * they have been marked temporarily reachable for this round,
			 * and are handled above.
			 */

#ifdef DUK_USE_DEBUG
			count_free++;
#endif

			/* weak refs should be handled here, but no weak refs for
			 * any non-string objects exist right now.
			 */

			/* free object and all auxiliary (non-heap) allocs */
			duk_heap_free_heaphdr_raw(heap, curr);

			curr = next;
		}
	}
	if (prev) {
		DUK_HEAPHDR_SET_NEXT(prev, NULL);
	}

#ifdef DUK_USE_DEBUG
	DUK_D(DUK_DPRINT("mark-and-sweep sweep objects (non-string): %d freed, %d kept, %d rescued, %d queued for finalization",
	                 (int) count_free, (int) count_keep, (int) count_rescue, (int) count_finalize));
#endif
	*out_count_keep = count_keep;
}