DUK_LOCAL void duk__refcount_run_torture_finalizer(duk_hthread *thr, duk_hobject *obj) { duk_context *ctx; duk_int_t rc; DUK_ASSERT(thr != NULL); DUK_ASSERT(obj != NULL); ctx = (duk_context *) thr; /* Avoid fake finalization for the duk__refcount_fake_finalizer function * itself, otherwise we're in infinite recursion. */ if (DUK_HOBJECT_HAS_NATFUNC(obj)) { if (((duk_hnatfunc *) obj)->func == duk__refcount_fake_finalizer) { DUK_DD(DUK_DDPRINT("avoid fake torture finalizer for duk__refcount_fake_finalizer itself")); return; } } /* Avoid fake finalization when callstack limit has been reached. * Otherwise a callstack limit error will be created, then refzero'ed, * and we're in an infinite loop. */ if (thr->heap->call_recursion_depth >= thr->heap->call_recursion_limit || thr->callstack_size + 2 * DUK_CALLSTACK_GROW_STEP >= thr->callstack_max /*approximate*/) { DUK_D(DUK_DPRINT("call recursion depth reached, avoid fake torture finalizer")); return; } /* Run fake finalizer. Avoid creating new refzero queue entries * so that we are not forced into a forever loop. */ duk_push_c_function(ctx, duk__refcount_fake_finalizer, 1 /*nargs*/); duk_push_hobject(ctx, obj); rc = duk_pcall(ctx, 1); DUK_UNREF(rc); /* ignored */ duk_pop(ctx); }
DUK_LOCAL void duk__duplicate_ram_global_object(duk_hthread *thr) { duk_context *ctx; duk_hobject *h1; #if defined(DUK_USE_ROM_GLOBAL_CLONE) duk_hobject *h2; duk_uint8_t *props; duk_size_t alloc_size; #endif ctx = (duk_context *) thr; /* XXX: refactor into internal helper, duk_clone_hobject() */ #if defined(DUK_USE_ROM_GLOBAL_INHERIT) /* Inherit from ROM-based global object: less RAM usage, less transparent. */ duk_push_object_helper(ctx, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_GLOBAL), DUK_BIDX_GLOBAL); h1 = duk_get_hobject(ctx, -1); DUK_ASSERT(h1 != NULL); #elif defined(DUK_USE_ROM_GLOBAL_CLONE) /* Clone the properties of the ROM-based global object to create a * fully RAM-based global object. Uses more memory than the inherit * model but more compliant. */ duk_push_object_helper(ctx, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_GLOBAL), DUK_BIDX_OBJECT_PROTOTYPE); h1 = duk_get_hobject(ctx, -1); DUK_ASSERT(h1 != NULL); h2 = thr->builtins[DUK_BIDX_GLOBAL]; DUK_ASSERT(h2 != NULL); /* Copy the property table verbatim; this handles attributes etc. * For ROM objects it's not necessary (or possible) to update * refcounts so leave them as is. */ alloc_size = DUK_HOBJECT_P_ALLOC_SIZE(h2); DUK_ASSERT(alloc_size > 0); props = DUK_ALLOC(thr->heap, alloc_size); if (!props) { DUK_ERROR_ALLOC_FAILED(thr); return; } DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, h2) != NULL); DUK_MEMCPY((void *) props, (const void *) DUK_HOBJECT_GET_PROPS(thr->heap, h2), alloc_size); /* XXX: keep property attributes or tweak them here? * Properties will now be non-configurable even when they're * normally configurable for the global object. */ DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, h1) == NULL); DUK_HOBJECT_SET_PROPS(thr->heap, h1, props); DUK_HOBJECT_SET_ESIZE(h1, DUK_HOBJECT_GET_ESIZE(h2)); DUK_HOBJECT_SET_ENEXT(h1, DUK_HOBJECT_GET_ENEXT(h2)); DUK_HOBJECT_SET_ASIZE(h1, DUK_HOBJECT_GET_ASIZE(h2)); DUK_HOBJECT_SET_HSIZE(h1, DUK_HOBJECT_GET_HSIZE(h2)); #else #error internal error in defines #endif duk_hobject_compact_props(thr, h1); DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL] != NULL); DUK_ASSERT(!DUK_HEAPHDR_NEEDS_REFCOUNT_UPDATE((duk_heaphdr *) thr->builtins[DUK_BIDX_GLOBAL])); /* no need to decref */ thr->builtins[DUK_BIDX_GLOBAL] = h1; DUK_HOBJECT_INCREF(thr, h1); DUK_D(DUK_DPRINT("duplicated global object: %!O", h1)); /* Create a fresh object environment for the global scope. This is * needed so that the global scope points to the newly created RAM-based * global object. */ duk_push_object_helper(ctx, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJENV), -1); /* no prototype */ h1 = duk_get_hobject(ctx, -1); DUK_ASSERT(h1 != NULL); duk_dup(ctx, -2); duk_dup(ctx, -1); /* -> [ ... new_global new_globalenv new_global new_global ] */ duk_xdef_prop_stridx(thr, -3, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_NONE); duk_xdef_prop_stridx(thr, -2, DUK_STRIDX_INT_THIS, DUK_PROPDESC_FLAGS_NONE); /* always provideThis=true */ duk_hobject_compact_props(thr, h1); DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL_ENV] != NULL); DUK_ASSERT(!DUK_HEAPHDR_NEEDS_REFCOUNT_UPDATE((duk_heaphdr *) thr->builtins[DUK_BIDX_GLOBAL_ENV])); /* no need to decref */ thr->builtins[DUK_BIDX_GLOBAL_ENV] = h1; DUK_HOBJECT_INCREF(thr, h1); DUK_D(DUK_DPRINT("duplicated global env: %!O", h1)); duk_pop_2(ctx); }
DUK_INTERNAL void duk_err_longjmp(duk_hthread *thr) { DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); DUK_DD(DUK_DDPRINT("longjmp error: type=%d iserror=%d value1=%!T value2=%!T", (int) thr->heap->lj.type, (int) thr->heap->lj.iserror, &thr->heap->lj.value1, &thr->heap->lj.value2)); /* Prevent finalizer execution during error handling. All error * handling sites will process pending finalizers once error handling * is complete and we're ready for the side effects. Does not prevent * refzero freeing or mark-and-sweep during error handling. * * NOTE: when we come here some calling code may have used DECREF * NORZ macros without an explicit DUK_REFZERO_CHECK_xxx() call. * We don't want to do it here because it would just check for * pending finalizers and we prevent that explicitly. Instead, * the error catcher will run the finalizers once error handling * is complete. */ DUK_ASSERT_LJSTATE_SET(thr->heap); thr->heap->pf_prevent_count++; DUK_ASSERT(thr->heap->pf_prevent_count != 0); /* Wrap. */ #if defined(DUK_USE_ASSERTIONS) /* XXX: set this immediately when longjmp state is set */ DUK_ASSERT(thr->heap->error_not_allowed == 0); /* Detect error within critical section. */ thr->heap->error_not_allowed = 1; #endif DUK_DD(DUK_DDPRINT("about to longjmp, pf_prevent_count=%ld", (long) thr->heap->pf_prevent_count)); #if !defined(DUK_USE_CPP_EXCEPTIONS) /* If we don't have a jmpbuf_ptr, there is little we can do except * cause a fatal error. The caller's expectation is that we never * return. * * With C++ exceptions we now just propagate an uncaught error * instead of invoking the fatal error handler. Because there's * a dummy jmpbuf for C++ exceptions now, this could be changed. */ if (!thr->heap->lj.jmpbuf_ptr) { DUK_D(DUK_DPRINT("uncaught error: type=%d iserror=%d value1=%!T value2=%!T", (int) thr->heap->lj.type, (int) thr->heap->lj.iserror, &thr->heap->lj.value1, &thr->heap->lj.value2)); #if defined(DUK_USE_PREFER_SIZE) duk__uncaught_minimal(thr); #else duk__uncaught_error_aware(thr); #endif DUK_UNREACHABLE(); } #endif /* DUK_USE_CPP_EXCEPTIONS */ #if defined(DUK_USE_CPP_EXCEPTIONS) { duk_internal_exception exc; /* dummy */ throw exc; } #else /* DUK_USE_CPP_EXCEPTIONS */ DUK_LONGJMP(thr->heap->lj.jmpbuf_ptr->jb); #endif /* DUK_USE_CPP_EXCEPTIONS */ DUK_UNREACHABLE(); }
DUK_INTERNAL void *duk_heap_mem_realloc_indirect(duk_heap *heap, duk_mem_getptr cb, void *ud, duk_size_t newsize) { void *res; duk_bool_t rc; duk_small_int_t i; DUK_ASSERT(heap != NULL); DUK_ASSERT_DISABLE(newsize >= 0); /* * Voluntary periodic GC (if enabled) */ DUK__VOLUNTARY_PERIODIC_GC(heap); /* * First attempt */ #if defined(DUK_USE_GC_TORTURE) /* simulate alloc failure on every realloc (except when mark-and-sweep is running) */ if (!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) { DUK_DDD(DUK_DDDPRINT("gc torture enabled, pretend that first indirect realloc attempt fails")); res = NULL; DUK_UNREF(res); goto skip_attempt; } #endif res = heap->realloc_func(heap->heap_udata, cb(heap, ud), newsize); if (res || newsize == 0) { /* for zero size allocations NULL is allowed */ return res; } #if defined(DUK_USE_GC_TORTURE) skip_attempt: #endif DUK_D(DUK_DPRINT("first indirect realloc attempt failed, attempt to gc and retry")); /* * Avoid a GC if GC is already running. See duk_heap_mem_alloc(). */ if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) { DUK_D(DUK_DPRINT("duk_heap_mem_realloc_indirect() failed, gc in progress (gc skipped), alloc size %ld", (long) newsize)); return NULL; } /* * Retry with several GC attempts. Initial attempts are made without * emergency mode; later attempts use emergency mode which minimizes * memory allocations forcibly. */ for (i = 0; i < DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT; i++) { duk_small_uint_t flags; #if defined(DUK_USE_ASSERTIONS) void *ptr_pre; /* ptr before mark-and-sweep */ void *ptr_post; #endif #if defined(DUK_USE_ASSERTIONS) ptr_pre = cb(heap, ud); #endif flags = 0; if (i >= DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT - 1) { flags |= DUK_MS_FLAG_EMERGENCY; } rc = duk_heap_mark_and_sweep(heap, flags); DUK_UNREF(rc); #if defined(DUK_USE_ASSERTIONS) ptr_post = cb(heap, ud); if (ptr_pre != ptr_post) { /* useful for debugging */ DUK_DD(DUK_DDPRINT("note: base pointer changed by mark-and-sweep: %p -> %p", (void *) ptr_pre, (void *) ptr_post)); } #endif /* Note: key issue here is to re-lookup the base pointer on every attempt. * The pointer being reallocated may change after every mark-and-sweep. */ res = heap->realloc_func(heap->heap_udata, cb(heap, ud), newsize); if (res || newsize == 0) { DUK_D(DUK_DPRINT("duk_heap_mem_realloc_indirect() succeeded after gc (pass %ld), alloc size %ld", (long) (i + 1), (long) newsize)); return res; } } DUK_D(DUK_DPRINT("duk_heap_mem_realloc_indirect() failed even after gc, alloc size %ld", (long) newsize)); return NULL; }
duk_heap *duk_heap_alloc(duk_alloc_function alloc_func, duk_realloc_function realloc_func, duk_free_function free_func, void *alloc_udata, duk_fatal_function fatal_func) { duk_heap *res = NULL; DUK_D(DUK_DPRINT("allocate heap")); /* Debug dump type sizes */ #ifdef DUK_USE_DEBUG duk__dump_type_sizes(); #endif /* If selftests enabled, run them as early as possible. */ #ifdef DUK_USE_SELF_TESTS DUK_D(DUK_DPRINT("running self tests")); duk_selftest_run_tests(); DUK_D(DUK_DPRINT("self tests passed")); #endif #ifdef DUK_USE_COMPUTED_NAN do { /* Workaround for some exotic platforms where NAN is missing * and the expression (0.0 / 0.0) does NOT result in a NaN. * Such platforms use the global 'duk_computed_nan' which must * be initialized at runtime. Use 'volatile' to ensure that * the compiler will actually do the computation and not try * to do constant folding which might result in the original * problem. */ volatile double dbl1 = 0.0; volatile double dbl2 = 0.0; duk_computed_nan = dbl1 / dbl2; } while (0); #endif #ifdef DUK_USE_COMPUTED_INFINITY do { /* Similar workaround for INFINITY. */ volatile double dbl1 = 1.0; volatile double dbl2 = 0.0; duk_computed_infinity = dbl1 / dbl2; } while (0); #endif /* use a raw call, all macros expect the heap to be initialized */ res = (duk_heap *) alloc_func(alloc_udata, sizeof(duk_heap)); if (!res) { goto error; } /* zero everything */ DUK_MEMZERO(res, sizeof(*res)); /* explicit NULL inits */ #ifdef DUK_USE_EXPLICIT_NULL_INIT res->alloc_udata = NULL; res->heap_allocated = NULL; #ifdef DUK_USE_REFERENCE_COUNTING res->refzero_list = NULL; res->refzero_list_tail = NULL; #endif #ifdef DUK_USE_MARK_AND_SWEEP res->finalize_list = NULL; #endif res->heap_thread = NULL; res->curr_thread = NULL; res->heap_object = NULL; res->log_buffer = NULL; res->st = NULL; { int i; for (i = 0; i < DUK_HEAP_NUM_STRINGS; i++) { res->strs[i] = NULL; } } #endif /* initialize the structure, roughly in order */ res->alloc_func = alloc_func; res->realloc_func = realloc_func; res->free_func = free_func; res->alloc_udata = alloc_udata; res->fatal_func = fatal_func; /* res->mark_and_sweep_trigger_counter == 0 -> now causes immediate GC; which is OK */ res->call_recursion_depth = 0; res->call_recursion_limit = DUK_HEAP_DEFAULT_CALL_RECURSION_LIMIT; /* FIXME: use the pointer as a seed for now: mix in time at least */ /* cast through C99 intptr_t to avoid GCC warning: * * warning: cast from pointer to integer of different size [-Wpointer-to-int-cast] */ res->hash_seed = (duk_uint32_t) (duk_intptr_t) res; res->rnd_state = (duk_uint32_t) (duk_intptr_t) res; #ifdef DUK_USE_INTERRUPT_COUNTER /* zero value causes an interrupt before executing first instruction */ DUK_ASSERT(res->interrupt_counter == 0); DUK_ASSERT(res->interrupt_init == 0); #endif #ifdef DUK_USE_EXPLICIT_NULL_INIT res->lj.jmpbuf_ptr = NULL; #endif DUK_ASSERT(res->lj.type == DUK_LJ_TYPE_UNKNOWN); /* zero */ DUK_TVAL_SET_UNDEFINED_UNUSED(&res->lj.value1); DUK_TVAL_SET_UNDEFINED_UNUSED(&res->lj.value2); #if (DUK_STRTAB_INITIAL_SIZE < DUK_UTIL_MIN_HASH_PRIME) #error initial heap stringtable size is defined incorrectly #endif res->st = (duk_hstring **) alloc_func(alloc_udata, sizeof(duk_hstring *) * DUK_STRTAB_INITIAL_SIZE); if (!res->st) { goto error; } res->st_size = DUK_STRTAB_INITIAL_SIZE; #ifdef DUK_USE_EXPLICIT_NULL_INIT { duk_uint_fast32_t i; for (i = 0; i < res->st_size; i++) { res->st[i] = NULL; } } #else DUK_MEMZERO(res->st, sizeof(duk_hstring *) * DUK_STRTAB_INITIAL_SIZE); #endif /* strcache init */ #ifdef DUK_USE_EXPLICIT_NULL_INIT { int i; for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) { res->strcache[i].h = NULL; } } #endif /* FIXME: error handling is incomplete. It would be cleanest if * there was a setjmp catchpoint, so that all init code could * freely throw errors. If that were the case, the return code * passing here could be removed. */ /* built-in strings */ DUK_DD(DUK_DDPRINT("HEAP: INIT STRINGS")); if (!duk__init_heap_strings(res)) { goto error; } /* heap thread */ DUK_DD(DUK_DDPRINT("HEAP: INIT HEAP THREAD")); if (!duk__init_heap_thread(res)) { goto error; } /* heap object */ DUK_DD(DUK_DDPRINT("HEAP: INIT HEAP OBJECT")); DUK_ASSERT(res->heap_thread != NULL); res->heap_object = duk_hobject_alloc(res, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT)); if (!res->heap_object) { goto error; } DUK_HOBJECT_INCREF(res->heap_thread, res->heap_object); /* log buffer */ DUK_DD(DUK_DDPRINT("HEAP: INIT LOG BUFFER")); res->log_buffer = (duk_hbuffer_dynamic *) duk_hbuffer_alloc(res, DUK_BI_LOGGER_SHORT_MSG_LIMIT, 1 /*dynamic*/); if (!res->log_buffer) { goto error; } DUK_HBUFFER_INCREF(res->heap_thread, res->log_buffer); DUK_D(DUK_DPRINT("allocated heap: %p", res)); return res; error: DUK_D(DUK_DPRINT("heap allocation failed")); if (res) { /* assumes that allocated pointers and alloc funcs are valid * if res exists */ DUK_ASSERT(res->alloc_func != NULL); DUK_ASSERT(res->realloc_func != NULL); DUK_ASSERT(res->free_func != NULL); duk_heap_free(res); } return NULL; }
/* Allocate a new duk_hbuffer of a certain type and return a pointer to it * (NULL on error). Write buffer data pointer to 'out_bufdata' (only if * allocation successful). */ DUK_INTERNAL duk_hbuffer *duk_hbuffer_alloc(duk_heap *heap, duk_size_t size, duk_small_uint_t flags, void **out_bufdata) { duk_hbuffer *res = NULL; duk_size_t header_size; duk_size_t alloc_size; DUK_ASSERT(heap != NULL); DUK_ASSERT(out_bufdata != NULL); DUK_DDD(DUK_DDDPRINT("allocate hbuffer")); /* Size sanity check. Should not be necessary because caller is * required to check this, but we don't want to cause a segfault * if the size wraps either in duk_size_t computation or when * storing the size in a 16-bit field. */ if (size > DUK_HBUFFER_MAX_BYTELEN) { DUK_D(DUK_DPRINT("hbuffer alloc failed: size too large: %ld", (long) size)); return NULL; /* no need to write 'out_bufdata' */ } if (flags & DUK_BUF_FLAG_EXTERNAL) { header_size = sizeof(duk_hbuffer_external); alloc_size = sizeof(duk_hbuffer_external); } else if (flags & DUK_BUF_FLAG_DYNAMIC) { header_size = sizeof(duk_hbuffer_dynamic); alloc_size = sizeof(duk_hbuffer_dynamic); } else { header_size = sizeof(duk_hbuffer_fixed); alloc_size = sizeof(duk_hbuffer_fixed) + size; DUK_ASSERT(alloc_size >= sizeof(duk_hbuffer_fixed)); /* no wrapping */ } res = (duk_hbuffer *) DUK_ALLOC(heap, alloc_size); if (DUK_UNLIKELY(res == NULL)) { goto alloc_error; } /* zero everything unless requested not to do so */ #if defined(DUK_USE_ZERO_BUFFER_DATA) DUK_MEMZERO((void *) res, (flags & DUK_BUF_FLAG_NOZERO) ? header_size : alloc_size); #else DUK_MEMZERO((void *) res, header_size); #endif if (flags & DUK_BUF_FLAG_EXTERNAL) { duk_hbuffer_external *h; h = (duk_hbuffer_external *) res; DUK_UNREF(h); *out_bufdata = NULL; #if defined(DUK_USE_EXPLICIT_NULL_INIT) #if defined(DUK_USE_HEAPPTR16) /* the compressed pointer is zeroed which maps to NULL, so nothing to do. */ #else DUK_HBUFFER_EXTERNAL_SET_DATA_PTR(heap, h, NULL); #endif #endif DUK_ASSERT(DUK_HBUFFER_EXTERNAL_GET_DATA_PTR(heap, h) == NULL); } else if (flags & DUK_BUF_FLAG_DYNAMIC) { duk_hbuffer_dynamic *h = (duk_hbuffer_dynamic *) res; void *ptr; if (size > 0) { DUK_ASSERT(!(flags & DUK_BUF_FLAG_EXTERNAL)); /* alloc external with size zero */ DUK_DDD(DUK_DDDPRINT("dynamic buffer with nonzero size, alloc actual buffer")); #if defined(DUK_USE_ZERO_BUFFER_DATA) ptr = DUK_ALLOC_ZEROED(heap, size); #else ptr = DUK_ALLOC(heap, size); #endif if (DUK_UNLIKELY(ptr == NULL)) { /* Because size > 0, NULL check is correct */ goto alloc_error; } *out_bufdata = ptr; DUK_HBUFFER_DYNAMIC_SET_DATA_PTR(heap, h, ptr); } else { *out_bufdata = NULL; #if defined(DUK_USE_EXPLICIT_NULL_INIT) #if defined(DUK_USE_HEAPPTR16) /* the compressed pointer is zeroed which maps to NULL, so nothing to do. */ #else DUK_HBUFFER_DYNAMIC_SET_DATA_PTR(heap, h, NULL); #endif #endif DUK_ASSERT(DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(heap, h) == NULL); } } else { *out_bufdata = (void *) ((duk_hbuffer_fixed *) res + 1); } DUK_HBUFFER_SET_SIZE(res, size); DUK_HEAPHDR_SET_TYPE(&res->hdr, DUK_HTYPE_BUFFER); if (flags & DUK_BUF_FLAG_DYNAMIC) { DUK_HBUFFER_SET_DYNAMIC(res); if (flags & DUK_BUF_FLAG_EXTERNAL) { DUK_HBUFFER_SET_EXTERNAL(res); } } else { DUK_ASSERT(!(flags & DUK_BUF_FLAG_EXTERNAL)); } DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, &res->hdr); DUK_DDD(DUK_DDDPRINT("allocated hbuffer: %p", (void *) res)); return res; alloc_error: DUK_DD(DUK_DDPRINT("hbuffer allocation failed")); DUK_FREE(heap, res); return NULL; /* no need to write 'out_bufdata' */ }
DUK_LOCAL void duk__mark_hobject(duk_heap *heap, duk_hobject *h) { duk_uint_fast32_t i; DUK_DDD(DUK_DDDPRINT("duk__mark_hobject: %p", (void *) h)); DUK_ASSERT(h); /* XXX: use advancing pointers instead of index macros -> faster and smaller? */ for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(h); i++) { duk_hstring *key = DUK_HOBJECT_E_GET_KEY(heap, h, i); if (!key) { continue; } duk__mark_heaphdr(heap, (duk_heaphdr *) key); if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(heap, h, i)) { duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->a.get); duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->a.set); } else { duk__mark_tval(heap, &DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->v); } } for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ASIZE(h); i++) { duk__mark_tval(heap, DUK_HOBJECT_A_GET_VALUE_PTR(heap, h, i)); } /* hash part is a 'weak reference' and does not contribute */ duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_GET_PROTOTYPE(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 marking. */ if (DUK_HOBJECT_IS_COMPFUNC(h)) { duk_hcompfunc *f = (duk_hcompfunc *) h; duk_tval *tv, *tv_end; duk_hobject **fn, **fn_end; /* 'data' is reachable through every compiled function which * contains a reference. */ duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HCOMPFUNC_GET_DATA(heap, f)); duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HCOMPFUNC_GET_LEXENV(heap, f)); duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HCOMPFUNC_GET_VARENV(heap, f)); if (DUK_HCOMPFUNC_GET_DATA(heap, f) != NULL) { tv = DUK_HCOMPFUNC_GET_CONSTS_BASE(heap, f); tv_end = DUK_HCOMPFUNC_GET_CONSTS_END(heap, f); while (tv < tv_end) { duk__mark_tval(heap, tv); tv++; } fn = DUK_HCOMPFUNC_GET_FUNCS_BASE(heap, f); fn_end = DUK_HCOMPFUNC_GET_FUNCS_END(heap, f); while (fn < fn_end) { duk__mark_heaphdr(heap, (duk_heaphdr *) *fn); fn++; } } else { /* May happen in some out-of-memory corner cases. */ DUK_D(DUK_DPRINT("duk_hcompfunc 'data' is NULL, skipping marking")); } } else if (DUK_HOBJECT_IS_NATFUNC(h)) { duk_hnatfunc *f = (duk_hnatfunc *) h; DUK_UNREF(f); /* nothing to mark */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) } else if (DUK_HOBJECT_IS_BUFOBJ(h)) { duk_hbufobj *b = (duk_hbufobj *) h; duk__mark_heaphdr(heap, (duk_heaphdr *) b->buf); duk__mark_heaphdr(heap, (duk_heaphdr *) b->buf_prop); #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ } 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__mark_tval(heap, tv); tv++; } for (i = 0; i < (duk_uint_fast32_t) t->callstack_top; i++) { duk_activation *act = t->callstack + i; duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_ACT_GET_FUNC(act)); duk__mark_heaphdr(heap, (duk_heaphdr *) act->var_env); duk__mark_heaphdr(heap, (duk_heaphdr *) act->lex_env); #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY) duk__mark_heaphdr(heap, (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 duk__mark_heaphdr(heap, (duk_heaphdr *) t->resumer); /* XXX: duk_small_uint_t would be enough for this loop */ for (i = 0; i < DUK_NUM_BUILTINS; i++) { duk__mark_heaphdr(heap, (duk_heaphdr *) t->builtins[i]); } } }
DUK_LOCAL void duk__dump_type_sizes(void) { DUK_D(DUK_DPRINT("sizeof()")); /* basic platform types */ DUK__DUMPSZ(char); DUK__DUMPSZ(short); DUK__DUMPSZ(int); DUK__DUMPSZ(long); DUK__DUMPSZ(double); DUK__DUMPSZ(void *); DUK__DUMPSZ(size_t); /* basic types from duk_features.h */ DUK__DUMPSZ(duk_uint8_t); DUK__DUMPSZ(duk_int8_t); DUK__DUMPSZ(duk_uint16_t); DUK__DUMPSZ(duk_int16_t); DUK__DUMPSZ(duk_uint32_t); DUK__DUMPSZ(duk_int32_t); DUK__DUMPSZ(duk_uint64_t); DUK__DUMPSZ(duk_int64_t); DUK__DUMPSZ(duk_uint_least8_t); DUK__DUMPSZ(duk_int_least8_t); DUK__DUMPSZ(duk_uint_least16_t); DUK__DUMPSZ(duk_int_least16_t); DUK__DUMPSZ(duk_uint_least32_t); DUK__DUMPSZ(duk_int_least32_t); #if defined(DUK_USE_64BIT_OPS) DUK__DUMPSZ(duk_uint_least64_t); DUK__DUMPSZ(duk_int_least64_t); #endif DUK__DUMPSZ(duk_uint_fast8_t); DUK__DUMPSZ(duk_int_fast8_t); DUK__DUMPSZ(duk_uint_fast16_t); DUK__DUMPSZ(duk_int_fast16_t); DUK__DUMPSZ(duk_uint_fast32_t); DUK__DUMPSZ(duk_int_fast32_t); #if defined(DUK_USE_64BIT_OPS) DUK__DUMPSZ(duk_uint_fast64_t); DUK__DUMPSZ(duk_int_fast64_t); #endif DUK__DUMPSZ(duk_uintptr_t); DUK__DUMPSZ(duk_intptr_t); DUK__DUMPSZ(duk_uintmax_t); DUK__DUMPSZ(duk_intmax_t); DUK__DUMPSZ(duk_double_t); /* important chosen base types */ DUK__DUMPSZ(duk_int_t); DUK__DUMPSZ(duk_uint_t); DUK__DUMPSZ(duk_int_fast_t); DUK__DUMPSZ(duk_uint_fast_t); DUK__DUMPSZ(duk_small_int_t); DUK__DUMPSZ(duk_small_uint_t); DUK__DUMPSZ(duk_small_int_fast_t); DUK__DUMPSZ(duk_small_uint_fast_t); /* some derived types */ DUK__DUMPSZ(duk_codepoint_t); DUK__DUMPSZ(duk_ucodepoint_t); DUK__DUMPSZ(duk_idx_t); DUK__DUMPSZ(duk_errcode_t); DUK__DUMPSZ(duk_uarridx_t); /* tval */ DUK__DUMPSZ(duk_double_union); DUK__DUMPSZ(duk_tval); /* structs from duk_forwdecl.h */ DUK__DUMPSZ(duk_jmpbuf); DUK__DUMPSZ(duk_heaphdr); DUK__DUMPSZ(duk_heaphdr_string); DUK__DUMPSZ(duk_hstring); DUK__DUMPSZ(duk_hstring_external); DUK__DUMPSZ(duk_hobject); DUK__DUMPSZ(duk_hcompiledfunction); DUK__DUMPSZ(duk_hnativefunction); DUK__DUMPSZ(duk_hthread); DUK__DUMPSZ(duk_hbuffer); DUK__DUMPSZ(duk_hbuffer_fixed); DUK__DUMPSZ(duk_hbuffer_dynamic); DUK__DUMPSZ(duk_propaccessor); DUK__DUMPSZ(duk_propvalue); DUK__DUMPSZ(duk_propdesc); DUK__DUMPSZ(duk_heap); #if defined(DUK_USE_STRTAB_CHAIN) DUK__DUMPSZ(duk_strtab_entry); #endif DUK__DUMPSZ(duk_activation); DUK__DUMPSZ(duk_catcher); DUK__DUMPSZ(duk_strcache); DUK__DUMPSZ(duk_ljstate); DUK__DUMPSZ(duk_fixedbuffer); DUK__DUMPSZ(duk_bitdecoder_ctx); DUK__DUMPSZ(duk_bitencoder_ctx); DUK__DUMPSZ(duk_token); DUK__DUMPSZ(duk_re_token); DUK__DUMPSZ(duk_lexer_point); DUK__DUMPSZ(duk_lexer_ctx); DUK__DUMPSZ(duk_compiler_instr); DUK__DUMPSZ(duk_compiler_func); DUK__DUMPSZ(duk_compiler_ctx); DUK__DUMPSZ(duk_re_matcher_ctx); DUK__DUMPSZ(duk_re_compiler_ctx); }
DUK_INTERNAL void duk_err_create_and_throw(duk_hthread *thr, duk_errcode_t code, const char *msg, const char *filename, duk_int_t line) { #else DUK_INTERNAL void duk_err_create_and_throw(duk_hthread *thr, duk_errcode_t code) { #endif duk_context *ctx = (duk_context *) thr; duk_bool_t double_error = thr->heap->handling_error; #ifdef DUK_USE_VERBOSE_ERRORS DUK_DD(DUK_DDPRINT("duk_err_create_and_throw(): code=%ld, msg=%s, filename=%s, line=%ld", (long) code, (const char *) msg, (const char *) filename, (long) line)); #else DUK_DD(DUK_DDPRINT("duk_err_create_and_throw(): code=%ld", (long) code)); #endif DUK_ASSERT(thr != NULL); DUK_ASSERT(ctx != NULL); thr->heap->handling_error = 1; /* * Create and push an error object onto the top of stack. * If a "double error" occurs, use a fixed error instance * to avoid further trouble. */ /* XXX: if attempt to push beyond allocated valstack, this double fault * handling fails miserably. We should really write the double error * directly to thr->heap->lj.value1 and avoid valstack use entirely. */ if (double_error) { if (thr->builtins[DUK_BIDX_DOUBLE_ERROR]) { DUK_D(DUK_DPRINT("double fault detected -> push built-in fixed 'double error' instance")); duk_push_hobject_bidx(ctx, DUK_BIDX_DOUBLE_ERROR); } else { DUK_D(DUK_DPRINT("double fault detected; there is no built-in fixed 'double error' instance " "-> push the error code as a number")); duk_push_int(ctx, (duk_int_t) code); } } else { /* Error object is augmented at its creation here. */ duk_require_stack(ctx, 1); /* XXX: unnecessary '%s' formatting here, but cannot use * 'msg' as a format string directly. */ #ifdef DUK_USE_VERBOSE_ERRORS duk_push_error_object_raw(ctx, code | DUK_ERRCODE_FLAG_NOBLAME_FILELINE, filename, line, "%s", (const char *) msg); #else duk_push_error_object_raw(ctx, code | DUK_ERRCODE_FLAG_NOBLAME_FILELINE, NULL, 0, NULL); #endif } /* * Augment error (throw time), unless alloc/double error */ if (double_error || code == DUK_ERR_ALLOC_ERROR) { DUK_D(DUK_DPRINT("alloc or double error: skip throw augmenting to avoid further trouble")); } else { #if defined(DUK_USE_AUGMENT_ERROR_THROW) DUK_DDD(DUK_DDDPRINT("THROW ERROR (INTERNAL): %!iT (before throw augment)", (duk_tval *) duk_get_tval(ctx, -1))); duk_err_augment_error_throw(thr); #endif } /* * Finally, longjmp */ thr->heap->handling_error = 0; duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_THROW); DUK_DDD(DUK_DDDPRINT("THROW ERROR (INTERNAL): %!iT, %!iT (after throw augment)", (duk_tval *) &thr->heap->lj.value1, (duk_tval *) &thr->heap->lj.value2)); duk_err_longjmp(thr); DUK_UNREACHABLE(); }
void *duk_heap_mem_realloc(duk_heap *heap, void *ptr, size_t newsize) { void *res; int rc; int i; DUK_ASSERT(heap != NULL); /* ptr may be NULL */ DUK_ASSERT_DISABLE(newsize >= 0); /* * Voluntary periodic GC (if enabled) */ DUK__VOLUNTARY_PERIODIC_GC(heap); /* * First attempt */ #ifdef DUK_USE_GC_TORTURE /* simulate alloc failure on every realloc (except when mark-and-sweep is running) */ if (!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) { DUK_DDD(DUK_DDDPRINT("gc torture enabled, pretend that first realloc attempt fails")); res = NULL; DUK_UNREF(res); goto skip_attempt; } #endif res = heap->realloc_func(heap->alloc_udata, ptr, newsize); if (res || newsize == 0) { /* for zero size allocations NULL is allowed */ return res; } #ifdef DUK_USE_GC_TORTURE skip_attempt: #endif DUK_D(DUK_DPRINT("first realloc attempt failed, attempt to gc and retry")); /* * Avoid a GC if GC is already running. See duk_heap_mem_alloc(). */ if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) { DUK_D(DUK_DPRINT("duk_heap_mem_realloc() failed, gc in progress (gc skipped), alloc size %d", newsize)); return NULL; } /* * Retry with several GC attempts. Initial attempts are made without * emergency mode; later attempts use emergency mode which minimizes * memory allocations forcibly. */ for (i = 0; i < DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT; i++) { int flags; flags = 0; if (i >= DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT - 1) { flags |= DUK_MS_FLAG_EMERGENCY; } rc = duk_heap_mark_and_sweep(heap, flags); DUK_UNREF(rc); res = heap->realloc_func(heap->alloc_udata, ptr, newsize); if (res) { DUK_D(DUK_DPRINT("duk_heap_mem_realloc() succeeded after gc (pass %d), alloc size %d", i + 1, newsize)); return res; } } DUK_D(DUK_DPRINT("duk_heap_mem_realloc() failed even after gc, alloc size %d", newsize)); return NULL; }
DUK_INTERNAL void duk_regexp_compile(duk_hthread *thr) { duk_context *ctx = (duk_context *) thr; duk_re_compiler_ctx re_ctx; duk_lexer_point lex_point; duk_hstring *h_pattern; duk_hstring *h_flags; duk__re_disjunction_info ign_disj; DUK_ASSERT(thr != NULL); DUK_ASSERT(ctx != NULL); /* * Args validation */ /* TypeError if fails */ h_pattern = duk_require_hstring(ctx, -2); h_flags = duk_require_hstring(ctx, -1); /* * Create normalized 'source' property (E5 Section 15.10.3). */ /* [ ... pattern flags ] */ duk__create_escaped_source(thr, -2); /* [ ... pattern flags escaped_source ] */ /* * Init compilation context */ /* [ ... pattern flags escaped_source buffer ] */ DUK_MEMZERO(&re_ctx, sizeof(re_ctx)); DUK_LEXER_INITCTX(&re_ctx.lex); /* duplicate zeroing, expect for (possible) NULL inits */ re_ctx.thr = thr; re_ctx.lex.thr = thr; re_ctx.lex.input = DUK_HSTRING_GET_DATA(h_pattern); re_ctx.lex.input_length = DUK_HSTRING_GET_BYTELEN(h_pattern); re_ctx.lex.token_limit = DUK_RE_COMPILE_TOKEN_LIMIT; re_ctx.recursion_limit = DUK_USE_REGEXP_COMPILER_RECLIMIT; re_ctx.re_flags = duk__parse_regexp_flags(thr, h_flags); DUK_BW_INIT_PUSHBUF(thr, &re_ctx.bw, DUK__RE_INITIAL_BUFSIZE); DUK_DD(DUK_DDPRINT("regexp compiler ctx initialized, flags=0x%08lx, recursion_limit=%ld", (unsigned long) re_ctx.re_flags, (long) re_ctx.recursion_limit)); /* * Init lexer */ lex_point.offset = 0; /* expensive init, just want to fill window */ lex_point.line = 1; DUK_LEXER_SETPOINT(&re_ctx.lex, &lex_point); /* * Compilation */ DUK_D(DUK_DPRINT("starting regexp compilation")); duk__append_u32(&re_ctx, DUK_REOP_SAVE); duk__append_u32(&re_ctx, 0); duk__parse_disjunction(&re_ctx, 1 /*expect_eof*/, &ign_disj); duk__append_u32(&re_ctx, DUK_REOP_SAVE); duk__append_u32(&re_ctx, 1); duk__append_u32(&re_ctx, DUK_REOP_MATCH); /* * Check for invalid backreferences; note that it is NOT an error * to back-reference a capture group which has not yet been introduced * in the pattern (as in /\1(foo)/); in fact, the backreference will * always match! It IS an error to back-reference a capture group * which will never be introduced in the pattern. Thus, we can check * for such references only after parsing is complete. */ if (re_ctx.highest_backref > re_ctx.captures) { DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_BACKREFS); } /* * Emit compiled regexp header: flags, ncaptures * (insertion order inverted on purpose) */ duk__insert_u32(&re_ctx, 0, (re_ctx.captures + 1) * 2); duk__insert_u32(&re_ctx, 0, re_ctx.re_flags); /* [ ... pattern flags escaped_source buffer ] */ DUK_BW_COMPACT(thr, &re_ctx.bw); duk_to_string(ctx, -1); /* coerce to string */ /* [ ... pattern flags escaped_source bytecode ] */ /* * Finalize stack */ duk_remove(ctx, -4); /* -> [ ... flags escaped_source bytecode ] */ duk_remove(ctx, -3); /* -> [ ... escaped_source bytecode ] */ DUK_D(DUK_DPRINT("regexp compilation successful, bytecode: %!T, escaped source: %!T", (duk_tval *) duk_get_tval(ctx, -1), (duk_tval *) duk_get_tval(ctx, -2))); }
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 }
DUK_LOCAL void duk__sweep_stringtable_probe(duk_heap *heap, duk_size_t *out_count_keep) { duk_hstring *h; duk_uint_fast32_t i; #ifdef 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)); for (i = 0; i < heap->st_size; i++) { #if defined(DUK_USE_HEAPPTR16) h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]); #else h = heap->strtable[i]; #endif if (h == NULL || h == DUK_STRTAB_DELETED_MARKER(heap)) { continue; } else if (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h)) { DUK_HEAPHDR_CLEAR_REACHABLE((duk_heaphdr *) h); count_keep++; continue; } #ifdef 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 DUK_DDD(DUK_DDDPRINT("sweep string, not reachable: %p", (void *) h)); /* deal with weak references first */ duk_heap_strcache_string_remove(heap, (duk_hstring *) h); /* remove the string (mark DELETED), could also call * duk_heap_string_remove() but that would be slow and * pointless because we already know the slot. */ #if defined(DUK_USE_HEAPPTR16) heap->strtable16[i] = heap->heapptr_deleted16; #else heap->strtable[i] = DUK_STRTAB_DELETED_MARKER(heap); #endif /* free inner references (these exist e.g. when external * strings are enabled) */ duk_free_hstring_inner(heap, (duk_hstring *) h); /* finally free the struct itself */ DUK_FREE(heap, h); } #ifdef 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; }
DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) { duk_context *ctx = (duk_context *) thr; duk_bitdecoder_ctx bd_ctx; duk_bitdecoder_ctx *bd = &bd_ctx; /* convenience */ duk_hobject *h; duk_small_uint_t i, j; DUK_D(DUK_DPRINT("INITBUILTINS BEGIN")); DUK_MEMZERO(&bd_ctx, sizeof(bd_ctx)); bd->data = (const duk_uint8_t *) duk_builtins_data; bd->length = (duk_size_t) DUK_BUILTINS_DATA_LENGTH; /* * First create all built-in bare objects on the empty valstack. * During init, their indices will correspond to built-in indices. * * Built-ins will be reachable from both valstack and thr->builtins. */ /* XXX: there is no need to resize valstack because builtin count * is much less than the default space; assert for it. */ DUK_DD(DUK_DDPRINT("create empty built-ins")); DUK_ASSERT_TOP(ctx, 0); for (i = 0; i < DUK_NUM_BUILTINS; i++) { duk_small_uint_t class_num; duk_small_int_t len = -1; /* must be signed */ class_num = (duk_small_uint_t) duk_bd_decode(bd, DUK__CLASS_BITS); len = (duk_small_int_t) duk_bd_decode_flagged(bd, DUK__LENGTH_PROP_BITS, (duk_int32_t) -1 /*def_value*/); if (class_num == DUK_HOBJECT_CLASS_FUNCTION) { duk_small_uint_t natidx; duk_small_uint_t stridx; duk_int_t c_nargs; /* must hold DUK_VARARGS */ duk_c_function c_func; duk_int16_t magic; DUK_DDD(DUK_DDDPRINT("len=%ld", (long) len)); DUK_ASSERT(len >= 0); natidx = (duk_small_uint_t) duk_bd_decode(bd, DUK__NATIDX_BITS); stridx = (duk_small_uint_t) duk_bd_decode(bd, DUK__STRIDX_BITS); c_func = duk_bi_native_functions[natidx]; c_nargs = (duk_small_uint_t) duk_bd_decode_flagged(bd, DUK__NARGS_BITS, len /*def_value*/); if (c_nargs == DUK__NARGS_VARARGS_MARKER) { c_nargs = DUK_VARARGS; } /* XXX: set magic directly here? (it could share the c_nargs arg) */ duk_push_c_function_noexotic(ctx, c_func, c_nargs); h = duk_require_hobject(ctx, -1); DUK_ASSERT(h != NULL); /* Currently all built-in native functions are strict. * duk_push_c_function() now sets strict flag, so * assert for it. */ DUK_ASSERT(DUK_HOBJECT_HAS_STRICT(h)); /* XXX: function properties */ duk_push_hstring_stridx(ctx, stridx); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE); /* Almost all global level Function objects are constructable * but not all: Function.prototype is a non-constructable, * callable Function. */ if (duk_bd_decode_flag(bd)) { DUK_ASSERT(DUK_HOBJECT_HAS_CONSTRUCTABLE(h)); } else { DUK_HOBJECT_CLEAR_CONSTRUCTABLE(h); } /* Cast converts magic to 16-bit signed value */ magic = (duk_int16_t) duk_bd_decode_flagged(bd, DUK__MAGIC_BITS, 0 /*def_value*/); ((duk_hnativefunction *) h)->magic = magic; } else { /* XXX: ARRAY_PART for Array prototype? */ duk_push_object_helper(ctx, DUK_HOBJECT_FLAG_EXTENSIBLE, -1); /* no prototype or class yet */ h = duk_require_hobject(ctx, -1); DUK_ASSERT(h != NULL); } DUK_HOBJECT_SET_CLASS_NUMBER(h, class_num); thr->builtins[i] = h; DUK_HOBJECT_INCREF(thr, &h->hdr); if (len >= 0) { /* * For top-level objects, 'length' property has the following * default attributes: non-writable, non-enumerable, non-configurable * (E5 Section 15). * * However, 'length' property for Array.prototype has attributes * expected of an Array instance which are different: writable, * non-enumerable, non-configurable (E5 Section 15.4.5.2). * * This is currently determined implicitly based on class; there are * no attribute flags in the init data. */ duk_push_int(ctx, len); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, (class_num == DUK_HOBJECT_CLASS_ARRAY ? /* only Array.prototype matches */ DUK_PROPDESC_FLAGS_W : DUK_PROPDESC_FLAGS_NONE)); } /* enable exotic behaviors last */ if (class_num == DUK_HOBJECT_CLASS_ARRAY) { DUK_HOBJECT_SET_EXOTIC_ARRAY(h); } if (class_num == DUK_HOBJECT_CLASS_STRING) { DUK_HOBJECT_SET_EXOTIC_STRINGOBJ(h); } /* some assertions */ DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(h)); /* DUK_HOBJECT_FLAG_CONSTRUCTABLE varies */ DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(h)); DUK_ASSERT(!DUK_HOBJECT_HAS_COMPILEDFUNCTION(h)); /* DUK_HOBJECT_FLAG_NATIVEFUNCTION varies */ DUK_ASSERT(!DUK_HOBJECT_HAS_THREAD(h)); DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(h)); /* currently, even for Array.prototype */ /* DUK_HOBJECT_FLAG_STRICT varies */ DUK_ASSERT(!DUK_HOBJECT_HAS_NATIVEFUNCTION(h) || /* all native functions have NEWENV */ DUK_HOBJECT_HAS_NEWENV(h)); DUK_ASSERT(!DUK_HOBJECT_HAS_NAMEBINDING(h)); DUK_ASSERT(!DUK_HOBJECT_HAS_CREATEARGS(h)); DUK_ASSERT(!DUK_HOBJECT_HAS_ENVRECCLOSED(h)); /* DUK_HOBJECT_FLAG_EXOTIC_ARRAY varies */ /* DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ varies */ DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(h)); DUK_DDD(DUK_DDDPRINT("created built-in %ld, class=%ld, length=%ld", (long) i, (long) class_num, (long) len)); } /* * Then decode the builtins init data (see genbuiltins.py) to * init objects */ DUK_DD(DUK_DDPRINT("initialize built-in object properties")); for (i = 0; i < DUK_NUM_BUILTINS; i++) { duk_small_uint_t t; duk_small_uint_t num; DUK_DDD(DUK_DDDPRINT("initializing built-in object at index %ld", (long) i)); h = thr->builtins[i]; t = (duk_small_uint_t) duk_bd_decode(bd, DUK__BIDX_BITS); if (t != DUK__NO_BIDX_MARKER) { DUK_DDD(DUK_DDDPRINT("set internal prototype: built-in %ld", (long) t)); DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h, thr->builtins[t]); } t = (duk_small_uint_t) duk_bd_decode(bd, DUK__BIDX_BITS); if (t != DUK__NO_BIDX_MARKER) { /* 'prototype' property for all built-in objects (which have it) has attributes: * [[Writable]] = false, * [[Enumerable]] = false, * [[Configurable]] = false */ DUK_DDD(DUK_DDDPRINT("set external prototype: built-in %ld", (long) t)); duk_xdef_prop_stridx_builtin(ctx, i, DUK_STRIDX_PROTOTYPE, t, DUK_PROPDESC_FLAGS_NONE); } t = (duk_small_uint_t) duk_bd_decode(bd, DUK__BIDX_BITS); if (t != DUK__NO_BIDX_MARKER) { /* 'constructor' property for all built-in objects (which have it) has attributes: * [[Writable]] = true, * [[Enumerable]] = false, * [[Configurable]] = true */ DUK_DDD(DUK_DDDPRINT("set external constructor: built-in %ld", (long) t)); duk_xdef_prop_stridx_builtin(ctx, i, DUK_STRIDX_CONSTRUCTOR, t, DUK_PROPDESC_FLAGS_WC); } /* normal valued properties */ num = (duk_small_uint_t) duk_bd_decode(bd, DUK__NUM_NORMAL_PROPS_BITS); DUK_DDD(DUK_DDDPRINT("built-in object %ld, %ld normal valued properties", (long) i, (long) num)); for (j = 0; j < num; j++) { duk_small_uint_t stridx; duk_small_uint_t prop_flags; stridx = (duk_small_uint_t) duk_bd_decode(bd, DUK__STRIDX_BITS); /* * Property attribute defaults are defined in E5 Section 15 (first * few pages); there is a default for all properties and a special * default for 'length' properties. Variation from the defaults is * signaled using a single flag bit in the bitstream. */ if (duk_bd_decode_flag(bd)) { prop_flags = (duk_small_uint_t) duk_bd_decode(bd, DUK__PROP_FLAGS_BITS); } else { if (stridx == DUK_STRIDX_LENGTH) { prop_flags = DUK_PROPDESC_FLAGS_NONE; } else { prop_flags = DUK_PROPDESC_FLAGS_WC; } } t = (duk_small_uint_t) duk_bd_decode(bd, DUK__PROP_TYPE_BITS); DUK_DDD(DUK_DDDPRINT("built-in %ld, normal-valued property %ld, stridx %ld, flags 0x%02lx, type %ld", (long) i, (long) j, (long) stridx, (unsigned long) prop_flags, (long) t)); switch (t) { case DUK__PROP_TYPE_DOUBLE: { duk_double_union du; duk_small_uint_t k; for (k = 0; k < 8; k++) { /* Encoding endianness must match target memory layout, * build scripts and genbuiltins.py must ensure this. */ du.uc[k] = (duk_uint8_t) duk_bd_decode(bd, 8); } duk_push_number(ctx, du.d); /* push operation normalizes NaNs */ break; } case DUK__PROP_TYPE_STRING: { duk_small_uint_t n; duk_small_uint_t k; duk_uint8_t *p; n = (duk_small_uint_t) duk_bd_decode(bd, DUK__STRING_LENGTH_BITS); p = (duk_uint8_t *) duk_push_fixed_buffer(ctx, n); for (k = 0; k < n; k++) { *p++ = (duk_uint8_t) duk_bd_decode(bd, DUK__STRING_CHAR_BITS); } duk_to_string(ctx, -1); break; } case DUK__PROP_TYPE_STRIDX: { duk_small_uint_t n; n = (duk_small_uint_t) duk_bd_decode(bd, DUK__STRIDX_BITS); DUK_ASSERT_DISABLE(n >= 0); /* unsigned */ DUK_ASSERT(n < DUK_HEAP_NUM_STRINGS); duk_push_hstring_stridx(ctx, n); break; } case DUK__PROP_TYPE_BUILTIN: { duk_small_uint_t bidx; bidx = (duk_small_uint_t) duk_bd_decode(bd, DUK__BIDX_BITS); DUK_ASSERT(bidx != DUK__NO_BIDX_MARKER); duk_dup(ctx, (duk_idx_t) bidx); break; } case DUK__PROP_TYPE_UNDEFINED: { duk_push_undefined(ctx); break; } case DUK__PROP_TYPE_BOOLEAN_TRUE: { duk_push_true(ctx); break; } case DUK__PROP_TYPE_BOOLEAN_FALSE: { duk_push_false(ctx); break; } case DUK__PROP_TYPE_ACCESSOR: { duk_small_uint_t natidx_getter = (duk_small_uint_t) duk_bd_decode(bd, DUK__NATIDX_BITS); duk_small_uint_t natidx_setter = (duk_small_uint_t) duk_bd_decode(bd, DUK__NATIDX_BITS); duk_c_function c_func_getter; duk_c_function c_func_setter; /* XXX: this is a bit awkward because there is no exposed helper * in the API style, only this internal helper. */ DUK_DDD(DUK_DDDPRINT("built-in accessor property: objidx=%ld, stridx=%ld, getteridx=%ld, setteridx=%ld, flags=0x%04lx", (long) i, (long) stridx, (long) natidx_getter, (long) natidx_setter, (unsigned long) prop_flags)); c_func_getter = duk_bi_native_functions[natidx_getter]; c_func_setter = duk_bi_native_functions[natidx_setter]; duk_push_c_function_noconstruct_noexotic(ctx, c_func_getter, 0); /* always 0 args */ duk_push_c_function_noconstruct_noexotic(ctx, c_func_setter, 1); /* always 1 arg */ /* XXX: magic for getter/setter? */ prop_flags |= DUK_PROPDESC_FLAG_ACCESSOR; /* accessor flag not encoded explicitly */ duk_hobject_define_accessor_internal(thr, duk_require_hobject(ctx, i), DUK_HTHREAD_GET_STRING(thr, stridx), duk_require_hobject(ctx, -2), duk_require_hobject(ctx, -1), prop_flags); duk_pop_2(ctx); /* getter and setter, now reachable through object */ goto skip_value; } default: { /* exhaustive */ DUK_UNREACHABLE(); } } DUK_ASSERT((prop_flags & DUK_PROPDESC_FLAG_ACCESSOR) == 0); duk_xdef_prop_stridx(ctx, i, stridx, prop_flags); skip_value: continue; /* avoid empty label at the end of a compound statement */ } /* native function properties */ num = (duk_small_uint_t) duk_bd_decode(bd, DUK__NUM_FUNC_PROPS_BITS); DUK_DDD(DUK_DDDPRINT("built-in object %ld, %ld function valued properties", (long) i, (long) num)); for (j = 0; j < num; j++) { duk_small_uint_t stridx; duk_small_uint_t natidx; duk_int_t c_nargs; /* must hold DUK_VARARGS */ duk_small_uint_t c_length; duk_int16_t magic; duk_c_function c_func; duk_hnativefunction *h_func; #if defined(DUK_USE_LIGHTFUNC_BUILTINS) duk_small_int_t lightfunc_eligible; #endif stridx = (duk_small_uint_t) duk_bd_decode(bd, DUK__STRIDX_BITS); natidx = (duk_small_uint_t) duk_bd_decode(bd, DUK__NATIDX_BITS); c_length = (duk_small_uint_t) duk_bd_decode(bd, DUK__LENGTH_PROP_BITS); c_nargs = (duk_int_t) duk_bd_decode_flagged(bd, DUK__NARGS_BITS, (duk_int32_t) c_length /*def_value*/); if (c_nargs == DUK__NARGS_VARARGS_MARKER) { c_nargs = DUK_VARARGS; } c_func = duk_bi_native_functions[natidx]; DUK_DDD(DUK_DDDPRINT("built-in %ld, function-valued property %ld, stridx %ld, natidx %ld, length %ld, nargs %ld", (long) i, (long) j, (long) stridx, (long) natidx, (long) c_length, (c_nargs == DUK_VARARGS ? (long) -1 : (long) c_nargs))); /* Cast converts magic to 16-bit signed value */ magic = (duk_int16_t) duk_bd_decode_flagged(bd, DUK__MAGIC_BITS, 0); #if defined(DUK_USE_LIGHTFUNC_BUILTINS) lightfunc_eligible = ((c_nargs >= DUK_LFUNC_NARGS_MIN && c_nargs <= DUK_LFUNC_NARGS_MAX) || (c_nargs == DUK_VARARGS)) && (c_length <= DUK_LFUNC_LENGTH_MAX) && (magic >= DUK_LFUNC_MAGIC_MIN && magic <= DUK_LFUNC_MAGIC_MAX); if (stridx == DUK_STRIDX_EVAL || stridx == DUK_STRIDX_YIELD || stridx == DUK_STRIDX_RESUME || stridx == DUK_STRIDX_REQUIRE) { /* These functions have trouble working as lightfuncs. * Some of them have specific asserts and some may have * additional properties (e.g. 'require.id' may be written). */ DUK_D(DUK_DPRINT("reject as lightfunc: stridx=%d, i=%d, j=%d", (int) stridx, (int) i, (int) j)); lightfunc_eligible = 0; } if (lightfunc_eligible) { duk_tval tv_lfunc; duk_small_uint_t lf_nargs = (c_nargs == DUK_VARARGS ? DUK_LFUNC_NARGS_VARARGS : c_nargs); duk_small_uint_t lf_flags = DUK_LFUNC_FLAGS_PACK(magic, c_length, lf_nargs); DUK_TVAL_SET_LIGHTFUNC(&tv_lfunc, c_func, lf_flags); duk_push_tval(ctx, &tv_lfunc); DUK_D(DUK_DPRINT("built-in function eligible as light function: i=%d, j=%d c_length=%ld, c_nargs=%ld, magic=%ld -> %!iT", (int) i, (int) j, (long) c_length, (long) c_nargs, (long) magic, duk_get_tval(ctx, -1))); goto lightfunc_skip; } DUK_D(DUK_DPRINT("built-in function NOT ELIGIBLE as light function: i=%d, j=%d c_length=%ld, c_nargs=%ld, magic=%ld", (int) i, (int) j, (long) c_length, (long) c_nargs, (long) magic)); #endif /* DUK_USE_LIGHTFUNC_BUILTINS */ /* [ (builtin objects) ] */ duk_push_c_function_noconstruct_noexotic(ctx, c_func, c_nargs); h_func = duk_require_hnativefunction(ctx, -1); DUK_UNREF(h_func); /* Currently all built-in native functions are strict. * This doesn't matter for many functions, but e.g. * String.prototype.charAt (and other string functions) * rely on being strict so that their 'this' binding is * not automatically coerced. */ DUK_HOBJECT_SET_STRICT((duk_hobject *) h_func); /* No built-in functions are constructable except the top * level ones (Number, etc). */ DUK_ASSERT(!DUK_HOBJECT_HAS_CONSTRUCTABLE((duk_hobject *) h_func)); /* XXX: any way to avoid decoding magic bit; there are quite * many function properties and relatively few with magic values. */ h_func->magic = magic; /* [ (builtin objects) func ] */ duk_push_int(ctx, c_length); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE); duk_push_hstring_stridx(ctx, stridx); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE); /* XXX: other properties of function instances; 'arguments', 'caller'. */ DUK_DD(DUK_DDPRINT("built-in object %ld, function property %ld -> %!T", (long) i, (long) j, (duk_tval *) duk_get_tval(ctx, -1))); /* [ (builtin objects) func ] */ /* * The default property attributes are correct for all * function valued properties of built-in objects now. */ #if defined(DUK_USE_LIGHTFUNC_BUILTINS) lightfunc_skip: #endif duk_xdef_prop_stridx(ctx, i, stridx, DUK_PROPDESC_FLAGS_WC); /* [ (builtin objects) ] */ } } /* * Special post-tweaks, for cases not covered by the init data format. * * - Set Date.prototype.toGMTString to Date.prototype.toUTCString. * toGMTString is required to have the same Function object as * toUTCString in E5 Section B.2.6. Note that while Smjs respects * this, V8 does not (the Function objects are distinct). * * - Make DoubleError non-extensible. * * - Add info about most important effective compile options to Duktape. * * - Possibly remove some properties (values or methods) which are not * desirable with current feature options but are not currently * conditional in init data. */ duk_get_prop_stridx(ctx, DUK_BIDX_DATE_PROTOTYPE, DUK_STRIDX_TO_UTC_STRING); duk_xdef_prop_stridx(ctx, DUK_BIDX_DATE_PROTOTYPE, DUK_STRIDX_TO_GMT_STRING, DUK_PROPDESC_FLAGS_WC); h = duk_require_hobject(ctx, DUK_BIDX_DOUBLE_ERROR); DUK_ASSERT(h != NULL); DUK_HOBJECT_CLEAR_EXTENSIBLE(h); #if !defined(DUK_USE_ES6_OBJECT_PROTO_PROPERTY) DUK_DD(DUK_DDPRINT("delete Object.prototype.__proto__ built-in which is not enabled in features")); (void) duk_hobject_delprop_raw(thr, thr->builtins[DUK_BIDX_OBJECT_PROTOTYPE], DUK_HTHREAD_STRING___PROTO__(thr), DUK_DELPROP_FLAG_THROW); #endif #if !defined(DUK_USE_ES6_OBJECT_SETPROTOTYPEOF) DUK_DD(DUK_DDPRINT("delete Object.setPrototypeOf built-in which is not enabled in features")); (void) duk_hobject_delprop_raw(thr, thr->builtins[DUK_BIDX_OBJECT_CONSTRUCTOR], DUK_HTHREAD_STRING_SET_PROTOTYPE_OF(thr), DUK_DELPROP_FLAG_THROW); #endif duk_push_string(ctx, /* Endianness indicator */ #if defined(DUK_USE_INTEGER_LE) "l" #elif defined(DUK_USE_INTEGER_BE) "b" #elif defined(DUK_USE_INTEGER_ME) /* integer mixed endian not really used now */ "m" #else "?" #endif #if defined(DUK_USE_DOUBLE_LE) "l" #elif defined(DUK_USE_DOUBLE_BE) "b" #elif defined(DUK_USE_DOUBLE_ME) "m" #else "?" #endif #if defined(DUK_USE_BYTEORDER_FORCED) "f" #endif " " /* Packed or unpacked tval */ #if defined(DUK_USE_PACKED_TVAL) "p" #else "u" #endif #if defined(DUK_USE_FASTINT) "f" #endif " " /* Low memory options */ #if defined(DUK_USE_STRTAB_CHAIN) "c" /* chain */ #elif defined(DUK_USE_STRTAB_PROBE) "p" /* probe */ #else "?" #endif #if !defined(DUK_USE_HEAPPTR16) && !defined(DUK_DATAPTR16) && !defined(DUK_FUNCPTR16) "n" #endif #if defined(DUK_USE_HEAPPTR16) "h" #endif #if defined(DUK_USE_DATAPTR16) "d" #endif #if defined(DUK_USE_FUNCPTR16) "f" #endif #if defined(DUK_USE_REFCOUNT16) "R" #endif #if defined(DUK_USE_STRHASH16) "H" #endif #if defined(DUK_USE_STRLEN16) "S" #endif #if defined(DUK_USE_BUFLEN16) "B" #endif #if defined(DUK_USE_OBJSIZES16) "O" #endif #if defined(DUK_USE_LIGHTFUNC_BUILTINS) "L" #endif " " /* Object property allocation layout */ #if defined(DUK_USE_HOBJECT_LAYOUT_1) "p1" #elif defined(DUK_USE_HOBJECT_LAYOUT_2) "p2" #elif defined(DUK_USE_HOBJECT_LAYOUT_3) "p3" #else "p?" #endif " " /* Alignment guarantee */ #if defined(DUK_USE_ALIGN_4) "a4" #elif defined(DUK_USE_ALIGN_8) "a8" #else "a1" #endif " " /* Architecture, OS, and compiler strings */ DUK_USE_ARCH_STRING " " DUK_USE_OS_STRING " " DUK_USE_COMPILER_STRING); duk_xdef_prop_stridx(ctx, DUK_BIDX_DUKTAPE, DUK_STRIDX_ENV, DUK_PROPDESC_FLAGS_WC); /* * InitJS code - Ecmascript code evaluated from a built-in source * which provides e.g. backward compatibility. User can also provide * JS code to be evaluated at startup. */ #ifdef DUK_USE_BUILTIN_INITJS /* XXX: compression */ DUK_DD(DUK_DDPRINT("running built-in initjs")); duk_eval_string(ctx, (const char *) duk_initjs_data); /* initjs data is NUL terminated */ duk_pop(ctx); #endif /* DUK_USE_BUILTIN_INITJS */ #ifdef DUK_USE_USER_INITJS /* XXX: compression (as an option) */ DUK_DD(DUK_DDPRINT("running user initjs")); duk_eval_string_noresult(ctx, (const char *) DUK_USE_USER_INITJS); #endif /* DUK_USE_USER_INITJS */ /* * Since built-ins are not often extended, compact them. */ DUK_DD(DUK_DDPRINT("compact built-ins")); for (i = 0; i < DUK_NUM_BUILTINS; i++) { duk_hobject_compact_props(thr, thr->builtins[i]); } DUK_D(DUK_DPRINT("INITBUILTINS END")); #ifdef DUK_USE_DDPRINT for (i = 0; i < DUK_NUM_BUILTINS; i++) { DUK_DD(DUK_DDPRINT("built-in object %ld after initialization and compacting: %!@iO", (long) i, (duk_heaphdr *) thr->builtins[i])); } #endif /* * Pop built-ins from stack: they are now INCREF'd and * reachable from the builtins[] array. */ duk_pop_n(ctx, DUK_NUM_BUILTINS); DUK_ASSERT_TOP(ctx, 0); }
void duk_hthread_create_builtin_objects(duk_hthread *thr) { duk_context *ctx = (duk_context *) thr; duk_bitdecoder_ctx bd_ctx; duk_bitdecoder_ctx *bd = &bd_ctx; /* convenience */ duk_hobject *h; int i, j; DUK_DPRINT("INITBUILTINS BEGIN"); DUK_MEMZERO(&bd_ctx, sizeof(bd_ctx)); bd->data = (const duk_uint8_t *) duk_builtins_data; bd->length = (duk_size_t) DUK_BUILTINS_DATA_LENGTH; /* * First create all built-in bare objects on the empty valstack. * During init, their indices will correspond to built-in indices. * * Built-ins will be reachable from both valstack and thr->builtins. */ /* XXX: there is no need to resize valstack because builtin count * is much less than the default space; assert for it. */ DUK_DDPRINT("create empty built-ins"); DUK_ASSERT_TOP(ctx, 0); for (i = 0; i < DUK_NUM_BUILTINS; i++) { int class_num; int len = -1; class_num = duk_bd_decode(bd, DUK__CLASS_BITS); len = duk_bd_decode_flagged(bd, DUK__LENGTH_PROP_BITS, (duk_int32_t) -1 /*def_value*/); if (class_num == DUK_HOBJECT_CLASS_FUNCTION) { int natidx; int stridx; int c_nargs; duk_c_function c_func; duk_int16_t magic; DUK_DDDPRINT("len=%d", len); DUK_ASSERT(len >= 0); natidx = duk_bd_decode(bd, DUK__NATIDX_BITS); stridx = duk_bd_decode(bd, DUK__STRIDX_BITS); c_func = duk_bi_native_functions[natidx]; c_nargs = duk_bd_decode_flagged(bd, DUK__NARGS_BITS, len /*def_value*/); if (c_nargs == DUK__NARGS_VARARGS_MARKER) { c_nargs = DUK_VARARGS; } /* FIXME: set magic directly here? (it could share the c_nargs arg) */ duk_push_c_function_nospecial(ctx, c_func, c_nargs); h = duk_require_hobject(ctx, -1); DUK_ASSERT(h != NULL); /* Currently all built-in native functions are strict. * duk_push_c_function() now sets strict flag, so * assert for it. */ DUK_ASSERT(DUK_HOBJECT_HAS_STRICT(h)); /* FIXME: function properties */ duk_push_hstring_stridx(ctx, stridx); duk_def_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE); /* Almost all global level Function objects are constructable * but not all: Function.prototype is a non-constructable, * callable Function. */ if (duk_bd_decode_flag(bd)) { DUK_ASSERT(DUK_HOBJECT_HAS_CONSTRUCTABLE(h)); } else { DUK_HOBJECT_CLEAR_CONSTRUCTABLE(h); } /* Cast converts magic to 16-bit signed value */ magic = (duk_int16_t) duk_bd_decode_flagged(bd, DUK__MAGIC_BITS, 0 /*def_value*/); ((duk_hnativefunction *) h)->magic = magic; } else { /* FIXME: ARRAY_PART for Array prototype? */ duk_push_object_helper(ctx, DUK_HOBJECT_FLAG_EXTENSIBLE, -1); /* no prototype or class yet */ h = duk_require_hobject(ctx, -1); DUK_ASSERT(h != NULL); } DUK_HOBJECT_SET_CLASS_NUMBER(h, class_num); thr->builtins[i] = h; DUK_HOBJECT_INCREF(thr, &h->hdr); if (len >= 0) { /* * For top-level objects, 'length' property has the following * default attributes: non-writable, non-enumerable, non-configurable * (E5 Section 15). * * However, 'length' property for Array.prototype has attributes * expected of an Array instance which are different: writable, * non-enumerable, non-configurable (E5 Section 15.4.5.2). * * This is currently determined implicitly based on class; there are * no attribute flags in the init data. */ duk_push_int(ctx, len); duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, (class_num == DUK_HOBJECT_CLASS_ARRAY ? /* only Array.prototype matches */ DUK_PROPDESC_FLAGS_W : DUK_PROPDESC_FLAGS_NONE)); } /* enable special behaviors last */ if (class_num == DUK_HOBJECT_CLASS_ARRAY) { DUK_HOBJECT_SET_SPECIAL_ARRAY(h); } if (class_num == DUK_HOBJECT_CLASS_STRING) { DUK_HOBJECT_SET_SPECIAL_STRINGOBJ(h); } /* some assertions */ DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(h)); /* DUK_HOBJECT_FLAG_CONSTRUCTABLE varies */ DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(h)); DUK_ASSERT(!DUK_HOBJECT_HAS_COMPILEDFUNCTION(h)); /* DUK_HOBJECT_FLAG_NATIVEFUNCTION varies */ DUK_ASSERT(!DUK_HOBJECT_HAS_THREAD(h)); DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(h)); /* currently, even for Array.prototype */ /* DUK_HOBJECT_FLAG_STRICT varies */ DUK_ASSERT(!DUK_HOBJECT_HAS_NATIVEFUNCTION(h) || /* all native functions have NEWENV */ DUK_HOBJECT_HAS_NEWENV(h)); DUK_ASSERT(!DUK_HOBJECT_HAS_NAMEBINDING(h)); DUK_ASSERT(!DUK_HOBJECT_HAS_CREATEARGS(h)); DUK_ASSERT(!DUK_HOBJECT_HAS_ENVRECCLOSED(h)); /* DUK_HOBJECT_FLAG_SPECIAL_ARRAY varies */ /* DUK_HOBJECT_FLAG_SPECIAL_STRINGOBJ varies */ DUK_ASSERT(!DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(h)); DUK_DDDPRINT("created built-in %d, class=%d, length=%d", i, class_num, len); } /* * Then decode the builtins init data (see genbuiltins.py) to * init objects */ DUK_DDPRINT("initialize built-in object properties"); for (i = 0; i < DUK_NUM_BUILTINS; i++) { unsigned char t; int num; DUK_DDDPRINT("initializing built-in object at index %d", i); h = thr->builtins[i]; t = duk_bd_decode(bd, DUK__BIDX_BITS); if (t != DUK__NO_BIDX_MARKER) { DUK_DDDPRINT("set internal prototype: built-in %d", (int) t); DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h, thr->builtins[t]); } t = duk_bd_decode(bd, DUK__BIDX_BITS); if (t != DUK__NO_BIDX_MARKER) { /* 'prototype' property for all built-in objects (which have it) has attributes: * [[Writable]] = false, * [[Enumerable]] = false, * [[Configurable]] = false */ DUK_DDDPRINT("set external prototype: built-in %d", (int) t); duk_def_prop_stridx_builtin(ctx, i, DUK_STRIDX_PROTOTYPE, t, DUK_PROPDESC_FLAGS_NONE); } t = duk_bd_decode(bd, DUK__BIDX_BITS); if (t != DUK__NO_BIDX_MARKER) { /* 'constructor' property for all built-in objects (which have it) has attributes: * [[Writable]] = true, * [[Enumerable]] = false, * [[Configurable]] = true */ DUK_DDDPRINT("set external constructor: built-in %d", (int) t); duk_def_prop_stridx_builtin(ctx, i, DUK_STRIDX_CONSTRUCTOR, t, DUK_PROPDESC_FLAGS_WC); } /* normal valued properties */ num = duk_bd_decode(bd, DUK__NUM_NORMAL_PROPS_BITS); DUK_DDDPRINT("built-in object %d, %d normal valued properties", i, num); for (j = 0; j < num; j++) { int stridx; int prop_flags; stridx = duk_bd_decode(bd, DUK__STRIDX_BITS); /* * Property attribute defaults are defined in E5 Section 15 (first * few pages); there is a default for all properties and a special * default for 'length' properties. Variation from the defaults is * signaled using a single flag bit in the bitstream. */ if (duk_bd_decode_flag(bd)) { prop_flags = duk_bd_decode(bd, DUK__PROP_FLAGS_BITS); } else { if (stridx == DUK_STRIDX_LENGTH) { prop_flags = DUK_PROPDESC_FLAGS_NONE; } else { prop_flags = DUK_PROPDESC_FLAGS_WC; } } t = duk_bd_decode(bd, DUK__PROP_TYPE_BITS); DUK_DDDPRINT("built-in %d, normal-valued property %d, stridx %d, flags 0x%02x, type %d", i, j, stridx, prop_flags, (int) t); switch (t) { case DUK__PROP_TYPE_DOUBLE: { duk_double_union du; int k; for (k = 0; k < 8; k++) { /* Encoding endianness must match target memory layout, * build scripts and genbuiltins.py must ensure this. */ du.uc[k] = (duk_uint8_t) duk_bd_decode(bd, 8); } duk_push_number(ctx, du.d); /* push operation normalizes NaNs */ break; } case DUK__PROP_TYPE_STRING: { int n; int k; char *p; n = duk_bd_decode(bd, DUK__STRING_LENGTH_BITS); p = (char *) duk_push_fixed_buffer(ctx, n); for (k = 0; k < n; k++) { *p++ = duk_bd_decode(bd, DUK__STRING_CHAR_BITS); } duk_to_string(ctx, -1); break; } case DUK__PROP_TYPE_STRIDX: { int n; n = duk_bd_decode(bd, DUK__STRIDX_BITS); DUK_ASSERT(n >= 0 && n < DUK_HEAP_NUM_STRINGS); duk_push_hstring_stridx(ctx, n); break; } case DUK__PROP_TYPE_BUILTIN: { int bidx; bidx = duk_bd_decode(bd, DUK__BIDX_BITS); DUK_ASSERT(bidx != DUK__NO_BIDX_MARKER); duk_dup(ctx, bidx); break; } case DUK__PROP_TYPE_UNDEFINED: { duk_push_undefined(ctx); break; } case DUK__PROP_TYPE_BOOLEAN_TRUE: { duk_push_true(ctx); break; } case DUK__PROP_TYPE_BOOLEAN_FALSE: { duk_push_false(ctx); break; } case DUK__PROP_TYPE_ACCESSOR: { int natidx_getter = duk_bd_decode(bd, DUK__NATIDX_BITS); int natidx_setter = duk_bd_decode(bd, DUK__NATIDX_BITS); duk_c_function c_func_getter; duk_c_function c_func_setter; /* XXX: this is a bit awkward because there is no exposed helper * in the API style, only this internal helper. */ DUK_DDDPRINT("built-in accessor property: objidx=%d, stridx=%d, getteridx=%d, setteridx=%d, flags=0x%04x", i, stridx, natidx_getter, natidx_setter, prop_flags); c_func_getter = duk_bi_native_functions[natidx_getter]; c_func_setter = duk_bi_native_functions[natidx_setter]; duk_push_c_function_noconstruct_nospecial(ctx, c_func_getter, 0); /* always 0 args */ duk_push_c_function_noconstruct_nospecial(ctx, c_func_setter, 1); /* always 1 arg */ /* FIXME: magic for getter/setter? */ prop_flags |= DUK_PROPDESC_FLAG_ACCESSOR; /* accessor flag not encoded explicitly */ duk_hobject_define_accessor_internal(thr, duk_require_hobject(ctx, i), DUK_HTHREAD_GET_STRING(thr, stridx), duk_require_hobject(ctx, -2), duk_require_hobject(ctx, -1), prop_flags); duk_pop_2(ctx); /* getter and setter, now reachable through object */ goto skip_value; } default: { /* exhaustive */ DUK_UNREACHABLE(); } } DUK_ASSERT((prop_flags & DUK_PROPDESC_FLAG_ACCESSOR) == 0); duk_def_prop_stridx(ctx, i, stridx, prop_flags); skip_value: continue; /* avoid empty label at the end of a compound statement */ } /* native function properties */ num = duk_bd_decode(bd, DUK__NUM_FUNC_PROPS_BITS); DUK_DDDPRINT("built-in object %d, %d function valued properties", i, num); for (j = 0; j < num; j++) { int stridx; int natidx; int c_nargs; int c_length; duk_int16_t magic; duk_c_function c_func; duk_hnativefunction *h_func; stridx = duk_bd_decode(bd, DUK__STRIDX_BITS); natidx = duk_bd_decode(bd, DUK__NATIDX_BITS); c_length = duk_bd_decode(bd, DUK__LENGTH_PROP_BITS); c_nargs = duk_bd_decode_flagged(bd, DUK__NARGS_BITS, (duk_int32_t) c_length /*def_value*/); if (c_nargs == DUK__NARGS_VARARGS_MARKER) { c_nargs = DUK_VARARGS; } c_func = duk_bi_native_functions[natidx]; DUK_DDDPRINT("built-in %d, function-valued property %d, stridx %d, natidx %d, length %d, nargs %d", i, j, stridx, natidx, c_length, (c_nargs == DUK_VARARGS ? -1 : c_nargs)); /* [ (builtin objects) ] */ duk_push_c_function_noconstruct_nospecial(ctx, c_func, c_nargs); h_func = duk_require_hnativefunction(ctx, -1); DUK_UNREF(h_func); /* Currently all built-in native functions are strict. * This doesn't matter for many functions, but e.g. * String.prototype.charAt (and other string functions) * rely on being strict so that their 'this' binding is * not automatically coerced. */ DUK_HOBJECT_SET_STRICT((duk_hobject *) h_func); /* No built-in functions are constructable except the top * level ones (Number, etc). */ DUK_ASSERT(!DUK_HOBJECT_HAS_CONSTRUCTABLE((duk_hobject *) h_func)); /* FIXME: any way to avoid decoding magic bit; there are quite * many function properties and relatively few with magic values. */ /* Cast converts magic to 16-bit signed value */ magic = (duk_int16_t) duk_bd_decode_flagged(bd, DUK__MAGIC_BITS, 0); h_func->magic = magic; /* [ (builtin objects) func ] */ duk_push_int(ctx, c_length); duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE); duk_push_hstring_stridx(ctx, stridx); duk_def_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE); /* FIXME: other properties of function instances; 'arguments', 'caller'. */ DUK_DDPRINT("built-in object %d, function property %d -> %!T", i, j, duk_get_tval(ctx, -1)); /* [ (builtin objects) func ] */ /* * The default property attributes are correct for all * function valued properties of built-in objects now. */ duk_def_prop_stridx(ctx, i, stridx, DUK_PROPDESC_FLAGS_WC); /* [ (builtin objects) ] */ } } /* * Special post-tweaks, for cases not covered by the init data format. * * - Set Date.prototype.toGMTString to Date.prototype.toUTCString. * toGMTString is required to have the same Function object as * toUTCString in E5 Section B.2.6. Note that while Smjs respects * this, V8 does not (the Function objects are distinct). * * - Make DoubleError non-extensible. * * - Add info about most important effective compile options to Duktape. */ duk_get_prop_stridx(ctx, DUK_BIDX_DATE_PROTOTYPE, DUK_STRIDX_TO_UTC_STRING); duk_def_prop_stridx(ctx, DUK_BIDX_DATE_PROTOTYPE, DUK_STRIDX_TO_GMT_STRING, DUK_PROPDESC_FLAGS_WC); h = duk_require_hobject(ctx, DUK_BIDX_DOUBLE_ERROR); DUK_ASSERT(h != NULL); DUK_HOBJECT_CLEAR_EXTENSIBLE(h); duk_push_string(ctx, #if defined(DUK_USE_INTEGER_LE) "l" #elif defined(DUK_USE_INTEGER_BE) "b" #elif defined(DUK_USE_INTEGER_ME) /* integer mixed endian not really used now */ "m" #else "?" #endif #if defined(DUK_USE_DOUBLE_LE) "l" #elif defined(DUK_USE_DOUBLE_BE) "b" #elif defined(DUK_USE_DOUBLE_ME) "m" #else "?" #endif #if defined(DUK_USE_BYTEORDER_FORCED) "f" #endif " " #if defined(DUK_USE_PACKED_TVAL) "p" #else "u" #endif " " #if defined(DUK_USE_HOBJECT_LAYOUT_1) "p1" #elif defined(DUK_USE_HOBJECT_LAYOUT_2) "p2" #elif defined(DUK_USE_HOBJECT_LAYOUT_3) "p3" #else "p?" #endif " " #if defined(DUK_USE_ALIGN_4) "a4" #elif defined(DUK_USE_ALIGN_8) "a8" #else "a1" #endif " " DUK_USE_ARCH_STRING); duk_def_prop_stridx(ctx, DUK_BIDX_DUKTAPE, DUK_STRIDX_ENV, DUK_PROPDESC_FLAGS_WC); /* * InitJS code - Ecmascript code evaluated from a built-in source * which provides e.g. backward compatibility. User can also provide * JS code to be evaluated at startup. */ #ifdef DUK_USE_INITJS /* FIXME: compression */ duk_eval_string(ctx, (const char *) duk_initjs_data); /* initjs data is NUL terminated */ duk_pop(ctx); #endif /* DUK_USE_INITJS */ #ifdef DUK_USE_USER_INITJS /* FIXME: compression, at least as an option? */ /* FIXME: unused now */ duk_eval_string(ctx, (const char *) DUK_USE_USER_INITJS); duk_pop(ctx); #endif /* DUK_USE_USER_INITJS */ /* * Since built-ins are not often extended, compact them. */ DUK_DDPRINT("compact built-ins"); for (i = 0; i < DUK_NUM_BUILTINS; i++) { duk_hobject_compact_props(thr, thr->builtins[i]); } DUK_DPRINT("INITBUILTINS END"); #ifdef DUK_USE_DDEBUG for (i = 0; i < DUK_NUM_BUILTINS; i++) { DUK_DDPRINT("built-in object %d after initialization and compacting: %!@iO", i, thr->builtins[i]); } #endif #ifdef DUK_USE_DDDEBUG for (i = 0; i < DUK_NUM_BUILTINS; i++) { DUK_DDDPRINT("built-in object %d after initialization and compacting", i); DUK_DEBUG_DUMP_HOBJECT(thr->builtins[i]); } #endif /* * Pop built-ins from stack: they are now INCREF'd and * reachable from the builtins[] array. */ duk_pop_n(ctx, DUK_NUM_BUILTINS); DUK_ASSERT_TOP(ctx, 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(); } }
/* Get local time offset (in seconds) for a certain (UTC) instant 'd'. */ static int get_local_tzoffset_gmtime(double d) { time_t t, t1, t2; int parts[NUM_PARTS]; double dparts[NUM_PARTS]; struct tm tms[2]; #ifdef DUK_USE_DATE_TZO_GMTIME struct tm *tm_ptr; #endif /* For NaN/inf, the return value doesn't matter. */ if (!DUK_ISFINITE(d)) { return 0; } /* * This is a bit tricky to implement portably. The result depends * on the timestamp (specifically, DST depends on the timestamp). * If e.g. UNIX APIs are used, they'll have portability issues with * very small and very large years. * * Current approach: * * - Clamp year to stay within portable UNIX limits. Avoid 2038 as * some conversions start to fail. Avoid 1970, as some conversions * in January 1970 start to fail (verified in practice). * * - Create a UTC time breakdown from 't', and then pretend it is a * local time breakdown and build a UTC time from it. The timestamp * will effectively shift backwards by time the time offset (e.g. -2h * or -3h for EET/EEST). Convert with mktime() twice to get the DST * flag for the final conversion. * * FIXME: this is probably not entirely correct nor clear, but is * good enough for now. */ timeval_to_parts(d, parts, dparts, 0 /*flags*/); /* * FIXME: must choose 'equivalent year', E5 Section 15.9.1.8, instead * of just clamping. */ if (parts[IDX_YEAR] < 1971) { dparts[IDX_YEAR] = 1971.0; } else if (parts[IDX_YEAR] > 2037) { dparts[IDX_YEAR] = 2037.0; } d = get_timeval_from_dparts(dparts, 0 /*flags*/); DUK_ASSERT(d >= 0 && d < 2147483648.0 * 1000.0); /* unsigned 31-bit range */ t = (size_t) (d / 1000.0); DUK_DDDPRINT("timeval: %lf -> time_t %d", d, (int) t); t1 = t; DUK_MEMSET((void *) tms, 0, sizeof(struct tm) * 2); #if defined(DUK_USE_DATE_TZO_GMTIME_R) (void) gmtime_r(&t, &tms[0]); #elif defined(DUK_USE_DATE_TZO_GMTIME) tm_ptr = gmtime(&t); DUK_MEMCPY((void *) &tms[0], tm_ptr, sizeof(struct tm)); #else #error internal error #endif DUK_MEMCPY((void *) &tms[1], &tms[0], sizeof(struct tm)); DUK_DDDPRINT("before mktime: tm={sec:%d,min:%d,hour:%d,mday:%d,mon:%d,year:%d," "wday:%d,yday:%d,isdst:%d}", (int) tms[0].tm_sec, (int) tms[0].tm_min, (int) tms[0].tm_hour, (int) tms[0].tm_mday, (int) tms[0].tm_mon, (int) tms[0].tm_year, (int) tms[0].tm_wday, (int) tms[0].tm_yday, (int) tms[0].tm_isdst); (void) mktime(&tms[0]); tms[1].tm_isdst = tms[0].tm_isdst; t2 = mktime(&tms[1]); DUK_ASSERT(t2 >= 0); if (t2 < 0) { goto error; } DUK_DDDPRINT("after mktime: tm={sec:%d,min:%d,hour:%d,mday:%d,mon:%d,year:%d," "wday:%d,yday:%d,isdst:%d}", (int) tms[1].tm_sec, (int) tms[1].tm_min, (int) tms[1].tm_hour, (int) tms[1].tm_mday, (int) tms[1].tm_mon, (int) tms[1].tm_year, (int) tms[1].tm_wday, (int) tms[1].tm_yday, (int) tms[1].tm_isdst); DUK_DDDPRINT("t2=%d", (int) t2); /* Positive if local time ahead of UTC. */ /* difftime() returns a double, so coercion to int generates quite * a lot of code. Direct subtraction is not portable, however. * * FIXME: allow direct subtraction on known platforms. */ #if 0 return t1 - t2; #endif return (int) difftime(t1, t2); error: /* FIXME: return something more useful, so that caller can throw? */ DUK_DPRINT("mktime() failed, d=%lf", d); return 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; }
DUK_INTERNAL duk_bool_t duk_heap_mark_and_sweep(duk_heap *heap, duk_small_uint_t flags) { duk_hthread *thr; duk_size_t count_keep_obj; duk_size_t count_keep_str; #if defined(DUK_USE_VOLUNTARY_GC) duk_size_t tmp; #endif /* XXX: thread selection for mark-and-sweep is currently a hack. * If we don't have a thread, the entire mark-and-sweep is now * skipped (although we could just skip finalizations). */ /* If thr != NULL, the thr may still be in the middle of * initialization. * XXX: Improve the thread viability test. */ thr = duk__get_temp_hthread(heap); if (thr == NULL) { DUK_D(DUK_DPRINT("gc skipped because we don't have a temp thread")); /* reset voluntary gc trigger count */ #if defined(DUK_USE_VOLUNTARY_GC) heap->mark_and_sweep_trigger_counter = DUK_HEAP_MARK_AND_SWEEP_TRIGGER_SKIP; #endif return 0; /* OK */ } /* If debugger is paused, garbage collection is disabled by default. */ /* XXX: will need a force flag if garbage collection is triggered * explicitly during paused state. */ #if defined(DUK_USE_DEBUGGER_SUPPORT) if (DUK_HEAP_IS_PAUSED(heap)) { /* Checking this here rather that in memory alloc primitives * reduces checking code there but means a failed allocation * will go through a few retries before giving up. That's * fine because this only happens during debugging. */ DUK_D(DUK_DPRINT("gc skipped because debugger is paused")); return 0; } #endif DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) starting, requested flags: 0x%08lx, effective flags: 0x%08lx", (unsigned long) flags, (unsigned long) (flags | heap->mark_and_sweep_base_flags))); flags |= heap->mark_and_sweep_base_flags; /* * Assertions before */ #if defined(DUK_USE_ASSERTIONS) DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)); DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)); DUK_ASSERT(heap->mark_and_sweep_recursion_depth == 0); duk__assert_heaphdr_flags(heap); #if defined(DUK_USE_REFERENCE_COUNTING) /* Note: DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap) may be true; a refcount * finalizer may trigger a mark-and-sweep. */ duk__assert_valid_refcounts(heap); #endif /* DUK_USE_REFERENCE_COUNTING */ #endif /* DUK_USE_ASSERTIONS */ /* * Begin */ DUK_HEAP_SET_MARKANDSWEEP_RUNNING(heap); /* * Mark roots, hoping that recursion limit is not normally hit. * If recursion limit is hit, run additional reachability rounds * starting from "temproots" until marking is complete. * * Marking happens in two phases: first we mark actual reachability * roots (and run "temproots" to complete the process). Then we * check which objects are unreachable and are finalizable; such * objects are marked as FINALIZABLE and marked as reachability * (and "temproots" is run again to complete the process). * * The heap finalize_list must also be marked as a reachability root. * There may be objects on the list from a previous round if the * previous run had finalizer skip flag. */ duk__mark_roots_heap(heap); /* main reachability roots */ #if defined(DUK_USE_REFERENCE_COUNTING) duk__mark_refzero_list(heap); /* refzero_list treated as reachability roots */ #endif duk__mark_temproots_by_heap_scan(heap); /* temproots */ #if defined(DUK_USE_FINALIZER_SUPPORT) duk__mark_finalizable(heap); /* mark finalizable as reachability roots */ duk__mark_finalize_list(heap); /* mark finalizer work list as reachability roots */ #endif duk__mark_temproots_by_heap_scan(heap); /* temproots */ /* * Sweep garbage and remove marking flags, and move objects with * finalizers to the finalizer work list. * * Objects to be swept need to get their refcounts finalized before * they are swept. In other words, their target object refcounts * need to be decreased. This has to be done before freeing any * objects to avoid decref'ing dangling pointers (which may happen * even without bugs, e.g. with reference loops) * * Because strings don't point to other heap objects, similar * finalization is not necessary for strings. */ /* XXX: more emergency behavior, e.g. find smaller hash sizes etc */ #if defined(DUK_USE_REFERENCE_COUNTING) duk__finalize_refcounts(heap); #endif duk__sweep_heap(heap, flags, &count_keep_obj); duk__sweep_stringtable(heap, &count_keep_str); #if defined(DUK_USE_REFERENCE_COUNTING) duk__clear_refzero_list_flags(heap); #endif #if defined(DUK_USE_FINALIZER_SUPPORT) duk__clear_finalize_list_flags(heap); #endif /* * Object compaction (emergency only). * * Object compaction is a separate step after sweeping, as there is * more free memory for it to work with. Also, currently compaction * may insert new objects into the heap allocated list and the string * table which we don't want to do during a sweep (the reachability * flags of such objects would be incorrect). The objects inserted * are currently: * * - a temporary duk_hbuffer for a new properties allocation * - if array part is abandoned, string keys are interned * * The object insertions go to the front of the list, so they do not * cause an infinite loop (they are not compacted). */ if ((flags & DUK_MS_FLAG_EMERGENCY) && !(flags & DUK_MS_FLAG_NO_OBJECT_COMPACTION)) { duk__compact_objects(heap); } /* * String table resize check. * * This is mainly useful in emergency GC: if the string table load * factor is really low for some reason, we can shrink the string * table to a smaller size and free some memory in the process. * Only execute in emergency GC. String table has internal flags * to protect against recursive resizing if this mark-and-sweep pass * was triggered by a string table resize. */ if (flags & DUK_MS_FLAG_EMERGENCY) { DUK_D(DUK_DPRINT("stringtable resize check in emergency gc")); duk_heap_strtable_force_resize(heap); } /* * Finalize objects in the finalization work list. Finalized * objects are queued back to heap_allocated with FINALIZED set. * * Since finalizers may cause arbitrary side effects, they are * prevented during string table and object property allocation * resizing using the DUK_MS_FLAG_NO_FINALIZERS flag in * heap->mark_and_sweep_base_flags. In this case the objects * remain in the finalization work list after mark-and-sweep * exits and they may be finalized on the next pass. * * Finalization currently happens inside "MARKANDSWEEP_RUNNING" * protection (no mark-and-sweep may be triggered by the * finalizers). As a side effect: * * 1) an out-of-memory error inside a finalizer will not * cause a mark-and-sweep and may cause the finalizer * to fail unnecessarily * * 2) any temporary objects whose refcount decreases to zero * during finalization will not be put into refzero_list; * they can only be collected by another mark-and-sweep * * This is not optimal, but since the sweep for this phase has * already happened, this is probably good enough for now. */ #if defined(DUK_USE_FINALIZER_SUPPORT) #if defined(DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE) /* Cannot simulate individual finalizers because finalize_list only * contains objects with actual finalizers. But simulate side effects * from finalization by doing a bogus function call and resizing the * stacks. */ if (flags & DUK_MS_FLAG_NO_FINALIZERS) { DUK_D(DUK_DPRINT("skip mark-and-sweep torture finalizer, DUK_MS_FLAG_NO_FINALIZERS is set")); } else if (!(thr->valstack != NULL && thr->callstack != NULL && thr->catchstack != NULL)) { DUK_D(DUK_DPRINT("skip mark-and-sweep torture finalizer, thread not yet viable")); } else { DUK_D(DUK_DPRINT("run mark-and-sweep torture finalizer")); duk__markandsweep_torture_finalizer(thr); } #endif /* DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE */ if (flags & DUK_MS_FLAG_NO_FINALIZERS) { DUK_D(DUK_DPRINT("finalizer run skipped because DUK_MS_FLAG_NO_FINALIZERS is set")); } else { duk__run_object_finalizers(heap, flags); } #endif /* DUK_USE_FINALIZER_SUPPORT */ /* * Finish */ DUK_HEAP_CLEAR_MARKANDSWEEP_RUNNING(heap); /* * Assertions after */ #if defined(DUK_USE_ASSERTIONS) DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)); DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)); DUK_ASSERT(heap->mark_and_sweep_recursion_depth == 0); duk__assert_heaphdr_flags(heap); #if defined(DUK_USE_REFERENCE_COUNTING) /* Note: DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap) may be true; a refcount * finalizer may trigger a mark-and-sweep. */ duk__assert_valid_refcounts(heap); #endif /* DUK_USE_REFERENCE_COUNTING */ #endif /* DUK_USE_ASSERTIONS */ /* * Reset trigger counter */ #if defined(DUK_USE_VOLUNTARY_GC) tmp = (count_keep_obj + count_keep_str) / 256; heap->mark_and_sweep_trigger_counter = (duk_int_t) ( (tmp * DUK_HEAP_MARK_AND_SWEEP_TRIGGER_MULT) + DUK_HEAP_MARK_AND_SWEEP_TRIGGER_ADD); DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %ld objects kept, %ld strings kept, trigger reset to %ld", (long) count_keep_obj, (long) count_keep_str, (long) heap->mark_and_sweep_trigger_counter)); #else DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %ld objects kept, %ld strings kept, no voluntary trigger", (long) count_keep_obj, (long) count_keep_str)); #endif return 0; /* OK */ }
int duk_heap_mark_and_sweep(duk_heap *heap, int flags) { duk_size_t count_keep_obj; duk_size_t count_keep_str; duk_size_t tmp; /* FIXME: thread selection for mark-and-sweep is currently a hack. * If we don't have a thread, the entire mark-and-sweep is now * skipped (although we could just skip finalizations). */ if (duk__get_temp_hthread(heap) == NULL) { DUK_D(DUK_DPRINT("temporary hack: gc skipped because we don't have a temp thread")); /* reset voluntary gc trigger count */ #ifdef DUK_USE_VOLUNTARY_GC heap->mark_and_sweep_trigger_counter = DUK_HEAP_MARK_AND_SWEEP_TRIGGER_SKIP; #endif return 0; /* OK */ } DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) starting, requested flags: 0x%08x, effective flags: 0x%08x", flags, flags | heap->mark_and_sweep_base_flags)); flags |= heap->mark_and_sweep_base_flags; /* * Assertions before */ #ifdef DUK_USE_ASSERTIONS DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)); DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)); DUK_ASSERT(heap->mark_and_sweep_recursion_depth == 0); duk__assert_heaphdr_flags(heap); #ifdef DUK_USE_REFERENCE_COUNTING /* Note: DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap) may be true; a refcount * finalizer may trigger a mark-and-sweep. */ duk__assert_valid_refcounts(heap); #endif /* DUK_USE_REFERENCE_COUNTING */ #endif /* DUK_USE_ASSERTIONS */ /* * Begin */ DUK_HEAP_SET_MARKANDSWEEP_RUNNING(heap); /* * Mark roots, hoping that recursion limit is not normally hit. * If recursion limit is hit, run additional reachability rounds * starting from "temproots" until marking is complete. * * Marking happens in two phases: first we mark actual reachability * roots (and run "temproots" to complete the process). Then we * check which objects are unreachable and are finalizable; such * objects are marked as FINALIZABLE and marked as reachability * (and "temproots" is run again to complete the process). */ duk__mark_roots_heap(heap); /* main reachability roots */ #ifdef DUK_USE_REFERENCE_COUNTING duk__mark_refzero_list(heap); /* refzero_list treated as reachability roots */ #endif duk__mark_temproots_by_heap_scan(heap); /* temproots */ duk__mark_finalizable(heap); /* mark finalizable as reachability roots */ duk__mark_temproots_by_heap_scan(heap); /* temproots */ /* * Sweep garbage and remove marking flags, and move objects with * finalizers to the finalizer work list. * * Objects to be swept need to get their refcounts finalized before * they are swept. In other words, their target object refcounts * need to be decreased. This has to be done before freeing any * objects to avoid decref'ing dangling pointers (which may happen * even without bugs, e.g. with reference loops) * * Because strings don't point to other heap objects, similar * finalization is not necessary for strings. */ /* XXX: more emergency behavior, e.g. find smaller hash sizes etc */ #ifdef DUK_USE_REFERENCE_COUNTING duk__finalize_refcounts(heap); #endif duk__sweep_heap(heap, flags, &count_keep_obj); duk__sweep_stringtable(heap, &count_keep_str); #ifdef DUK_USE_REFERENCE_COUNTING duk__clear_refzero_list_flags(heap); #endif /* * Object compaction (emergency only). * * Object compaction is a separate step after sweeping, as there is * more free memory for it to work with. Also, currently compaction * may insert new objects into the heap allocated list and the string * table which we don't want to do during a sweep (the reachability * flags of such objects would be incorrect). The objects inserted * are currently: * * - a temporary duk_hbuffer for a new properties allocation * - if array part is abandoned, string keys are interned * * The object insertions go to the front of the list, so they do not * cause an infinite loop (they are not compacted). */ if ((flags & DUK_MS_FLAG_EMERGENCY) && !(flags & DUK_MS_FLAG_NO_OBJECT_COMPACTION)) { duk__compact_objects(heap); } /* * String table resize check. * * Note: this may silently (and safely) fail if GC is caused by an * allocation call in stringtable resize_hash(). Resize_hash() * will prevent a recursive call to itself by setting the * DUK_MS_FLAG_NO_STRINGTABLE_RESIZE in heap->mark_and_sweep_base_flags. */ /* XXX: stringtable emergency compaction? */ #if defined(DUK_USE_MS_STRINGTABLE_RESIZE) if (!(flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE)) { DUK_DD(DUK_DDPRINT("resize stringtable: %p", (void *) heap)); duk_heap_force_stringtable_resize(heap); } else { DUK_D(DUK_DPRINT("stringtable resize skipped because DUK_MS_FLAG_NO_STRINGTABLE_RESIZE is set")); } #endif /* * Finalize objects in the finalization work list. Finalized * objects are queued back to heap_allocated with FINALIZED set. * * Since finalizers may cause arbitrary side effects, they are * prevented during string table and object property allocation * resizing using the DUK_MS_FLAG_NO_FINALIZERS flag in * heap->mark_and_sweep_base_flags. In this case the objects * remain in the finalization work list after mark-and-sweep * exits and they may be finalized on the next pass. * * Finalization currently happens inside "MARKANDSWEEP_RUNNING" * protection (no mark-and-sweep may be triggered by the * finalizers). As a side effect: * * 1) an out-of-memory error inside a finalizer will not * cause a mark-and-sweep and may cause the finalizer * to fail unnecessarily * * 2) any temporary objects whose refcount decreases to zero * during finalization will not be put into refzero_list; * they can only be collected by another mark-and-sweep * * This is not optimal, but since the sweep for this phase has * already happened, this is probably good enough for now. */ if (!(flags & DUK_MS_FLAG_NO_FINALIZERS)) { duk__run_object_finalizers(heap); } else { DUK_D(DUK_DPRINT("finalizer run skipped because DUK_MS_FLAG_NO_FINALIZERS is set")); } /* * Finish */ DUK_HEAP_CLEAR_MARKANDSWEEP_RUNNING(heap); /* * Assertions after */ #ifdef DUK_USE_ASSERTIONS DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)); DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)); DUK_ASSERT(heap->mark_and_sweep_recursion_depth == 0); duk__assert_heaphdr_flags(heap); #ifdef DUK_USE_REFERENCE_COUNTING /* Note: DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap) may be true; a refcount * finalizer may trigger a mark-and-sweep. */ duk__assert_valid_refcounts(heap); #endif /* DUK_USE_REFERENCE_COUNTING */ #endif /* DUK_USE_ASSERTIONS */ /* * Reset trigger counter */ #ifdef DUK_USE_VOLUNTARY_GC tmp = (count_keep_obj + count_keep_str) / 256; heap->mark_and_sweep_trigger_counter = (tmp * DUK_HEAP_MARK_AND_SWEEP_TRIGGER_MULT) + DUK_HEAP_MARK_AND_SWEEP_TRIGGER_ADD; DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %d objects kept, %d strings kept, trigger reset to %d", (int) count_keep_obj, (int) count_keep_str, (int) heap->mark_and_sweep_trigger_counter)); #else DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %d objects kept, %d strings kept, no voluntary trigger", (int) count_keep_obj, (int) count_keep_str)); #endif return 0; /* OK */ }
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; }
void duk_debug_dump_heap(duk_heap *heap) { char buf[64+1]; DUK_D(DUK_DPRINT("=== heap %p ===", (void *) heap)); DUK_D(DUK_DPRINT(" flags: 0x%08lx", (unsigned long) heap->flags)); /* Note: there is no standard formatter for function pointers */ #ifdef DUK_USE_GCC_PRAGMAS #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-pedantic" #endif duk_debug_format_funcptr(buf, sizeof(buf), (unsigned char *) &heap->alloc_func, sizeof(heap->alloc_func)); DUK_D(DUK_DPRINT(" alloc_func: %s", (const char *) buf)); duk_debug_format_funcptr(buf, sizeof(buf), (unsigned char *) &heap->realloc_func, sizeof(heap->realloc_func)); DUK_D(DUK_DPRINT(" realloc_func: %s", (const char *) buf)); duk_debug_format_funcptr(buf, sizeof(buf), (unsigned char *) &heap->free_func, sizeof(heap->free_func)); DUK_D(DUK_DPRINT(" free_func: %s", (const char *) buf)); duk_debug_format_funcptr(buf, sizeof(buf), (unsigned char *) &heap->fatal_func, sizeof(heap->fatal_func)); DUK_D(DUK_DPRINT(" fatal_func: %s", (const char *) buf)); #ifdef DUK_USE_GCC_PRAGMAS #pragma GCC diagnostic pop #endif DUK_D(DUK_DPRINT(" alloc_udata: %p", (void *) heap->alloc_udata)); #ifdef DUK_USE_MARK_AND_SWEEP #ifdef DUK_USE_VOLUNTARY_GC DUK_D(DUK_DPRINT(" mark-and-sweep trig counter: %ld", (long) heap->mark_and_sweep_trigger_counter)); #endif DUK_D(DUK_DPRINT(" mark-and-sweep rec depth: %ld", (long) heap->mark_and_sweep_recursion_depth)); DUK_D(DUK_DPRINT(" mark-and-sweep base flags: 0x%08lx", (unsigned long) heap->mark_and_sweep_base_flags)); #endif DUK_D(DUK_DPRINT(" lj.jmpbuf_ptr: %p", (void *) heap->lj.jmpbuf_ptr)); DUK_D(DUK_DPRINT(" lj.type: %ld", (long) heap->lj.type)); DUK_D(DUK_DPRINT(" lj.value1: %!T", (duk_tval *) &heap->lj.value1)); DUK_D(DUK_DPRINT(" lj.value2: %!T", (duk_tval *) &heap->lj.value2)); DUK_D(DUK_DPRINT(" lj.iserror: %ld", (long) heap->lj.iserror)); DUK_D(DUK_DPRINT(" handling_error: %ld", (long) heap->handling_error)); DUK_D(DUK_DPRINT(" heap_thread: %!@O", (duk_heaphdr *) heap->heap_thread)); DUK_D(DUK_DPRINT(" curr_thread: %!@O", (duk_heaphdr *) heap->curr_thread)); DUK_D(DUK_DPRINT(" heap_object: %!@O", (duk_heaphdr *) heap->heap_object)); DUK_D(DUK_DPRINT(" call_recursion_depth: %ld", (long) heap->call_recursion_depth)); DUK_D(DUK_DPRINT(" call_recursion_limit: %ld", (long) heap->call_recursion_limit)); DUK_D(DUK_DPRINT(" hash_seed: 0x%08lx", (unsigned long) heap->hash_seed)); DUK_D(DUK_DPRINT(" rnd_state: 0x%08lx", (unsigned long) heap->rnd_state)); duk__dump_strcache(heap); duk__dump_heaphdr_list(heap, heap->heap_allocated, "heap allocated"); #ifdef DUK_USE_REFERENCE_COUNTING duk__dump_heaphdr_list(heap, heap->refzero_list, "refcounting refzero list"); #endif #ifdef DUK_USE_MARK_AND_SWEEP duk__dump_heaphdr_list(heap, heap->finalize_list, "mark-and-sweep finalize list"); #endif duk__dump_stringtable(heap); /* heap->strs: not worth dumping */ }
static void duk__dump_type_sizes(void) { DUK_D(DUK_DPRINT("sizeofs()")); /* basic platform types */ DUK__DUMPSZ(char); DUK__DUMPSZ(short); DUK__DUMPSZ(int); DUK__DUMPSZ(long); DUK__DUMPSZ(double); DUK__DUMPSZ(void *); DUK__DUMPSZ(size_t); /* basic types from duk_features.h */ DUK__DUMPSZ(uint8_t); DUK__DUMPSZ(int8_t); DUK__DUMPSZ(uint16_t); DUK__DUMPSZ(int16_t); DUK__DUMPSZ(uint32_t); DUK__DUMPSZ(int32_t); DUK__DUMPSZ(uint64_t); DUK__DUMPSZ(int64_t); DUK__DUMPSZ(uint_least8_t); DUK__DUMPSZ(int_least8_t); DUK__DUMPSZ(uint_least16_t); DUK__DUMPSZ(int_least16_t); DUK__DUMPSZ(uint_least32_t); DUK__DUMPSZ(int_least32_t); DUK__DUMPSZ(uint_least64_t); DUK__DUMPSZ(int_least64_t); DUK__DUMPSZ(uint_fast8_t); DUK__DUMPSZ(int_fast8_t); DUK__DUMPSZ(uint_fast16_t); DUK__DUMPSZ(int_fast16_t); DUK__DUMPSZ(uint_fast32_t); DUK__DUMPSZ(int_fast32_t); DUK__DUMPSZ(uint_fast64_t); DUK__DUMPSZ(int_fast64_t); DUK__DUMPSZ(uintptr_t); DUK__DUMPSZ(intptr_t); DUK__DUMPSZ(uintmax_t); DUK__DUMPSZ(intmax_t); DUK__DUMPSZ(duk_small_int_t); DUK__DUMPSZ(duk_small_uint_t); DUK__DUMPSZ(duk_codepoint_t); DUK__DUMPSZ(duk_ucodepoint_t); DUK__DUMPSZ(duk_double_t); /* tval */ DUK__DUMPSZ(duk_double_union); DUK__DUMPSZ(duk_tval); /* structs from duk_forwdecl.h */ DUK__DUMPSZ(duk_jmpbuf); DUK__DUMPSZ(duk_heaphdr); DUK__DUMPSZ(duk_heaphdr_string); DUK__DUMPSZ(duk_hstring); DUK__DUMPSZ(duk_hobject); DUK__DUMPSZ(duk_hcompiledfunction); DUK__DUMPSZ(duk_hnativefunction); DUK__DUMPSZ(duk_hthread); DUK__DUMPSZ(duk_hbuffer); DUK__DUMPSZ(duk_hbuffer_fixed); DUK__DUMPSZ(duk_hbuffer_dynamic); DUK__DUMPSZ(duk_propaccessor); DUK__DUMPSZ(duk_propvalue); DUK__DUMPSZ(duk_propdesc); DUK__DUMPSZ(duk_heap); DUK__DUMPSZ(duk_activation); DUK__DUMPSZ(duk_catcher); DUK__DUMPSZ(duk_strcache); DUK__DUMPSZ(duk_ljstate); DUK__DUMPSZ(duk_fixedbuffer); DUK__DUMPSZ(duk_bitdecoder_ctx); DUK__DUMPSZ(duk_bitencoder_ctx); DUK__DUMPSZ(duk_token); DUK__DUMPSZ(duk_re_token); DUK__DUMPSZ(duk_lexer_point); DUK__DUMPSZ(duk_lexer_ctx); DUK__DUMPSZ(duk_compiler_instr); DUK__DUMPSZ(duk_compiler_func); DUK__DUMPSZ(duk_compiler_ctx); DUK__DUMPSZ(duk_re_matcher_ctx); DUK__DUMPSZ(duk_re_compiler_ctx); }
/* XXX: better place for this */ DUK_EXTERNAL void duk_set_global_object(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *h_glob; duk_hobject *h_prev_glob; duk_hobject *h_env; duk_hobject *h_prev_env; DUK_D(DUK_DPRINT("replace global object with: %!T", duk_get_tval(ctx, -1))); h_glob = duk_require_hobject(ctx, -1); DUK_ASSERT(h_glob != NULL); /* * Replace global object. */ h_prev_glob = thr->builtins[DUK_BIDX_GLOBAL]; DUK_UNREF(h_prev_glob); thr->builtins[DUK_BIDX_GLOBAL] = h_glob; DUK_HOBJECT_INCREF(thr, h_glob); DUK_HOBJECT_DECREF_ALLOWNULL(thr, h_prev_glob); /* side effects, in theory (referenced by global env) */ /* * Replace lexical environment for global scope * * Create a new object environment for the global lexical scope. * We can't just reset the _Target property of the current one, * because the lexical scope is shared by other threads with the * same (initial) built-ins. */ (void) duk_push_object_helper(ctx, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJENV), -1); /* no prototype, updated below */ duk_dup(ctx, -2); duk_dup(ctx, -3); /* [ ... new_glob new_env new_glob new_glob ] */ duk_xdef_prop_stridx(thr, -3, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_NONE); duk_xdef_prop_stridx(thr, -2, DUK_STRIDX_INT_THIS, DUK_PROPDESC_FLAGS_NONE); /* [ ... new_glob new_env ] */ h_env = duk_get_hobject(ctx, -1); DUK_ASSERT(h_env != NULL); h_prev_env = thr->builtins[DUK_BIDX_GLOBAL_ENV]; thr->builtins[DUK_BIDX_GLOBAL_ENV] = h_env; DUK_HOBJECT_INCREF(thr, h_env); DUK_HOBJECT_DECREF_ALLOWNULL(thr, h_prev_env); /* side effects */ DUK_UNREF(h_env); /* without refcounts */ DUK_UNREF(h_prev_env); /* [ ... new_glob new_env ] */ duk_pop_2(ctx); /* [ ... ] */ }
DUK_LOCAL duk_hstring *duk__alloc_init_hstring(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash, const duk_uint8_t *extdata) { duk_hstring *res = NULL; duk_uint8_t *data; duk_size_t alloc_size; duk_uarridx_t dummy; duk_uint32_t clen; #if defined(DUK_USE_STRLEN16) /* If blen <= 0xffffUL, clen is also guaranteed to be <= 0xffffUL. */ if (blen > 0xffffUL) { DUK_D(DUK_DPRINT("16-bit string blen/clen active and blen over 16 bits, reject intern")); return NULL; } #endif if (extdata) { alloc_size = (duk_size_t) sizeof(duk_hstring_external); res = (duk_hstring *) DUK_ALLOC(heap, alloc_size); if (!res) { goto alloc_error; } DUK_MEMZERO(res, sizeof(duk_hstring_external)); #ifdef DUK_USE_EXPLICIT_NULL_INIT DUK_HEAPHDR_STRING_INIT_NULLS(&res->hdr); #endif DUK_HEAPHDR_SET_TYPE_AND_FLAGS(&res->hdr, DUK_HTYPE_STRING, DUK_HSTRING_FLAG_EXTDATA); ((duk_hstring_external *) res)->extdata = extdata; } else { /* NUL terminate for convenient C access */ alloc_size = (duk_size_t) (sizeof(duk_hstring) + blen + 1); res = (duk_hstring *) DUK_ALLOC(heap, alloc_size); if (!res) { goto alloc_error; } DUK_MEMZERO(res, sizeof(duk_hstring)); #ifdef DUK_USE_EXPLICIT_NULL_INIT DUK_HEAPHDR_STRING_INIT_NULLS(&res->hdr); #endif DUK_HEAPHDR_SET_TYPE_AND_FLAGS(&res->hdr, DUK_HTYPE_STRING, 0); data = (duk_uint8_t *) (res + 1); DUK_MEMCPY(data, str, blen); data[blen] = (duk_uint8_t) 0; } if (duk_js_to_arrayindex_raw_string(str, blen, &dummy)) { DUK_HSTRING_SET_ARRIDX(res); } /* All strings beginning with 0xff are treated as "internal", * even strings interned by the user. This allows user code to * create internal properties too, and makes behavior consistent * in case user code happens to use a string also used by Duktape * (such as string has already been interned and has the 'internal' * flag set). */ if (blen > 0 && str[0] == (duk_uint8_t) 0xff) { DUK_HSTRING_SET_INTERNAL(res); } DUK_HSTRING_SET_HASH(res, strhash); DUK_HSTRING_SET_BYTELEN(res, blen); clen = (duk_uint32_t) duk_unicode_unvalidated_utf8_length(str, (duk_size_t) blen); DUK_ASSERT(clen <= blen); DUK_HSTRING_SET_CHARLEN(res, clen); DUK_DDD(DUK_DDDPRINT("interned string, hash=0x%08lx, blen=%ld, clen=%ld, has_arridx=%ld, has_extdata=%ld", (unsigned long) DUK_HSTRING_GET_HASH(res), (long) DUK_HSTRING_GET_BYTELEN(res), (long) DUK_HSTRING_GET_CHARLEN(res), (long) (DUK_HSTRING_HAS_ARRIDX(res) ? 1 : 0), (long) (DUK_HSTRING_HAS_EXTDATA(res) ? 1 : 0))); return res; alloc_error: DUK_FREE(heap, res); return NULL; }
DUK_LOCAL void duk__dump_misc_options(void) { DUK_D(DUK_DPRINT("DUK_VERSION: %ld", (long) DUK_VERSION)); DUK_D(DUK_DPRINT("DUK_GIT_DESCRIBE: %s", DUK_GIT_DESCRIBE)); #if defined(DUK_USE_PACKED_TVAL) DUK_D(DUK_DPRINT("DUK_USE_PACKED_TVAL: yes")); #else DUK_D(DUK_DPRINT("DUK_USE_PACKED_TVAL: no")); #endif #if defined(DUK_USE_INTEGER_LE) DUK_D(DUK_DPRINT("Integer endianness: little")); #elif defined(DUK_USE_INTEGER_ME) DUK_D(DUK_DPRINT("Integer endianness: mixed")); #elif defined(DUK_USE_INTEGER_BE) DUK_D(DUK_DPRINT("Integer endianness: big")); #else DUK_D(DUK_DPRINT("Integer endianness: ???")); #endif #if defined(DUK_USE_DOUBLE_LE) DUK_D(DUK_DPRINT("IEEE double endianness: little")); #elif defined(DUK_USE_DOUBLE_ME) DUK_D(DUK_DPRINT("IEEE double endianness: mixed")); #elif defined(DUK_USE_DOUBLE_BE) DUK_D(DUK_DPRINT("IEEE double endianness: big")); #else DUK_D(DUK_DPRINT("IEEE double endianness: ???")); #endif }
DUK_INTERNAL void *duk_heap_mem_alloc(duk_heap *heap, duk_size_t size) { void *res; duk_bool_t rc; duk_small_int_t i; DUK_ASSERT(heap != NULL); DUK_ASSERT_DISABLE(size >= 0); /* * Voluntary periodic GC (if enabled) */ DUK__VOLUNTARY_PERIODIC_GC(heap); /* * First attempt */ #if defined(DUK_USE_GC_TORTURE) /* simulate alloc failure on every alloc (except when mark-and-sweep is running) */ if (!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) { DUK_DDD(DUK_DDDPRINT("gc torture enabled, pretend that first alloc attempt fails")); res = NULL; DUK_UNREF(res); goto skip_attempt; } #endif res = heap->alloc_func(heap->heap_udata, size); if (res || size == 0) { /* for zero size allocations NULL is allowed */ return res; } #if defined(DUK_USE_GC_TORTURE) skip_attempt: #endif DUK_D(DUK_DPRINT("first alloc attempt failed, attempt to gc and retry")); /* * Avoid a GC if GC is already running. This can happen at a late * stage in a GC when we try to e.g. resize the stringtable * or compact objects. */ if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) { DUK_D(DUK_DPRINT("duk_heap_mem_alloc() failed, gc in progress (gc skipped), alloc size %ld", (long) size)); return NULL; } /* * Retry with several GC attempts. Initial attempts are made without * emergency mode; later attempts use emergency mode which minimizes * memory allocations forcibly. */ for (i = 0; i < DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT; i++) { duk_small_uint_t flags; flags = 0; if (i >= DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT - 1) { flags |= DUK_MS_FLAG_EMERGENCY; } rc = duk_heap_mark_and_sweep(heap, flags); DUK_UNREF(rc); res = heap->alloc_func(heap->heap_udata, size); if (res) { DUK_D(DUK_DPRINT("duk_heap_mem_alloc() succeeded after gc (pass %ld), alloc size %ld", (long) (i + 1), (long) size)); return res; } } DUK_D(DUK_DPRINT("duk_heap_mem_alloc() failed even after gc, alloc size %ld", (long) size)); return NULL; }
DUK_INTERNAL duk_heap *duk_heap_alloc(duk_alloc_function alloc_func, duk_realloc_function realloc_func, duk_free_function free_func, void *heap_udata, duk_fatal_function fatal_func) { duk_heap *res = NULL; /* Silence a few global unused warnings here. */ DUK_UNREF(duk_str_unsupported); DUK_D(DUK_DPRINT("allocate heap")); /* * Debug dump type sizes */ #ifdef DUK_USE_DEBUG duk__dump_misc_options(); duk__dump_type_sizes(); duk__dump_type_limits(); #endif /* * If selftests enabled, run them as early as possible */ #ifdef DUK_USE_SELF_TESTS DUK_D(DUK_DPRINT("running self tests")); duk_selftest_run_tests(); DUK_D(DUK_DPRINT("self tests passed")); #endif /* * Computed values (e.g. INFINITY) */ #ifdef DUK_USE_COMPUTED_NAN do { /* Workaround for some exotic platforms where NAN is missing * and the expression (0.0 / 0.0) does NOT result in a NaN. * Such platforms use the global 'duk_computed_nan' which must * be initialized at runtime. Use 'volatile' to ensure that * the compiler will actually do the computation and not try * to do constant folding which might result in the original * problem. */ volatile double dbl1 = 0.0; volatile double dbl2 = 0.0; duk_computed_nan = dbl1 / dbl2; } while (0); #endif #ifdef DUK_USE_COMPUTED_INFINITY do { /* Similar workaround for INFINITY. */ volatile double dbl1 = 1.0; volatile double dbl2 = 0.0; duk_computed_infinity = dbl1 / dbl2; } while (0); #endif /* * Allocate heap struct * * Use a raw call, all macros expect the heap to be initialized */ res = (duk_heap *) alloc_func(heap_udata, sizeof(duk_heap)); if (!res) { goto error; } /* * Zero the struct, and start initializing roughly in order */ DUK_MEMZERO(res, sizeof(*res)); /* explicit NULL inits */ #ifdef DUK_USE_EXPLICIT_NULL_INIT res->heap_udata = NULL; res->heap_allocated = NULL; #ifdef DUK_USE_REFERENCE_COUNTING res->refzero_list = NULL; res->refzero_list_tail = NULL; #endif #ifdef DUK_USE_MARK_AND_SWEEP res->finalize_list = NULL; #endif res->heap_thread = NULL; res->curr_thread = NULL; res->heap_object = NULL; #if defined(DUK_USE_STRTAB_CHAIN) /* nothing to NULL */ #elif defined(DUK_USE_STRTAB_PROBE) #if defined(DUK_USE_HEAPPTR16) res->strtable16 = (duk_uint16_t *) NULL; #else res->strtable = (duk_hstring **) NULL; #endif #endif #if defined(DUK_USE_HEAPPTR16) /* res->strs16[] is zeroed and zero decodes to NULL, so no NULL inits. */ #else { duk_small_uint_t i; for (i = 0; i < DUK_HEAP_NUM_STRINGS; i++) { res->strs[i] = NULL; } } #endif #if defined(DUK_USE_DEBUGGER_SUPPORT) res->dbg_read_cb = NULL; res->dbg_write_cb = NULL; res->dbg_peek_cb = NULL; res->dbg_read_flush_cb = NULL; res->dbg_write_flush_cb = NULL; res->dbg_udata = NULL; res->dbg_step_thread = NULL; #endif #endif /* DUK_USE_EXPLICIT_NULL_INIT */ res->alloc_func = alloc_func; res->realloc_func = realloc_func; res->free_func = free_func; res->heap_udata = heap_udata; res->fatal_func = fatal_func; #if defined(DUK_USE_HEAPPTR16) /* XXX: zero assumption */ res->heapptr_null16 = DUK_USE_HEAPPTR_ENC16(res->heap_udata, (void *) NULL); res->heapptr_deleted16 = DUK_USE_HEAPPTR_ENC16(res->heap_udata, (void *) DUK_STRTAB_DELETED_MARKER(res)); #endif /* res->mark_and_sweep_trigger_counter == 0 -> now causes immediate GC; which is OK */ res->call_recursion_depth = 0; res->call_recursion_limit = DUK_HEAP_DEFAULT_CALL_RECURSION_LIMIT; /* XXX: use the pointer as a seed for now: mix in time at least */ /* The casts through duk_intr_pt is to avoid the following GCC warning: * * warning: cast from pointer to integer of different size [-Wpointer-to-int-cast] * * This still generates a /Wp64 warning on VS2010 when compiling for x86. */ res->hash_seed = (duk_uint32_t) (duk_intptr_t) res; res->rnd_state = (duk_uint32_t) (duk_intptr_t) res; #ifdef DUK_USE_EXPLICIT_NULL_INIT res->lj.jmpbuf_ptr = NULL; #endif DUK_ASSERT(res->lj.type == DUK_LJ_TYPE_UNKNOWN); /* zero */ DUK_TVAL_SET_UNDEFINED_UNUSED(&res->lj.value1); DUK_TVAL_SET_UNDEFINED_UNUSED(&res->lj.value2); #if (DUK_STRTAB_INITIAL_SIZE < DUK_UTIL_MIN_HASH_PRIME) #error initial heap stringtable size is defined incorrectly #endif /* * Init stringtable: fixed variant */ #if defined(DUK_USE_STRTAB_CHAIN) DUK_MEMZERO(res->strtable, sizeof(duk_strtab_entry) * DUK_STRTAB_CHAIN_SIZE); #ifdef DUK_USE_EXPLICIT_NULL_INIT { duk_small_uint_t i; for (i = 0; i < DUK_STRTAB_CHAIN_SIZE; i++) { #if defined(DUK_USE_HEAPPTR16) res->strtable[i].u.str16 = res->heapptr_null16; #else res->strtable[i].u.str = NULL; #endif } } #endif /* DUK_USE_EXPLICIT_NULL_INIT */ #endif /* DUK_USE_STRTAB_CHAIN */ /* * Init stringtable: probe variant */ #if defined(DUK_USE_STRTAB_PROBE) #if defined(DUK_USE_HEAPPTR16) res->strtable16 = (duk_uint16_t *) alloc_func(heap_udata, sizeof(duk_uint16_t) * DUK_STRTAB_INITIAL_SIZE); if (!res->strtable16) { goto error; } #else /* DUK_USE_HEAPPTR16 */ res->strtable = (duk_hstring **) alloc_func(heap_udata, sizeof(duk_hstring *) * DUK_STRTAB_INITIAL_SIZE); if (!res->strtable) { goto error; } #endif /* DUK_USE_HEAPPTR16 */ res->st_size = DUK_STRTAB_INITIAL_SIZE; #ifdef DUK_USE_EXPLICIT_NULL_INIT { duk_small_uint_t i; DUK_ASSERT(res->st_size == DUK_STRTAB_INITIAL_SIZE); for (i = 0; i < DUK_STRTAB_INITIAL_SIZE; i++) { #if defined(DUK_USE_HEAPPTR16) res->strtable16[i] = res->heapptr_null16; #else res->strtable[i] = NULL; #endif } } #else /* DUK_USE_EXPLICIT_NULL_INIT */ #if defined(DUK_USE_HEAPPTR16) DUK_MEMZERO(res->strtable16, sizeof(duk_uint16_t) * DUK_STRTAB_INITIAL_SIZE); #else DUK_MEMZERO(res->strtable, sizeof(duk_hstring *) * DUK_STRTAB_INITIAL_SIZE); #endif #endif /* DUK_USE_EXPLICIT_NULL_INIT */ #endif /* DUK_USE_STRTAB_PROBE */ /* * Init stringcache */ #ifdef DUK_USE_EXPLICIT_NULL_INIT { duk_small_uint_t i; for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) { res->strcache[i].h = NULL; } } #endif /* XXX: error handling is incomplete. It would be cleanest if * there was a setjmp catchpoint, so that all init code could * freely throw errors. If that were the case, the return code * passing here could be removed. */ /* * Init built-in strings */ DUK_DD(DUK_DDPRINT("HEAP: INIT STRINGS")); if (!duk__init_heap_strings(res)) { goto error; } /* * Init the heap thread */ DUK_DD(DUK_DDPRINT("HEAP: INIT HEAP THREAD")); if (!duk__init_heap_thread(res)) { goto error; } /* * Init the heap object */ DUK_DD(DUK_DDPRINT("HEAP: INIT HEAP OBJECT")); DUK_ASSERT(res->heap_thread != NULL); res->heap_object = duk_hobject_alloc(res, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT)); if (!res->heap_object) { goto error; } DUK_HOBJECT_INCREF(res->heap_thread, res->heap_object); /* * All done */ DUK_D(DUK_DPRINT("allocated heap: %p", (void *) res)); return res; error: DUK_D(DUK_DPRINT("heap allocation failed")); if (res) { /* assumes that allocated pointers and alloc funcs are valid * if res exists */ DUK_ASSERT(res->alloc_func != NULL); DUK_ASSERT(res->realloc_func != NULL); DUK_ASSERT(res->free_func != NULL); duk_heap_free(res); } return NULL; }
void duk_debug_dump_hobject(duk_hobject *obj) { duk_uint_fast32_t i; const char *str_empty = ""; const char *str_excl = "!"; DUK_D(DUK_DPRINT("=== hobject %p ===", (void *) obj)); if (!obj) { return; } DUK_D(DUK_DPRINT(" %sextensible", DUK_HOBJECT_HAS_EXTENSIBLE(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %sconstructable", DUK_HOBJECT_HAS_CONSTRUCTABLE(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %sbound", DUK_HOBJECT_HAS_BOUND(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %scompiledfunction", DUK_HOBJECT_HAS_COMPILEDFUNCTION(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %snativefunction", DUK_HOBJECT_HAS_NATIVEFUNCTION(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %sthread", DUK_HOBJECT_HAS_THREAD(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %sarray_part", DUK_HOBJECT_HAS_ARRAY_PART(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %sstrict", DUK_HOBJECT_HAS_STRICT(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %snewenv", DUK_HOBJECT_HAS_NEWENV(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %snamebinding", DUK_HOBJECT_HAS_NAMEBINDING(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %screateargs", DUK_HOBJECT_HAS_CREATEARGS(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %senvrecclosed", DUK_HOBJECT_HAS_ENVRECCLOSED(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %sexotic_array", DUK_HOBJECT_HAS_EXOTIC_ARRAY(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %sexotic_stringobj", DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %sexotic_arguments", DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %sexotic_dukfunc", DUK_HOBJECT_HAS_EXOTIC_DUKFUNC(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %sexotic_bufferobj", DUK_HOBJECT_HAS_EXOTIC_BUFFEROBJ(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" %sexotic_proxyobj", DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj) ? str_empty : str_excl)); DUK_D(DUK_DPRINT(" class: number %d -> %s", (int) DUK_HOBJECT_GET_CLASS_NUMBER(obj), duk__class_names[(DUK_HOBJECT_GET_CLASS_NUMBER(obj)) & ((1 << DUK_HOBJECT_FLAG_CLASS_BITS) - 1)])); DUK_D(DUK_DPRINT(" prototype: %p -> %!O", (void *) obj->prototype, (duk_heaphdr *) obj->prototype)); DUK_D(DUK_DPRINT(" props: p=%p, e_size=%d, e_used=%d, a_size=%d, h_size=%d", (void *) obj->p, (int) obj->e_size, (int) obj->e_used, (int) obj->a_size, (int) obj->h_size)); /* * Object (struct layout) specific dumping. Inline code here * instead of helpers, to ensure debug line prefix is identical. */ if (DUK_HOBJECT_IS_COMPILEDFUNCTION(obj)) { duk_hcompiledfunction *h = (duk_hcompiledfunction *) obj; DUK_D(DUK_DPRINT(" hcompiledfunction")); DUK_D(DUK_DPRINT(" data: %!O", h->data)); DUK_D(DUK_DPRINT(" nregs: %d", (int) h->nregs)); DUK_D(DUK_DPRINT(" nargs: %d", (int) h->nargs)); if (h->data && DUK_HBUFFER_HAS_DYNAMIC(h->data) && DUK_HBUFFER_GET_DATA_PTR(h->data)) { DUK_D(DUK_DPRINT(" consts: %p (%d, %d bytes)", (void *) DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(h), (int) DUK_HCOMPILEDFUNCTION_GET_CONSTS_COUNT(h), (int) DUK_HCOMPILEDFUNCTION_GET_CONSTS_SIZE(h))); DUK_D(DUK_DPRINT(" funcs: %p (%d, %d bytes)", (void *) DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(h), (int) DUK_HCOMPILEDFUNCTION_GET_FUNCS_COUNT(h), (int) DUK_HCOMPILEDFUNCTION_GET_FUNCS_SIZE(h))); DUK_D(DUK_DPRINT(" bytecode: %p (%d, %d bytes)", (void *) DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(h), (int) DUK_HCOMPILEDFUNCTION_GET_CODE_COUNT(h), (int) DUK_HCOMPILEDFUNCTION_GET_CODE_SIZE(h))); } else { DUK_D(DUK_DPRINT(" consts: ???")); DUK_D(DUK_DPRINT(" funcs: ???")); DUK_D(DUK_DPRINT(" bytecode: ???")); } } else if (DUK_HOBJECT_IS_NATIVEFUNCTION(obj)) { duk_hnativefunction *h = (duk_hnativefunction *) obj; DUK_D(DUK_DPRINT(" hnativefunction")); /* XXX: h->func, cannot print function pointers portably */ DUK_D(DUK_DPRINT(" nargs: %d", (int) h->nargs)); } else if (DUK_HOBJECT_IS_THREAD(obj)) { duk_hthread *thr = (duk_hthread *) obj; duk_tval *p; DUK_D(DUK_DPRINT(" hthread")); DUK_D(DUK_DPRINT(" strict: %d", (int) thr->strict)); DUK_D(DUK_DPRINT(" state: %d", (int) thr->state)); DUK_D(DUK_DPRINT(" valstack_max: %d, callstack_max:%d, catchstack_max: %d", thr->valstack_max, thr->callstack_max, thr->catchstack_max)); DUK_D(DUK_DPRINT(" callstack: ptr %p, size %d, top %d, preventcount %d, used size %d entries (%d bytes), alloc size %d entries (%d bytes)", (void *) thr->callstack, thr->callstack_size, thr->callstack_top, thr->callstack_preventcount, thr->callstack_top, thr->callstack_top * sizeof(duk_activation), thr->callstack_size, thr->callstack_size * sizeof(duk_activation))); DUK_DEBUG_SUMMARY_INIT(); DUK_DEBUG_SUMMARY_CHAR('['); for (i = 0; i <= thr->callstack_size; i++) { if (i == thr->callstack_top) { DUK_DEBUG_SUMMARY_CHAR('|'); } if (!thr->callstack) { DUK_DEBUG_SUMMARY_CHAR('@'); } else if (i < thr->callstack_size) { if (i < thr->callstack_top) { /* tailcalling is nice to see immediately; other flags (e.g. strict) * not that important. */ if (thr->callstack[i].flags & DUK_ACT_FLAG_TAILCALLED) { DUK_DEBUG_SUMMARY_CHAR('/'); } DUK_DEBUG_SUMMARY_CHAR(duk__get_act_summary_char(&thr->callstack[i])); } else { DUK_DEBUG_SUMMARY_CHAR('.'); } } } DUK_DEBUG_SUMMARY_CHAR(']'); DUK_DEBUG_SUMMARY_FINISH(); DUK_D(DUK_DPRINT(" valstack: ptr %p, end %p (%d), bottom %p (%d), top %p (%d), used size %d entries (%d bytes), alloc size %d entries (%d bytes)", (void *) thr->valstack, (void *) thr->valstack_end, (int) (thr->valstack_end - thr->valstack), (void *) thr->valstack_bottom, (int) (thr->valstack_bottom - thr->valstack), (void *) thr->valstack_top, (int) (thr->valstack_top - thr->valstack), (int) (thr->valstack_top - thr->valstack), (int) (thr->valstack_top - thr->valstack) * sizeof(duk_tval), (int) (thr->valstack_end - thr->valstack), (int) (thr->valstack_end - thr->valstack) * sizeof(duk_tval))); DUK_DEBUG_SUMMARY_INIT(); DUK_DEBUG_SUMMARY_CHAR('['); p = thr->valstack; while (p <= thr->valstack_end) { i = (duk_uint_fast32_t) (p - thr->valstack); if (thr->callstack && thr->callstack_top > 0 && i == (duk_size_t) (thr->callstack + thr->callstack_top - 1)->idx_bottom) { DUK_DEBUG_SUMMARY_CHAR('>'); } if (p == thr->valstack_top) { DUK_DEBUG_SUMMARY_CHAR('|'); } if (p < thr->valstack_end) { if (p < thr->valstack_top) { DUK_DEBUG_SUMMARY_CHAR(duk__get_tval_summary_char(p)); } else { /* XXX: safe printer for these? would be nice, because * we could visualize whether the values are in proper * state. */ DUK_DEBUG_SUMMARY_CHAR('.'); } } p++; } DUK_DEBUG_SUMMARY_CHAR(']'); DUK_DEBUG_SUMMARY_FINISH(); DUK_D(DUK_DPRINT(" catchstack: ptr %p, size %d, top %d, used size %d entries (%d bytes), alloc size %d entries (%d bytes)", (void *) thr->catchstack, thr->catchstack_size, thr->catchstack_top, thr->catchstack_top, thr->catchstack_top * sizeof(duk_catcher), thr->catchstack_size, thr->catchstack_size * sizeof(duk_catcher))); DUK_DEBUG_SUMMARY_INIT(); DUK_DEBUG_SUMMARY_CHAR('['); for (i = 0; i <= thr->catchstack_size; i++) { if (i == thr->catchstack_top) { DUK_DEBUG_SUMMARY_CHAR('|'); } if (!thr->catchstack) { DUK_DEBUG_SUMMARY_CHAR('@'); } else if (i < thr->catchstack_size) { if (i < thr->catchstack_top) { DUK_DEBUG_SUMMARY_CHAR(duk__get_cat_summary_char(&thr->catchstack[i])); } else { DUK_DEBUG_SUMMARY_CHAR('.'); } } } DUK_DEBUG_SUMMARY_CHAR(']'); DUK_DEBUG_SUMMARY_FINISH(); DUK_D(DUK_DPRINT(" resumer: ptr %p", (void *) thr->resumer)); #if 0 /* worth dumping? */ for (i = 0; i < DUK_NUM_BUILTINS; i++) { DUK_D(DUK_DPRINT(" builtins[%d] -> %!@O", i, thr->builtins[i])); } #endif } if (obj->p) { DUK_D(DUK_DPRINT(" props alloc size: %d", (int) DUK_HOBJECT_P_COMPUTE_SIZE(obj->e_size, obj->a_size, obj->h_size))); } else { DUK_D(DUK_DPRINT(" props alloc size: n/a")); } DUK_D(DUK_DPRINT(" prop entries:")); for (i = 0; i < obj->e_size; i++) { duk_hstring *k; duk_propvalue *v; k = DUK_HOBJECT_E_GET_KEY(obj, i); v = DUK_HOBJECT_E_GET_VALUE_PTR(obj, i); if (i >= obj->e_used) { DUK_D(DUK_DPRINT(" [%d]: UNUSED", i)); continue; } if (!k) { DUK_D(DUK_DPRINT(" [%d]: NULL", i)); continue; } if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, i)) { DUK_D(DUK_DPRINT(" [%d]: [w=%d e=%d c=%d a=%d] %!O -> get:%p set:%p; get %!O; set %!O", i, DUK_HOBJECT_E_SLOT_IS_WRITABLE(obj, i), DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(obj, i), DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(obj, i), DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, i), k, (void *) v->a.get, (void *) v->a.set, (duk_heaphdr *) v->a.get, (duk_heaphdr *) v->a.set)); } else { DUK_D(DUK_DPRINT(" [%d]: [w=%d e=%d c=%d a=%d] %!O -> %!T", i, DUK_HOBJECT_E_SLOT_IS_WRITABLE(obj, i), DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(obj, i), DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(obj, i), DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, i), k, &v->v)); } } DUK_D(DUK_DPRINT(" array entries:")); for (i = 0; i < obj->a_size; i++) { DUK_D(DUK_DPRINT(" [%d]: [w=%d e=%d c=%d a=%d] %d -> %!T", i, 1, /* implicit attributes */ 1, 1, 0, i, DUK_HOBJECT_A_GET_VALUE_PTR(obj, i))); } DUK_D(DUK_DPRINT(" hash entries:")); for (i = 0; i < obj->h_size; i++) { duk_uint32_t t = DUK_HOBJECT_H_GET_INDEX(obj, i); if (t == DUK_HOBJECT_HASHIDX_UNUSED) { DUK_D(DUK_DPRINT(" [%d]: unused", i)); } else if (t == DUK_HOBJECT_HASHIDX_DELETED) { DUK_D(DUK_DPRINT(" [%d]: deleted", i)); } else { DUK_D(DUK_DPRINT(" [%d]: %d", i, (int) t)); } } }
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 */ }