/* * duk_heap allocation and freeing. */ #include "duk_internal.h" #if defined(DUK_USE_ROM_STRINGS) /* Fixed seed value used with ROM strings. */ #define DUK__FIXED_HASH_SEED 0xabcd1234 #endif /* * Free a heap object. * * Free heap object and its internal (non-heap) pointers. Assumes that * caller has removed the object from heap allocated list or the string * intern table, and any weak references (which strings may have) have * been already dealt with. */ DUK_INTERNAL void duk_free_hobject(duk_heap *heap, duk_hobject *h) { DUK_ASSERT(heap != NULL); DUK_ASSERT(h != NULL); DUK_FREE(heap, DUK_HOBJECT_GET_PROPS(heap, h)); if (DUK_HOBJECT_IS_COMPFUNC(h)) { duk_hcompfunc *f = (duk_hcompfunc *) h; DUK_UNREF(f); /* Currently nothing to free; 'data' is a heap object */ } else if (DUK_HOBJECT_IS_NATFUNC(h)) { duk_hnatfunc *f = (duk_hnatfunc *) h; DUK_UNREF(f); /* Currently nothing to free */ } else if (DUK_HOBJECT_IS_THREAD(h)) { duk_hthread *t = (duk_hthread *) h; duk_activation *act; DUK_FREE(heap, t->valstack); /* Don't free h->resumer because it exists in the heap. * Callstack entries also contain function pointers which * are not freed for the same reason. They are decref * finalized and the targets are freed if necessary based * on their refcount (or reachability). */ for (act = t->callstack_curr; act != NULL;) { duk_activation *act_next; duk_catcher *cat; for (cat = act->cat; cat != NULL;) { duk_catcher *cat_next; cat_next = cat->parent; DUK_FREE(heap, (void *) cat); cat = cat_next; } act_next = act->parent; DUK_FREE(heap, (void *) act); act = act_next; } /* XXX: with 'caller' property the callstack would need * to be unwound to update the 'caller' properties of * functions in the callstack. */ } else if (DUK_HOBJECT_IS_BOUNDFUNC(h)) { duk_hboundfunc *f = (duk_hboundfunc *) (void *) h; DUK_FREE(heap, f->args); } DUK_FREE(heap, (void *) h); } DUK_INTERNAL void duk_free_hbuffer(duk_heap *heap, duk_hbuffer *h) { DUK_ASSERT(heap != NULL); DUK_ASSERT(h != NULL); if (DUK_HBUFFER_HAS_DYNAMIC(h) && !DUK_HBUFFER_HAS_EXTERNAL(h)) { duk_hbuffer_dynamic *g = (duk_hbuffer_dynamic *) h; DUK_DDD(DUK_DDDPRINT("free dynamic buffer %p", (void *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(heap, g))); DUK_FREE(heap, DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(heap, g)); } DUK_FREE(heap, (void *) h); } DUK_INTERNAL void duk_free_hstring(duk_heap *heap, duk_hstring *h) { DUK_ASSERT(heap != NULL); DUK_ASSERT(h != NULL); DUK_UNREF(heap); DUK_UNREF(h); #if defined(DUK_USE_HSTRING_EXTDATA) && defined(DUK_USE_EXTSTR_FREE) if (DUK_HSTRING_HAS_EXTDATA(h)) { DUK_DDD(DUK_DDDPRINT("free extstr: hstring %!O, extdata: %p", h, DUK_HSTRING_GET_EXTDATA((duk_hstring_external *) h))); DUK_USE_EXTSTR_FREE(heap->heap_udata, (const void *) DUK_HSTRING_GET_EXTDATA((duk_hstring_external *) h)); } #endif DUK_FREE(heap, (void *) h); } DUK_INTERNAL void duk_heap_free_heaphdr_raw(duk_heap *heap, duk_heaphdr *hdr) { DUK_ASSERT(heap); DUK_ASSERT(hdr); DUK_DDD(DUK_DDDPRINT("free heaphdr %p, htype %ld", (void *) hdr, (long) DUK_HEAPHDR_GET_TYPE(hdr))); switch (DUK_HEAPHDR_GET_TYPE(hdr)) { case DUK_HTYPE_STRING: duk_free_hstring(heap, (duk_hstring *) hdr); break; case DUK_HTYPE_OBJECT: duk_free_hobject(heap, (duk_hobject *) hdr); break; default: DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(hdr) == DUK_HTYPE_BUFFER); duk_free_hbuffer(heap, (duk_hbuffer *) hdr); } } /* * Free the heap. * * Frees heap-related non-heap-tracked allocations such as the * string intern table; then frees the heap allocated objects; * and finally frees the heap structure itself. Reference counts * and GC markers are ignored (and not updated) in this process, * and finalizers won't be called. * * The heap pointer and heap object pointers must not be used * after this call. */ #if defined(DUK_USE_CACHE_ACTIVATION) DUK_LOCAL duk_size_t duk__heap_free_activation_freelist(duk_heap *heap) { duk_activation *act; duk_activation *act_next; duk_size_t count_act = 0; for (act = heap->activation_free; act != NULL;) { act_next = act->parent; DUK_FREE(heap, (void *) act); act = act_next; #if defined(DUK_USE_DEBUG) count_act++; #endif } heap->activation_free = NULL; /* needed when called from mark-and-sweep */ return count_act; } #endif /* DUK_USE_CACHE_ACTIVATION */ #if defined(DUK_USE_CACHE_CATCHER) DUK_LOCAL duk_size_t duk__heap_free_catcher_freelist(duk_heap *heap) { duk_catcher *cat; duk_catcher *cat_next; duk_size_t count_cat = 0; for (cat = heap->catcher_free; cat != NULL;) { cat_next = cat->parent; DUK_FREE(heap, (void *) cat); cat = cat_next; #if defined(DUK_USE_DEBUG) count_cat++; #endif } heap->catcher_free = NULL; /* needed when called from mark-and-sweep */ return count_cat; } #endif /* DUK_USE_CACHE_CATCHER */ DUK_INTERNAL void duk_heap_free_freelists(duk_heap *heap) { duk_size_t count_act = 0; duk_size_t count_cat = 0; #if defined(DUK_USE_CACHE_ACTIVATION) count_act = duk__heap_free_activation_freelist(heap); #endif #if defined(DUK_USE_CACHE_CATCHER) count_cat = duk__heap_free_catcher_freelist(heap); #endif DUK_UNREF(heap); DUK_UNREF(count_act); DUK_UNREF(count_cat); DUK_D(DUK_DPRINT("freed %ld activation freelist entries, %ld catcher freelist entries", (long) count_act, (long) count_cat)); } DUK_LOCAL void duk__free_allocated(duk_heap *heap) { duk_heaphdr *curr; duk_heaphdr *next; curr = heap->heap_allocated; while (curr) { /* We don't log or warn about freeing zero refcount objects * because they may happen with finalizer processing. */ DUK_DDD(DUK_DDDPRINT("FINALFREE (allocated): %!iO", (duk_heaphdr *) curr)); next = DUK_HEAPHDR_GET_NEXT(heap, curr); duk_heap_free_heaphdr_raw(heap, curr); curr = next; } } #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_LOCAL void duk__free_finalize_list(duk_heap *heap) { duk_heaphdr *curr; duk_heaphdr *next; curr = heap->finalize_list; while (curr) { DUK_DDD(DUK_DDDPRINT("FINALFREE (finalize_list): %!iO", (duk_heaphdr *) curr)); next = DUK_HEAPHDR_GET_NEXT(heap, curr); duk_heap_free_heaphdr_raw(heap, curr); curr = next; } } #endif /* DUK_USE_FINALIZER_SUPPORT */ DUK_LOCAL void duk__free_stringtable(duk_heap *heap) { /* strings are only tracked by stringtable */ duk_heap_strtable_free(heap); } #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_LOCAL void duk__free_run_finalizers(duk_heap *heap) { duk_heaphdr *curr; duk_uint_t round_no; duk_size_t count_all; duk_size_t count_finalized; duk_size_t curr_limit; DUK_ASSERT(heap != NULL); #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(heap->refzero_list == NULL); /* refzero not running -> must be empty */ #endif DUK_ASSERT(heap->finalize_list == NULL); /* mark-and-sweep last pass */ if (heap->heap_thread == NULL) { /* May happen when heap allocation fails right off. There * cannot be any finalizable objects in this case. */ DUK_D(DUK_DPRINT("no heap_thread in heap destruct, assume no finalizable objects")); return; } /* Prevent finalize_list processing and mark-and-sweep entirely. * Setting ms_running != 0 also prevents refzero handling from moving * objects away from the heap_allocated list. The flag name is a bit * misleading here. * * Use a distinct value for ms_running here (== 2) so that assertions * can detect this situation separate from the normal runtime * mark-and-sweep case. This allows better assertions (GH-2030). */ DUK_ASSERT(heap->pf_prevent_count == 0); DUK_ASSERT(heap->ms_running == 0); DUK_ASSERT(heap->ms_prevent_count == 0); heap->pf_prevent_count = 1; heap->ms_running = 2; /* Use distinguishable value. */ heap->ms_prevent_count = 1; /* Bump, because mark-and-sweep assumes it's bumped when ms_running is set. */ curr_limit = 0; /* suppress warning, not used */ for (round_no = 0; ; round_no++) { curr = heap->heap_allocated; count_all = 0; count_finalized = 0; while (curr) { count_all++; if (DUK_HEAPHDR_IS_OBJECT(curr)) { /* Only objects in heap_allocated may have finalizers. Check that * the object itself has a _Finalizer property (own or inherited) * so that we don't execute finalizers for e.g. Proxy objects. */ DUK_ASSERT(curr != NULL); if (DUK_HOBJECT_HAS_FINALIZER_FAST(heap, (duk_hobject *) curr)) { if (!DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) curr)) { DUK_ASSERT(DUK_HEAP_HAS_FINALIZER_NORESCUE(heap)); /* maps to finalizer 2nd argument */ duk_heap_run_finalizer(heap, (duk_hobject *) curr); count_finalized++; } } } curr = DUK_HEAPHDR_GET_NEXT(heap, curr); } /* Each round of finalizer execution may spawn new finalizable objects * which is normal behavior for some applications. Allow multiple * rounds of finalization, but use a shrinking limit based on the * first round to detect the case where a runaway finalizer creates * an unbounded amount of new finalizable objects. Finalizer rescue * is not supported: the semantics are unclear because most of the * objects being finalized here are already reachable. The finalizer * is given a boolean to indicate that rescue is not possible. * * See discussion in: https://github.com/svaarala/duktape/pull/473 */ if (round_no == 0) { /* Cannot wrap: each object is at least 8 bytes so count is * at most 1/8 of that. */ curr_limit = count_all * 2; } else { curr_limit = (curr_limit * 3) / 4; /* Decrease by 25% every round */ } DUK_D(DUK_DPRINT("finalizer round %ld complete, %ld objects, tried to execute %ld finalizers, current limit is %ld", (long) round_no, (long) count_all, (long) count_finalized, (long) curr_limit)); if (count_finalized == 0) { DUK_D(DUK_DPRINT("no more finalizable objects, forced finalization finished")); break; } if (count_finalized >= curr_limit) { DUK_D(DUK_DPRINT("finalizer count above limit, potentially runaway finalizer; skip remaining finalizers")); break; } } DUK_ASSERT(heap->ms_running == 2); DUK_ASSERT(heap->pf_prevent_count == 1); heap->ms_running = 0; heap->pf_prevent_count = 0; } #endif /* DUK_USE_FINALIZER_SUPPORT */ DUK_INTERNAL void duk_heap_free(duk_heap *heap) { DUK_D(DUK_DPRINT("free heap: %p", (void *) heap)); #if defined(DUK_USE_DEBUG) duk_heap_strtable_dump(heap); #endif #if defined(DUK_USE_DEBUGGER_SUPPORT) /* Detach a debugger if attached (can be called multiple times) * safely. */ /* XXX: Add a flag to reject an attempt to re-attach? Otherwise * the detached callback may immediately reattach. */ duk_debug_do_detach(heap); #endif /* Execute finalizers before freeing the heap, even for reachable * objects. This gives finalizers the chance to free any native * resources like file handles, allocations made outside Duktape, * etc. This is quite tricky to get right, so that all finalizer * guarantees are honored. * * Run mark-and-sweep a few times just in case (unreachable object * finalizers run already here). The last round must rescue objects * from the previous round without running any more finalizers. This * ensures rescued objects get their FINALIZED flag cleared so that * their finalizer is called once more in forced finalization to * satisfy finalizer guarantees. However, we don't want to run any * more finalizers because that'd required one more loop, and so on. * * XXX: this perhaps requires an execution time limit. */ DUK_D(DUK_DPRINT("execute finalizers before freeing heap")); DUK_ASSERT(heap->pf_skip_finalizers == 0); DUK_D(DUK_DPRINT("forced gc #1 in heap destruction")); duk_heap_mark_and_sweep(heap, 0); DUK_D(DUK_DPRINT("forced gc #2 in heap destruction")); duk_heap_mark_and_sweep(heap, 0); DUK_D(DUK_DPRINT("forced gc #3 in heap destruction (don't run finalizers)")); heap->pf_skip_finalizers = 1; duk_heap_mark_and_sweep(heap, 0); /* Skip finalizers; queue finalizable objects to heap_allocated. */ /* There are never objects in refzero_list at this point, or at any * point beyond a DECREF (even a DECREF_NORZ). Since Duktape 2.1 * refzero_list processing is side effect free, so it is always * processed to completion by a DECREF initially triggering a zero * refcount. */ #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(heap->refzero_list == NULL); /* Always processed to completion inline. */ #endif #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_ASSERT(heap->finalize_list == NULL); /* Last mark-and-sweep with skip_finalizers. */ #endif #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_D(DUK_DPRINT("run finalizers for remaining finalizable objects")); DUK_HEAP_SET_FINALIZER_NORESCUE(heap); /* Rescue no longer supported. */ duk__free_run_finalizers(heap); #endif /* DUK_USE_FINALIZER_SUPPORT */ /* Note: heap->heap_thread, heap->curr_thread, and heap->heap_object * are on the heap allocated list. */ DUK_D(DUK_DPRINT("freeing temporary freelists")); duk_heap_free_freelists(heap); DUK_D(DUK_DPRINT("freeing heap_allocated of heap: %p", (void *) heap)); duk__free_allocated(heap); #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(heap->refzero_list == NULL); /* Always processed to completion inline. */ #endif #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_D(DUK_DPRINT("freeing finalize_list of heap: %p", (void *) heap)); duk__free_finalize_list(heap); #endif DUK_D(DUK_DPRINT("freeing string table of heap: %p", (void *) heap)); duk__free_stringtable(heap); DUK_D(DUK_DPRINT("freeing heap structure: %p", (void *) heap)); heap->free_func(heap->heap_udata, heap); } /* * Allocate a heap. * * String table is initialized with built-in strings from genbuiltins.py, * either by dynamically creating the strings or by referring to ROM strings. */ #if defined(DUK_USE_ROM_STRINGS) DUK_LOCAL duk_bool_t duk__init_heap_strings(duk_heap *heap) { #if defined(DUK_USE_ASSERTIONS) duk_small_uint_t i; #endif DUK_UNREF(heap); /* With ROM-based strings, heap->strs[] and thr->strs[] are omitted * so nothing to initialize for strs[]. */ #if defined(DUK_USE_ASSERTIONS) for (i = 0; i < sizeof(duk_rom_strings_lookup) / sizeof(const duk_hstring *); i++) { const duk_hstring *h; duk_uint32_t hash; h = duk_rom_strings_lookup[i]; while (h != NULL) { hash = duk_heap_hashstring(heap, (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h), DUK_HSTRING_GET_BYTELEN(h)); DUK_DD(DUK_DDPRINT("duk_rom_strings_lookup[%d] -> hash 0x%08lx, computed 0x%08lx", (int) i, (unsigned long) DUK_HSTRING_GET_HASH(h), (unsigned long) hash)); DUK_ASSERT(hash == (duk_uint32_t) DUK_HSTRING_GET_HASH(h)); h = (const duk_hstring *) h->hdr.h_next; } } #endif return 1; } #else /* DUK_USE_ROM_STRINGS */ DUK_LOCAL duk_bool_t duk__init_heap_strings(duk_heap *heap) { duk_bitdecoder_ctx bd_ctx; duk_bitdecoder_ctx *bd = &bd_ctx; /* convenience */ duk_small_uint_t i; duk_memzero(&bd_ctx, sizeof(bd_ctx)); bd->data = (const duk_uint8_t *) duk_strings_data; bd->length = (duk_size_t) DUK_STRDATA_DATA_LENGTH; for (i = 0; i < DUK_HEAP_NUM_STRINGS; i++) { duk_uint8_t tmp[DUK_STRDATA_MAX_STRLEN]; duk_small_uint_t len; duk_hstring *h; len = duk_bd_decode_bitpacked_string(bd, tmp); /* No need to length check string: it will never exceed even * the 16-bit length maximum. */ DUK_ASSERT(len <= 0xffffUL); DUK_DDD(DUK_DDDPRINT("intern built-in string %ld", (long) i)); h = duk_heap_strtable_intern(heap, tmp, len); if (!h) { goto failed; } DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) h)); /* Special flags checks. Since these strings are always * reachable and a string cannot appear twice in the string * table, there's no need to check/set these flags elsewhere. * The 'internal' flag is set by string intern code. */ if (i == DUK_STRIDX_EVAL || i == DUK_STRIDX_LC_ARGUMENTS) { DUK_HSTRING_SET_EVAL_OR_ARGUMENTS(h); } if (i >= DUK_STRIDX_START_RESERVED && i < DUK_STRIDX_END_RESERVED) { DUK_HSTRING_SET_RESERVED_WORD(h); if (i >= DUK_STRIDX_START_STRICT_RESERVED) { DUK_HSTRING_SET_STRICT_RESERVED_WORD(h); } } DUK_DDD(DUK_DDDPRINT("interned: %!O", (duk_heaphdr *) h)); /* XXX: The incref macro takes a thread pointer but doesn't * use it right now. */ DUK_HSTRING_INCREF(_never_referenced_, h); #if defined(DUK_USE_HEAPPTR16) heap->strs16[i] = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h); #else heap->strs[i] = h; #endif } return 1; failed: return 0; } #endif /* DUK_USE_ROM_STRINGS */ DUK_LOCAL duk_bool_t duk__init_heap_thread(duk_heap *heap) { duk_hthread *thr; DUK_D(DUK_DPRINT("heap init: alloc heap thread")); thr = duk_hthread_alloc_unchecked(heap, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_THREAD)); if (thr == NULL) { DUK_D(DUK_DPRINT("failed to alloc heap_thread")); return 0; } thr->state = DUK_HTHREAD_STATE_INACTIVE; #if defined(DUK_USE_ROM_STRINGS) /* No strs[] pointer. */ #else /* DUK_USE_ROM_STRINGS */ #if defined(DUK_USE_HEAPPTR16) thr->strs16 = heap->strs16; #else thr->strs = heap->strs; #endif #endif /* DUK_USE_ROM_STRINGS */ heap->heap_thread = thr; DUK_HTHREAD_INCREF(thr, thr); /* Note: first argument not really used */ /* 'thr' is now reachable */ DUK_D(DUK_DPRINT("heap init: init heap thread stacks")); if (!duk_hthread_init_stacks(heap, thr)) { return 0; } /* XXX: this may now fail, and is not handled correctly */ duk_hthread_create_builtin_objects(thr); /* default prototype */ DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, (duk_hobject *) thr, thr->builtins[DUK_BIDX_THREAD_PROTOTYPE]); return 1; } #if defined(DUK_USE_DEBUG) #define DUK__DUMPSZ(t) do { \ DUK_D(DUK_DPRINT("" #t "=%ld", (long) sizeof(t))); \ } while (0) /* These is not 100% because format would need to be non-portable "long long". * Also print out as doubles to catch cases where the "long" type is not wide * enough; the limits will then not be printed accurately but the magnitude * will be correct. */ #define DUK__DUMPLM_SIGNED_RAW(t,a,b) do { \ DUK_D(DUK_DPRINT(t "=[%ld,%ld]=[%lf,%lf]", \ (long) (a), (long) (b), \ (double) (a), (double) (b))); \ } while (0) #define DUK__DUMPLM_UNSIGNED_RAW(t,a,b) do { \ DUK_D(DUK_DPRINT(t "=[%lu,%lu]=[%lf,%lf]", \ (unsigned long) (a), (unsigned long) (b), \ (double) (a), (double) (b))); \ } while (0) #define DUK__DUMPLM_SIGNED(t) do { \ DUK__DUMPLM_SIGNED_RAW("DUK_" #t "_{MIN,MAX}", DUK_##t##_MIN, DUK_##t##_MAX); \ } while (0) #define DUK__DUMPLM_UNSIGNED(t) do { \ DUK__DUMPLM_UNSIGNED_RAW("DUK_" #t "_{MIN,MAX}", DUK_##t##_MIN, DUK_##t##_MAX); \ } while (0) 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); /* just one 'int' for C++ exceptions */ 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_harray); DUK__DUMPSZ(duk_hcompfunc); DUK__DUMPSZ(duk_hnatfunc); DUK__DUMPSZ(duk_hdecenv); DUK__DUMPSZ(duk_hobjenv); DUK__DUMPSZ(duk_hthread); #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK__DUMPSZ(duk_hbufobj); #endif DUK__DUMPSZ(duk_hproxy); DUK__DUMPSZ(duk_hbuffer); DUK__DUMPSZ(duk_hbuffer_fixed); DUK__DUMPSZ(duk_hbuffer_dynamic); DUK__DUMPSZ(duk_hbuffer_external); 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_entry); DUK__DUMPSZ(duk_litcache_entry); 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_LOCAL void duk__dump_type_limits(void) { DUK_D(DUK_DPRINT("limits")); /* basic types */ DUK__DUMPLM_SIGNED(INT8); DUK__DUMPLM_UNSIGNED(UINT8); DUK__DUMPLM_SIGNED(INT_FAST8); DUK__DUMPLM_UNSIGNED(UINT_FAST8); DUK__DUMPLM_SIGNED(INT_LEAST8); DUK__DUMPLM_UNSIGNED(UINT_LEAST8); DUK__DUMPLM_SIGNED(INT16); DUK__DUMPLM_UNSIGNED(UINT16); DUK__DUMPLM_SIGNED(INT_FAST16); DUK__DUMPLM_UNSIGNED(UINT_FAST16); DUK__DUMPLM_SIGNED(INT_LEAST16); DUK__DUMPLM_UNSIGNED(UINT_LEAST16); DUK__DUMPLM_SIGNED(INT32); DUK__DUMPLM_UNSIGNED(UINT32); DUK__DUMPLM_SIGNED(INT_FAST32); DUK__DUMPLM_UNSIGNED(UINT_FAST32); DUK__DUMPLM_SIGNED(INT_LEAST32); DUK__DUMPLM_UNSIGNED(UINT_LEAST32); #if defined(DUK_USE_64BIT_OPS) DUK__DUMPLM_SIGNED(INT64); DUK__DUMPLM_UNSIGNED(UINT64); DUK__DUMPLM_SIGNED(INT_FAST64); DUK__DUMPLM_UNSIGNED(UINT_FAST64); DUK__DUMPLM_SIGNED(INT_LEAST64); DUK__DUMPLM_UNSIGNED(UINT_LEAST64); #endif DUK__DUMPLM_SIGNED(INTPTR); DUK__DUMPLM_UNSIGNED(UINTPTR); DUK__DUMPLM_SIGNED(INTMAX); DUK__DUMPLM_UNSIGNED(UINTMAX); /* derived types */ DUK__DUMPLM_SIGNED(INT); DUK__DUMPLM_UNSIGNED(UINT); DUK__DUMPLM_SIGNED(INT_FAST); DUK__DUMPLM_UNSIGNED(UINT_FAST); DUK__DUMPLM_SIGNED(SMALL_INT); DUK__DUMPLM_UNSIGNED(SMALL_UINT); DUK__DUMPLM_SIGNED(SMALL_INT_FAST); DUK__DUMPLM_UNSIGNED(SMALL_UINT_FAST); } 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)); DUK_D(DUK_DPRINT("OS string: %s", DUK_USE_OS_STRING)); DUK_D(DUK_DPRINT("architecture string: %s", DUK_USE_ARCH_STRING)); DUK_D(DUK_DPRINT("compiler string: %s", DUK_USE_COMPILER_STRING)); DUK_D(DUK_DPRINT("debug level: %ld", (long) DUK_USE_DEBUG_LEVEL)); #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_VARIADIC_MACROS) DUK_D(DUK_DPRINT("DUK_USE_VARIADIC_MACROS: yes")); #else DUK_D(DUK_DPRINT("DUK_USE_VARIADIC_MACROS: 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 } #endif /* DUK_USE_DEBUG */ 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; duk_uint32_t st_initsize; DUK_D(DUK_DPRINT("allocate heap")); /* * Random config sanity asserts */ DUK_ASSERT(DUK_USE_STRTAB_MINSIZE >= 64); DUK_ASSERT((DUK_HTYPE_STRING & 0x01U) == 0); DUK_ASSERT((DUK_HTYPE_BUFFER & 0x01U) == 0); DUK_ASSERT((DUK_HTYPE_OBJECT & 0x01U) == 1); /* DUK_HEAPHDR_IS_OBJECT() relies ont his. */ /* * Debug dump type sizes */ #if defined(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. */ #if defined(DUK_USE_SELF_TESTS) DUK_D(DUK_DPRINT("run self tests")); if (duk_selftest_run_tests(alloc_func, realloc_func, free_func, heap_udata) > 0) { fatal_func(heap_udata, "self test(s) failed"); } DUK_D(DUK_DPRINT("self tests passed")); #endif /* * Important assert-like checks that should be enabled even * when assertions are otherwise not enabled. */ #if defined(DUK_USE_EXEC_REGCONST_OPTIMIZE) /* Can't check sizeof() using preprocessor so explicit check. * This will be optimized away in practice; unfortunately a * warning is generated on some compilers as a result. */ #if defined(DUK_USE_PACKED_TVAL) if (sizeof(duk_tval) != 8) { #else if (sizeof(duk_tval) != 16) { #endif fatal_func(heap_udata, "sizeof(duk_tval) not 8 or 16, cannot use DUK_USE_EXEC_REGCONST_OPTIMIZE option"); } #endif /* DUK_USE_EXEC_REGCONST_OPTIMIZE */ /* * Computed values (e.g. INFINITY) */ #if defined(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 #if defined(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 */ #if defined(DUK_USE_INJECT_HEAP_ALLOC_ERROR) && (DUK_USE_INJECT_HEAP_ALLOC_ERROR == 1) goto failed; #endif DUK_D(DUK_DPRINT("alloc duk_heap object")); res = (duk_heap *) alloc_func(heap_udata, sizeof(duk_heap)); if (!res) { goto failed; } /* * Zero the struct, and start initializing roughly in order */ duk_memzero(res, sizeof(*res)); #if defined(DUK_USE_ASSERTIONS) res->heap_initializing = 1; #endif /* explicit NULL inits */ #if defined(DUK_USE_EXPLICIT_NULL_INIT) res->heap_udata = NULL; res->heap_allocated = NULL; #if defined(DUK_USE_REFERENCE_COUNTING) res->refzero_list = NULL; #endif #if defined(DUK_USE_FINALIZER_SUPPORT) res->finalize_list = NULL; #if defined(DUK_USE_ASSERTIONS) res->currently_finalizing = NULL; #endif #endif #if defined(DUK_USE_CACHE_ACTIVATION) res->activation_free = NULL; #endif #if defined(DUK_USE_CACHE_CATCHER) res->catcher_free = NULL; #endif res->heap_thread = NULL; res->curr_thread = NULL; res->heap_object = NULL; #if defined(DUK_USE_STRTAB_PTRCOMP) res->strtable16 = NULL; #else res->strtable = NULL; #endif #if defined(DUK_USE_ROM_STRINGS) /* no res->strs[] */ #else /* DUK_USE_ROM_STRINGS */ #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 #endif /* DUK_USE_ROM_STRINGS */ #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_request_cb = NULL; res->dbg_udata = NULL; res->dbg_pause_act = 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; /* XXX: for now there's a pointer packing zero assumption, i.e. * NULL <=> compressed pointer 0. If this is removed, may need * to precompute e.g. null16 here. */ /* res->ms_trigger_counter == 0 -> now causes immediate GC; which is OK */ /* Prevent mark-and-sweep and finalizer execution until heap is completely * initialized. */ DUK_ASSERT(res->ms_prevent_count == 0); DUK_ASSERT(res->pf_prevent_count == 0); res->ms_prevent_count = 1; res->pf_prevent_count = 1; DUK_ASSERT(res->ms_running == 0); res->call_recursion_depth = 0; res->call_recursion_limit = DUK_USE_NATIVE_CALL_RECLIMIT; /* XXX: use the pointer as a seed for now: mix in time at least */ /* The casts through duk_uintptr_t 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. */ #if defined(DUK_USE_ROM_STRINGS) /* XXX: make a common DUK_USE_ option, and allow custom fixed seed? */ DUK_D(DUK_DPRINT("using rom strings, force heap hash_seed to fixed value 0x%08lx", (long) DUK__FIXED_HASH_SEED)); res->hash_seed = (duk_uint32_t) DUK__FIXED_HASH_SEED; #else /* DUK_USE_ROM_STRINGS */ res->hash_seed = (duk_uint32_t) (duk_uintptr_t) res; #if !defined(DUK_USE_STRHASH_DENSE) res->hash_seed ^= 5381; /* Bernstein hash init value is normally 5381; XOR it in in case pointer low bits are 0 */ #endif #endif /* DUK_USE_ROM_STRINGS */ #if defined(DUK_USE_EXPLICIT_NULL_INIT) res->lj.jmpbuf_ptr = NULL; #endif DUK_ASSERT(res->lj.type == DUK_LJ_TYPE_UNKNOWN); /* zero */ DUK_ASSERT(res->lj.iserror == 0); DUK_TVAL_SET_UNDEFINED(&res->lj.value1); DUK_TVAL_SET_UNDEFINED(&res->lj.value2); DUK_ASSERT_LJSTATE_UNSET(res); /* * Init stringtable: fixed variant */ st_initsize = DUK_USE_STRTAB_MINSIZE; #if defined(DUK_USE_STRTAB_PTRCOMP) res->strtable16 = (duk_uint16_t *) alloc_func(heap_udata, sizeof(duk_uint16_t) * st_initsize); if (res->strtable16 == NULL) { goto failed; } #else res->strtable = (duk_hstring **) alloc_func(heap_udata, sizeof(duk_hstring *) * st_initsize); if (res->strtable == NULL) { goto failed; } #endif res->st_size = st_initsize; res->st_mask = st_initsize - 1; #if (DUK_USE_STRTAB_MINSIZE != DUK_USE_STRTAB_MAXSIZE) DUK_ASSERT(res->st_count == 0); #endif #if defined(DUK_USE_STRTAB_PTRCOMP) /* zero assumption */ duk_memzero(res->strtable16, sizeof(duk_uint16_t) * st_initsize); #else #if defined(DUK_USE_EXPLICIT_NULL_INIT) { duk_uint32_t i; for (i = 0; i < st_initsize; i++) { res->strtable[i] = NULL; } } #else duk_memzero(res->strtable, sizeof(duk_hstring *) * st_initsize); #endif /* DUK_USE_EXPLICIT_NULL_INIT */ #endif /* DUK_USE_STRTAB_PTRCOMP */ /* * Init stringcache */ #if defined(DUK_USE_EXPLICIT_NULL_INIT) { duk_uint_t i; for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) { res->strcache[i].h = NULL; } } #endif /* * Init litcache */ #if defined(DUK_USE_LITCACHE_SIZE) DUK_ASSERT(DUK_USE_LITCACHE_SIZE > 0); DUK_ASSERT(DUK_IS_POWER_OF_TWO((duk_uint_t) DUK_USE_LITCACHE_SIZE)); #if defined(DUK_USE_EXPLICIT_NULL_INIT) { duk_uint_t i; for (i = 0; i < DUK_USE_LITCACHE_SIZE; i++) { res->litcache[i].addr = NULL; res->litcache[i].h = NULL; } } #endif #endif /* DUK_USE_LITCACHE_SIZE */ /* 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 */ #if defined(DUK_USE_INJECT_HEAP_ALLOC_ERROR) && (DUK_USE_INJECT_HEAP_ALLOC_ERROR == 2) goto failed; #endif DUK_D(DUK_DPRINT("heap init: initialize heap strings")); if (!duk__init_heap_strings(res)) { goto failed; } /* * Init the heap thread */ #if defined(DUK_USE_INJECT_HEAP_ALLOC_ERROR) && (DUK_USE_INJECT_HEAP_ALLOC_ERROR == 3) goto failed; #endif DUK_D(DUK_DPRINT("heap init: initialize heap thread")); if (!duk__init_heap_thread(res)) { goto failed; } /* * Init the heap object */ #if defined(DUK_USE_INJECT_HEAP_ALLOC_ERROR) && (DUK_USE_INJECT_HEAP_ALLOC_ERROR == 4) goto failed; #endif DUK_D(DUK_DPRINT("heap init: initialize heap object")); DUK_ASSERT(res->heap_thread != NULL); res->heap_object = duk_hobject_alloc_unchecked(res, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_FASTREFS | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT)); if (res->heap_object == NULL) { goto failed; } DUK_HOBJECT_INCREF(res->heap_thread, res->heap_object); /* * Odds and ends depending on the heap thread */ #if !defined(DUK_USE_GET_RANDOM_DOUBLE) #if defined(DUK_USE_PREFER_SIZE) || !defined(DUK_USE_64BIT_OPS) res->rnd_state = (duk_uint32_t) duk_time_get_ecmascript_time(res->heap_thread); duk_util_tinyrandom_prepare_seed(res->heap_thread); #else res->rnd_state[0] = (duk_uint64_t) duk_time_get_ecmascript_time(res->heap_thread); DUK_ASSERT(res->rnd_state[1] == 0); /* Not filled here, filled in by seed preparation. */ #if 0 /* Manual test values matching misc/xoroshiro128plus_test.c. */ res->rnd_state[0] = DUK_U64_CONSTANT(0xdeadbeef12345678); res->rnd_state[1] = DUK_U64_CONSTANT(0xcafed00d12345678); #endif duk_util_tinyrandom_prepare_seed(res->heap_thread); /* Mix in heap pointer: this ensures that if two Duktape heaps are * created on the same millisecond, they get a different PRNG * sequence (unless e.g. virtual memory addresses cause also the * heap object pointer to be the same). */ { duk_uint64_t tmp_u64; tmp_u64 = 0; duk_memcpy((void *) &tmp_u64, (const void *) &res, (size_t) (sizeof(void *) >= sizeof(duk_uint64_t) ? sizeof(duk_uint64_t) : sizeof(void *))); res->rnd_state[1] ^= tmp_u64; } do { duk_small_uint_t i; for (i = 0; i < 10; i++) { /* Throw away a few initial random numbers just in * case. Probably unnecessary due to SplitMix64 * preparation. */ (void) duk_util_tinyrandom_get_double(res->heap_thread); } } while (0); #endif #endif /* * Allow finalizer and mark-and-sweep processing. */ DUK_D(DUK_DPRINT("heap init: allow finalizer/mark-and-sweep processing")); DUK_ASSERT(res->ms_prevent_count == 1); DUK_ASSERT(res->pf_prevent_count == 1); res->ms_prevent_count = 0; res->pf_prevent_count = 0; DUK_ASSERT(res->ms_running == 0); #if defined(DUK_USE_ASSERTIONS) res->heap_initializing = 0; #endif /* * All done. */ DUK_D(DUK_DPRINT("allocated heap: %p", (void *) res)); return res; failed: DUK_D(DUK_DPRINT("heap allocation failed")); if (res != NULL) { /* Assumes that allocated pointers and alloc funcs are valid * if res exists. */ DUK_ASSERT(res->ms_prevent_count == 1); DUK_ASSERT(res->pf_prevent_count == 1); DUK_ASSERT(res->ms_running == 0); if (res->heap_thread != NULL) { res->ms_prevent_count = 0; res->pf_prevent_count = 0; } #if defined(DUK_USE_ASSERTIONS) res->heap_initializing = 0; #endif DUK_ASSERT(res->alloc_func != NULL); DUK_ASSERT(res->realloc_func != NULL); DUK_ASSERT(res->free_func != NULL); duk_heap_free(res); } return NULL; }