DUK_INTERNAL duk_ret_t duk_bi_object_constructor_seal_freeze_shared(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *h; duk_bool_t is_freeze; DUK_ASSERT_TOP(ctx, 1); is_freeze = (duk_bool_t) duk_get_current_magic(ctx); if (duk_is_buffer(ctx, 0)) { /* Plain buffer: already sealed, but not frozen (and can't be frozen * because index properties can't be made non-writable. */ if (is_freeze) { goto fail_cannot_freeze; } return 1; } else if (duk_is_lightfunc(ctx, 0)) { /* Lightfunc: already sealed and frozen, success. */ return 1; } #if 0 /* Seal/freeze are quite rare in practice so it'd be nice to get the * correct behavior simply via automatic promotion (at the cost of some * memory churn). However, the promoted objects don't behave the same, * e.g. promoted lightfuncs are extensible. */ h = duk_require_hobject_promote_mask(ctx, 0, DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER); #endif h = duk_get_hobject(ctx, 0); if (h == NULL) { /* ES2015 Sections 19.1.2.5, 19.1.2.17 */ return 1; } if (is_freeze && DUK_HOBJECT_IS_BUFOBJ(h)) { /* Buffer objects cannot be frozen because there's no internal * support for making virtual array indices non-writable. */ DUK_DD(DUK_DDPRINT("cannot freeze a buffer object")); goto fail_cannot_freeze; } duk_hobject_object_seal_freeze_helper(thr, h, is_freeze); /* Sealed and frozen objects cannot gain any more properties, * so this is a good time to compact them. */ duk_hobject_compact_props(thr, h); return 1; fail_cannot_freeze: DUK_DCERROR_TYPE_INVALID_ARGS(thr); /* XXX: proper error message */ }
DUK_LOCAL void duk__mark_refzero_list(duk_heap *heap) { duk_heaphdr *hdr; DUK_DD(DUK_DDPRINT("duk__mark_refzero_list: %p", (void *) heap)); hdr = heap->refzero_list; while (hdr) { duk__mark_heaphdr(heap, hdr); hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); } }
DUK_LOCAL void duk__run_voluntary_gc(duk_heap *heap) { if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) { DUK_DD(DUK_DDPRINT("mark-and-sweep in progress -> skip voluntary mark-and-sweep now")); } else { duk_small_uint_t flags; duk_bool_t rc; DUK_D(DUK_DPRINT("triggering voluntary mark-and-sweep")); flags = 0; rc = duk_heap_mark_and_sweep(heap, flags); DUK_UNREF(rc); } }
DUK_LOCAL duk_ret_t duk__refcount_fake_finalizer(duk_context *ctx) { DUK_UNREF(ctx); DUK_D(DUK_DPRINT("fake refcount torture finalizer executed")); #if 0 DUK_DD(DUK_DDPRINT("fake torture finalizer for: %!T", duk_get_tval(ctx, 0))); #endif /* Require a lot of stack to force a value stack grow/shrink. */ duk_require_stack(ctx, 100000); /* XXX: do something to force a callstack grow/shrink, perhaps * just a manual forced resize? */ return 0; }
DUK_LOCAL void duk__clear_refzero_list_flags(duk_heap *heap) { duk_heaphdr *hdr; DUK_DD(DUK_DDPRINT("duk__clear_refzero_list_flags: %p", (void *) heap)); hdr = heap->refzero_list; while (hdr) { DUK_HEAPHDR_CLEAR_REACHABLE(hdr); DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr)); /* DUK_HEAPHDR_HAS_FINALIZED may or may not be set. */ DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr)); hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); } }
DUK_LOCAL void duk__clear_finalize_list_flags(duk_heap *heap) { duk_heaphdr *hdr; DUK_DD(DUK_DDPRINT("duk__clear_finalize_list_flags: %p", (void *) heap)); hdr = heap->finalize_list; while (hdr) { DUK_HEAPHDR_CLEAR_REACHABLE(hdr); DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr)); DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(hdr)); DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr)); hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); } }
DUK_LOCAL void duk__compact_object_list(duk_heap *heap, duk_hthread *thr, duk_heaphdr *start, duk_size_t *p_count_check, duk_size_t *p_count_compact, duk_size_t *p_count_bytes_saved) { #else DUK_LOCAL void duk__compact_object_list(duk_heap *heap, duk_hthread *thr, duk_heaphdr *start) { #endif duk_heaphdr *curr; #if defined(DUK_USE_DEBUG) duk_size_t old_size, new_size; #endif duk_hobject *obj; DUK_UNREF(heap); curr = start; while (curr) { DUK_DDD(DUK_DDDPRINT("mark-and-sweep compact: %p", (void *) curr)); if (DUK_HEAPHDR_GET_TYPE(curr) != DUK_HTYPE_OBJECT) { goto next; } obj = (duk_hobject *) curr; #if defined(DUK_USE_DEBUG) old_size = DUK_HOBJECT_P_COMPUTE_SIZE(DUK_HOBJECT_GET_ESIZE(obj), DUK_HOBJECT_GET_ASIZE(obj), DUK_HOBJECT_GET_HSIZE(obj)); #endif DUK_DD(DUK_DDPRINT("compact object: %p", (void *) obj)); duk_push_hobject((duk_context *) thr, obj); /* XXX: disable error handlers for duration of compaction? */ duk_safe_call((duk_context *) thr, duk__protected_compact_object, NULL, 1, 0); #if defined(DUK_USE_DEBUG) new_size = DUK_HOBJECT_P_COMPUTE_SIZE(DUK_HOBJECT_GET_ESIZE(obj), DUK_HOBJECT_GET_ASIZE(obj), DUK_HOBJECT_GET_HSIZE(obj)); #endif #if defined(DUK_USE_DEBUG) (*p_count_compact)++; (*p_count_bytes_saved) += (duk_size_t) (old_size - new_size); #endif next: curr = DUK_HEAPHDR_GET_NEXT(heap, curr); #if defined(DUK_USE_DEBUG) (*p_count_check)++; #endif } }
DUK_LOCAL void duk__sweep_stringtable_chain(duk_heap *heap, duk_size_t *out_count_keep) { duk_strtab_entry *e; duk_uint_fast32_t i; duk_size_t count_free = 0; duk_size_t count_keep = 0; duk_size_t j, n; #if defined(DUK_USE_HEAPPTR16) duk_uint16_t *lst; #else duk_hstring **lst; #endif DUK_DD(DUK_DDPRINT("duk__sweep_stringtable: %p", (void *) heap)); /* 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). */ for (i = 0; i < DUK_STRTAB_CHAIN_SIZE; i++) { e = heap->strtable + i; if (e->listlen == 0) { #if defined(DUK_USE_HEAPPTR16) duk__sweep_string_chain16(heap, &e->u.str16, &count_keep, &count_free); #else duk__sweep_string_chain(heap, &e->u.str, &count_keep, &count_free); #endif } else { #if defined(DUK_USE_HEAPPTR16) lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16); #else lst = e->u.strlist; #endif for (j = 0, n = e->listlen; j < n; j++) { #if defined(DUK_USE_HEAPPTR16) duk__sweep_string_chain16(heap, lst + j, &count_keep, &count_free); #else duk__sweep_string_chain(heap, lst + j, &count_keep, &count_free); #endif } } } DUK_D(DUK_DPRINT("mark-and-sweep sweep stringtable: %ld freed, %ld kept", (long) count_free, (long) count_keep)); *out_count_keep = count_keep; }
static void duk__run_object_finalizers(duk_heap *heap) { duk_heaphdr *curr; duk_heaphdr *next; #ifdef DUK_USE_DEBUG int count = 0; #endif duk_hthread *thr; DUK_DD(DUK_DDPRINT("duk__run_object_finalizers: %p", (void *) heap)); thr = duk__get_temp_hthread(heap); DUK_ASSERT(thr != NULL); curr = heap->finalize_list; while (curr) { DUK_DDD(DUK_DDDPRINT("mark-and-sweep finalize: %p", (void *) curr)); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT); /* only objects have finalizers */ DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr)); /* flags have been already cleared */ DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(curr)); DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr)); DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr)); /* run the finalizer */ duk_hobject_run_finalizer(thr, (duk_hobject *) curr); /* must never longjmp */ /* mark FINALIZED, for next mark-and-sweep (will collect unless has become reachable; * prevent running finalizer again if reachable) */ DUK_HEAPHDR_SET_FINALIZED(curr); /* queue back to heap_allocated */ next = DUK_HEAPHDR_GET_NEXT(curr); DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, curr); curr = next; #ifdef DUK_USE_DEBUG count++; #endif } /* finalize_list will always be processed completely */ heap->finalize_list = NULL; #ifdef DUK_USE_DEBUG DUK_D(DUK_DPRINT("mark-and-sweep finalize objects: %d finalizers called", count)); #endif }
DUK_INTERNAL void duk_heap_strcache_string_remove(duk_heap *heap, duk_hstring *h) { duk_small_int_t i; for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) { duk_strcache *c = heap->strcache + i; if (c->h == h) { DUK_DD(DUK_DDPRINT("deleting weak strcache reference to hstring %p from heap %p", (void *) h, (void *) heap)); c->h = NULL; /* XXX: the string shouldn't appear twice, but we now loop to the * end anyway; if fixed, add a looping assertion to ensure there * is no duplicate. */ } } }
DUK_LOCAL duk_hstring *duk__do_lookup(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t *out_strhash) { duk_hstring *res; DUK_ASSERT(out_strhash); *out_strhash = duk_heap_hashstring(heap, str, (duk_size_t) blen); #if defined(DUK_USE_ROM_STRINGS) { duk_small_uint_t i; /* XXX: This is VERY inefficient now, and should be e.g. a * binary search or perfect hash, to be fixed. */ for (i = 0; i < (duk_small_uint_t) (sizeof(duk_rom_strings) / sizeof(duk_hstring *)); i++) { duk_hstring *romstr; romstr = (duk_hstring *) duk_rom_strings[i]; if (blen == DUK_HSTRING_GET_BYTELEN(romstr) && DUK_MEMCMP(str, (void *) DUK_HSTRING_GET_DATA(romstr), blen) == 0) { DUK_DD(DUK_DDPRINT("intern check: rom string: %!O, computed hash 0x%08lx, rom hash 0x%08lx", romstr, (unsigned long) *out_strhash, (unsigned long) DUK_HSTRING_GET_HASH(romstr))); DUK_ASSERT(*out_strhash == DUK_HSTRING_GET_HASH(romstr)); *out_strhash = DUK_HSTRING_GET_HASH(romstr); return romstr; } } } #endif /* DUK_USE_ROM_STRINGS */ #if defined(DUK_USE_STRTAB_CHAIN) res = duk__find_matching_string_chain(heap, str, blen, *out_strhash); #elif defined(DUK_USE_STRTAB_PROBE) res = duk__find_matching_string_probe(heap, #if defined(DUK_USE_HEAPPTR16) heap->strtable16, #else heap->strtable, #endif heap->st_size, str, blen, *out_strhash); #else #error internal error, invalid strtab options #endif return res; }
DUK_LOCAL DUK_INLINE void duk__check_voluntary_gc(duk_heap *heap) { if (DUK_UNLIKELY(--(heap)->ms_trigger_counter < 0)) { #if defined(DUK_USE_DEBUG) if (heap->ms_prevent_count == 0) { DUK_D(DUK_DPRINT("triggering voluntary mark-and-sweep")); } else { DUK_DD(DUK_DDPRINT("gc blocked -> skip voluntary mark-and-sweep now")); } #endif /* Prevention checks in the call target handle cases where * voluntary GC is not allowed. The voluntary GC trigger * counter is only rewritten if mark-and-sweep actually runs. */ duk_heap_mark_and_sweep(heap, DUK_MS_FLAG_VOLUNTARY /*flags*/); } }
static void duk__mark_roots_heap(duk_heap *heap) { int i; DUK_DD(DUK_DDPRINT("duk__mark_roots_heap: %p", (void *) heap)); duk__mark_heaphdr(heap, (duk_heaphdr *) heap->heap_thread); duk__mark_heaphdr(heap, (duk_heaphdr *) heap->heap_object); duk__mark_heaphdr(heap, (duk_heaphdr *) heap->log_buffer); for (i = 0; i < DUK_HEAP_NUM_STRINGS; i++) { duk_hstring *h = heap->strs[i]; duk__mark_heaphdr(heap, (duk_heaphdr *) h); } duk__mark_tval(heap, &heap->lj.value1); duk__mark_tval(heap, &heap->lj.value2); }
DUK_INTERNAL void duk_err_longjmp(duk_hthread *thr) { DUK_ASSERT(thr != 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)); #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_dump_strtab(duk_heap *heap) { duk_uint32_t i; duk_hstring *h; DUK_ASSERT(heap != NULL); #if defined(DUK_USE_HEAPPTR16) DUK_ASSERT(heap->strtable16 != NULL); #else DUK_ASSERT(heap->strtable != NULL); #endif for (i = 0; i < heap->st_size; i++) { #if defined(DUK_USE_HEAPPTR16) h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->strtable16[i]); #else h = heap->strtable[i]; #endif DUK_DD(DUK_DDPRINT("[%03d] -> %p", (int) i, (void *) h)); } }
DUK_LOCAL duk_bool_t duk__init_heap_thread(duk_heap *heap) { duk_hthread *thr; DUK_DD(DUK_DDPRINT("heap init: alloc heap thread")); thr = duk_hthread_alloc(heap, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_THREAD | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_THREAD)); if (!thr) { 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 */ 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 (Note: 'thr' must be reachable) */ DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, (duk_hobject *) thr, thr->builtins[DUK_BIDX_THREAD_PROTOTYPE]); return 1; }
DUK_INTERNAL void duk_err_longjmp(duk_hthread *thr) { DUK_ASSERT(thr != 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)); #if defined(DUK_USE_CPP_EXCEPTIONS) /* XXX: detecting uncaught exception case for C++ case; perhaps need * some marker in heap->lj state that a try-catch is active. For now, * invokes C++ uncaught exception handling. */ #else if (!thr->heap->lj.jmpbuf_ptr) { /* * If we don't have a jmpbuf_ptr, there is little we can do * except panic. The caller's expectation is that we never * return. */ 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)); duk_fatal((duk_context *) thr, DUK_ERR_UNCAUGHT_ERROR, "uncaught error"); DUK_UNREACHABLE(); } #endif #if defined(DUK_USE_CPP_EXCEPTIONS) { duk_internal_exception exc; /* dummy */ throw exc; } #else DUK_LONGJMP(thr->heap->lj.jmpbuf_ptr->jb); #endif DUK_UNREACHABLE(); }
DUK_INTERNAL void duk_hthread_catchstack_grow(duk_hthread *thr) { duk_catcher *new_ptr; duk_size_t old_size; duk_size_t new_size; DUK_ASSERT(thr != NULL); DUK_ASSERT_DISABLE(thr->catchstack_top); /* avoid warning (unsigned) */ DUK_ASSERT(thr->catchstack_size >= thr->catchstack_top); if (thr->catchstack_top < thr->catchstack_size) { return; } old_size = thr->catchstack_size; new_size = old_size + DUK_CATCHSTACK_GROW_STEP; /* this is a bit approximate (errors out before max is reached); this is OK */ if (new_size >= thr->catchstack_max) { DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, DUK_STR_CATCHSTACK_LIMIT); } DUK_DD(DUK_DDPRINT("growing catchstack %ld -> %ld", (long) old_size, (long) new_size)); /* * Note: must use indirect variant of DUK_REALLOC() because underlying * pointer may be changed by mark-and-sweep. */ DUK_ASSERT(new_size > 0); new_ptr = (duk_catcher *) DUK_REALLOC_INDIRECT(thr->heap, duk_hthread_get_catchstack_ptr, (void *) thr, sizeof(duk_catcher) * new_size); if (!new_ptr) { /* No need for a NULL/zero-size check because new_size > 0) */ DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, DUK_STR_REALLOC_FAILED); } thr->catchstack = new_ptr; thr->catchstack_size = new_size; /* note: any entries above the catchstack top are garbage and not zeroed */ }
DUK_LOCAL void duk__mark_roots_heap(duk_heap *heap) { duk_small_uint_t i; DUK_DD(DUK_DDPRINT("duk__mark_roots_heap: %p", (void *) heap)); duk__mark_heaphdr(heap, (duk_heaphdr *) heap->heap_thread); duk__mark_heaphdr(heap, (duk_heaphdr *) heap->heap_object); for (i = 0; i < DUK_HEAP_NUM_STRINGS; i++) { duk_hstring *h = DUK_HEAP_GET_STRING(heap, i); duk__mark_heaphdr(heap, (duk_heaphdr *) h); } duk__mark_tval(heap, &heap->lj.value1); duk__mark_tval(heap, &heap->lj.value2); #if defined(DUK_USE_DEBUGGER_SUPPORT) for (i = 0; i < heap->dbg_breakpoint_count; i++) { duk__mark_heaphdr(heap, (duk_heaphdr *) heap->dbg_breakpoints[i].filename); } #endif }
DUK_LOCAL duk_bool_t duk__init_heap_strings(duk_heap *heap) { #if defined(DUK_USE_ASSERTIONS) duk_small_uint_t i; #endif /* 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) / sizeof(const duk_hstring *); i++) { duk_uint32_t hash; const duk_hstring *h; h = duk_rom_strings[i]; DUK_ASSERT(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[%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)); } #endif return 1; }
DUK_INTERNAL void duk_hthread_callstack_shrink_check(duk_hthread *thr) { duk_size_t new_size; duk_activation *p; DUK_ASSERT(thr != NULL); DUK_ASSERT_DISABLE(thr->callstack_top >= 0); /* avoid warning (unsigned) */ DUK_ASSERT(thr->callstack_size >= thr->callstack_top); if (thr->callstack_size - thr->callstack_top < DUK_CALLSTACK_SHRINK_THRESHOLD) { return; } new_size = thr->callstack_top + DUK_CALLSTACK_SHRINK_SPARE; DUK_ASSERT(new_size >= thr->callstack_top); DUK_DD(DUK_DDPRINT("shrinking callstack %ld -> %ld", (long) thr->callstack_size, (long) new_size)); /* * Note: must use indirect variant of DUK_REALLOC() because underlying * pointer may be changed by mark-and-sweep. */ /* shrink failure is not fatal */ p = (duk_activation *) DUK_REALLOC_INDIRECT(thr->heap, duk_hthread_get_callstack_ptr, (void *) thr, sizeof(duk_activation) * new_size); if (p) { thr->callstack = p; thr->callstack_size = new_size; } else { /* Because new_size != 0, if condition doesn't need to be * (p != NULL || new_size == 0). */ DUK_ASSERT(new_size != 0); DUK_D(DUK_DPRINT("callstack shrink failed, ignoring")); } /* note: any entries above the callstack top are garbage and not zeroed */ }
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_NATIVEFUNCTION(obj)) { if (((duk_hnativefunction *) 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__mark_finalize_list(duk_heap *heap) { duk_heaphdr *hdr; #if defined(DUK_USE_DEBUG) duk_size_t count_finalize_list = 0; #endif DUK_DD(DUK_DDPRINT("duk__mark_finalize_list: %p", (void *) heap)); hdr = heap->finalize_list; while (hdr) { duk__mark_heaphdr(heap, hdr); hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); #if defined(DUK_USE_DEBUG) count_finalize_list++; #endif } #if defined(DUK_USE_DEBUG) if (count_finalize_list > 0) { DUK_D(DUK_DPRINT("marked %ld objects on the finalize_list as reachable (previous finalizer run skipped)", (long) count_finalize_list)); } #endif }
DUK_LOCAL void duk__err_augment_user(duk_hthread *thr, duk_small_uint_t stridx_cb) { duk_context *ctx = (duk_context *) thr; duk_tval *tv_hnd; duk_small_uint_t call_flags; duk_int_t rc; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); DUK_ASSERT_DISABLE(stridx_cb >= 0); /* unsigned */ DUK_ASSERT(stridx_cb < DUK_HEAP_NUM_STRINGS); if (DUK_HEAP_HAS_ERRHANDLER_RUNNING(thr->heap)) { DUK_DD(DUK_DDPRINT("recursive call to error handler, ignore")); return; } /* * Check whether or not we have an error handler. * * We must be careful of not triggering an error when looking up the * property. For instance, if the property is a getter, we don't want * to call it, only plain values are allowed. The value, if it exists, * is not checked. If the value is not a function, a TypeError happens * when it is called and that error replaces the original one. */ DUK_ASSERT_VALSTACK_SPACE(thr, 4); /* 3 entries actually needed below */ /* [ ... errval ] */ if (thr->builtins[DUK_BIDX_DUKTAPE] == NULL) { /* When creating built-ins, some of the built-ins may not be set * and we want to tolerate that when throwing errors. */ DUK_DD(DUK_DDPRINT("error occurred when DUK_BIDX_DUKTAPE is NULL, ignoring")); return; } tv_hnd = duk_hobject_find_existing_entry_tval_ptr(thr->heap, thr->builtins[DUK_BIDX_DUKTAPE], DUK_HTHREAD_GET_STRING(thr, stridx_cb)); if (tv_hnd == NULL) { DUK_DD(DUK_DDPRINT("error handler does not exist or is not a plain value: %!T", (duk_tval *) tv_hnd)); return; } DUK_DDD(DUK_DDDPRINT("error handler dump (callability not checked): %!T", (duk_tval *) tv_hnd)); duk_push_tval(ctx, tv_hnd); /* [ ... errval errhandler ] */ duk_insert(ctx, -2); /* -> [ ... errhandler errval ] */ duk_push_undefined(ctx); duk_insert(ctx, -2); /* -> [ ... errhandler undefined(= this) errval ] */ /* [ ... errhandler undefined errval ] */ /* * DUK_CALL_FLAG_IGNORE_RECLIMIT causes duk_handle_call() to ignore C * recursion depth limit (and won't increase it either). This is * dangerous, but useful because it allows the error handler to run * even if the original error is caused by C recursion depth limit. * * The heap level DUK_HEAP_FLAG_ERRHANDLER_RUNNING is set for the * duration of the error handler and cleared afterwards. This flag * prevents the error handler from running recursively. The flag is * heap level so that the flag properly controls even coroutines * launched by an error handler. Since the flag is heap level, it is * critical to restore it correctly. * * We ignore errors now: a success return and an error value both * replace the original error value. (This would be easy to change.) */ DUK_ASSERT(!DUK_HEAP_HAS_ERRHANDLER_RUNNING(thr->heap)); /* since no recursive error handler calls */ DUK_HEAP_SET_ERRHANDLER_RUNNING(thr->heap); call_flags = DUK_CALL_FLAG_IGNORE_RECLIMIT; /* ignore reclimit, not constructor */ rc = duk_handle_call_protected(thr, 1, /* num args */ call_flags); /* call_flags */ DUK_UNREF(rc); /* no need to check now: both success and error are OK */ DUK_ASSERT(DUK_HEAP_HAS_ERRHANDLER_RUNNING(thr->heap)); DUK_HEAP_CLEAR_ERRHANDLER_RUNNING(thr->heap); /* [ ... errval ] */ }
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_LOCAL void duk__parse_disjunction(duk_re_compiler_ctx *re_ctx, duk_bool_t expect_eof, duk__re_disjunction_info *out_atom_info) { duk_int32_t atom_start_offset = -1; /* negative -> no atom matched on previous round */ duk_int32_t atom_char_length = 0; /* negative -> complex atom */ duk_uint32_t atom_start_captures = re_ctx->captures; /* value of re_ctx->captures at start of atom */ duk_int32_t unpatched_disjunction_split = -1; duk_int32_t unpatched_disjunction_jump = -1; duk_uint32_t entry_offset = (duk_uint32_t) DUK__RE_BUFLEN(re_ctx); duk_int32_t res_charlen = 0; /* -1 if disjunction is complex, char length if simple */ duk__re_disjunction_info tmp_disj; DUK_ASSERT(out_atom_info != NULL); if (re_ctx->recursion_depth >= re_ctx->recursion_limit) { DUK_ERROR_RANGE(re_ctx->thr, DUK_STR_REGEXP_COMPILER_RECURSION_LIMIT); } re_ctx->recursion_depth++; #if 0 out_atom_info->start_captures = re_ctx->captures; #endif for (;;) { /* atom_char_length, atom_start_offset, atom_start_offset reflect the * atom matched on the previous loop. If a quantifier is encountered * on this loop, these are needed to handle the quantifier correctly. * new_atom_char_length etc are for the atom parsed on this round; * they're written to atom_char_length etc at the end of the round. */ duk_int32_t new_atom_char_length; /* char length of the atom parsed in this loop */ duk_int32_t new_atom_start_offset; /* bytecode start offset of the atom parsed in this loop * (allows quantifiers to copy the atom bytecode) */ duk_uint32_t new_atom_start_captures; /* re_ctx->captures at the start of the atom parsed in this loop */ duk_lexer_parse_re_token(&re_ctx->lex, &re_ctx->curr_token); DUK_DD(DUK_DDPRINT("re token: %ld (num=%ld, char=%c)", (long) re_ctx->curr_token.t, (long) re_ctx->curr_token.num, (re_ctx->curr_token.num >= 0x20 && re_ctx->curr_token.num <= 0x7e) ? (int) re_ctx->curr_token.num : (int) '?')); /* set by atom case clauses */ new_atom_start_offset = -1; new_atom_char_length = -1; new_atom_start_captures = re_ctx->captures; switch (re_ctx->curr_token.t) { case DUK_RETOK_DISJUNCTION: { /* * The handling here is a bit tricky. If a previous '|' has been processed, * we have a pending split1 and a pending jump (for a previous match). These * need to be back-patched carefully. See docs for a detailed example. */ /* patch pending jump and split */ if (unpatched_disjunction_jump >= 0) { duk_uint32_t offset; DUK_ASSERT(unpatched_disjunction_split >= 0); offset = unpatched_disjunction_jump; offset += duk__insert_jump_offset(re_ctx, offset, (duk_int32_t) (DUK__RE_BUFLEN(re_ctx) - offset)); /* offset is now target of the pending split (right after jump) */ duk__insert_jump_offset(re_ctx, unpatched_disjunction_split, offset - unpatched_disjunction_split); } /* add a new pending split to the beginning of the entire disjunction */ (void) duk__insert_u32(re_ctx, entry_offset, DUK_REOP_SPLIT1); /* prefer direct execution */ unpatched_disjunction_split = entry_offset + 1; /* +1 for opcode */ /* add a new pending match jump for latest finished alternative */ duk__append_u32(re_ctx, DUK_REOP_JUMP); unpatched_disjunction_jump = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); /* 'taint' result as complex */ res_charlen = -1; break; } case DUK_RETOK_QUANTIFIER: { if (atom_start_offset < 0) { DUK_ERROR_SYNTAX(re_ctx->thr, DUK_STR_INVALID_QUANTIFIER_NO_ATOM); } if (re_ctx->curr_token.qmin > re_ctx->curr_token.qmax) { DUK_ERROR_SYNTAX(re_ctx->thr, DUK_STR_INVALID_QUANTIFIER_VALUES); } if (atom_char_length >= 0) { /* * Simple atom * * If atom_char_length is zero, we'll have unbounded execution time for e.g. * /()*x/.exec('x'). We can't just skip the match because it might have some * side effects (for instance, if we allowed captures in simple atoms, the * capture needs to happen). The simple solution below is to force the * quantifier to match at most once, since the additional matches have no effect. * * With a simple atom there can be no capture groups, so no captures need * to be reset. */ duk_int32_t atom_code_length; duk_uint32_t offset; duk_uint32_t qmin, qmax; qmin = re_ctx->curr_token.qmin; qmax = re_ctx->curr_token.qmax; if (atom_char_length == 0) { /* qmin and qmax will be 0 or 1 */ if (qmin > 1) { qmin = 1; } if (qmax > 1) { qmax = 1; } } duk__append_u32(re_ctx, DUK_REOP_MATCH); /* complete 'sub atom' */ atom_code_length = (duk_int32_t) (DUK__RE_BUFLEN(re_ctx) - atom_start_offset); offset = atom_start_offset; if (re_ctx->curr_token.greedy) { offset += duk__insert_u32(re_ctx, offset, DUK_REOP_SQGREEDY); offset += duk__insert_u32(re_ctx, offset, qmin); offset += duk__insert_u32(re_ctx, offset, qmax); offset += duk__insert_u32(re_ctx, offset, atom_char_length); offset += duk__insert_jump_offset(re_ctx, offset, atom_code_length); } else { offset += duk__insert_u32(re_ctx, offset, DUK_REOP_SQMINIMAL); offset += duk__insert_u32(re_ctx, offset, qmin); offset += duk__insert_u32(re_ctx, offset, qmax); offset += duk__insert_jump_offset(re_ctx, offset, atom_code_length); } DUK_UNREF(offset); /* silence scan-build warning */ } else { /* * Complex atom * * The original code is used as a template, and removed at the end * (this differs from the handling of simple quantifiers). * * NOTE: there is no current solution for empty atoms in complex * quantifiers. This would need some sort of a 'progress' instruction. * * XXX: impose limit on maximum result size, i.e. atom_code_len * atom_copies? */ duk_int32_t atom_code_length; duk_uint32_t atom_copies; duk_uint32_t tmp_qmin, tmp_qmax; /* pre-check how many atom copies we're willing to make (atom_copies not needed below) */ atom_copies = (re_ctx->curr_token.qmax == DUK_RE_QUANTIFIER_INFINITE) ? re_ctx->curr_token.qmin : re_ctx->curr_token.qmax; if (atom_copies > DUK_RE_MAX_ATOM_COPIES) { DUK_ERROR_RANGE(re_ctx->thr, DUK_STR_QUANTIFIER_TOO_MANY_COPIES); } /* wipe the capture range made by the atom (if any) */ DUK_ASSERT(atom_start_captures <= re_ctx->captures); if (atom_start_captures != re_ctx->captures) { DUK_ASSERT(atom_start_captures < re_ctx->captures); DUK_DDD(DUK_DDDPRINT("must wipe ]atom_start_captures,re_ctx->captures]: ]%ld,%ld]", (long) atom_start_captures, (long) re_ctx->captures)); /* insert (DUK_REOP_WIPERANGE, start, count) in reverse order so the order ends up right */ duk__insert_u32(re_ctx, atom_start_offset, (re_ctx->captures - atom_start_captures) * 2); duk__insert_u32(re_ctx, atom_start_offset, (atom_start_captures + 1) * 2); duk__insert_u32(re_ctx, atom_start_offset, DUK_REOP_WIPERANGE); } else { DUK_DDD(DUK_DDDPRINT("no need to wipe captures: atom_start_captures == re_ctx->captures == %ld", (long) atom_start_captures)); } atom_code_length = (duk_int32_t) DUK__RE_BUFLEN(re_ctx) - atom_start_offset; /* insert the required matches (qmin) by copying the atom */ tmp_qmin = re_ctx->curr_token.qmin; tmp_qmax = re_ctx->curr_token.qmax; while (tmp_qmin > 0) { duk__append_slice(re_ctx, atom_start_offset, atom_code_length); tmp_qmin--; if (tmp_qmax != DUK_RE_QUANTIFIER_INFINITE) { tmp_qmax--; } } DUK_ASSERT(tmp_qmin == 0); /* insert code for matching the remainder - infinite or finite */ if (tmp_qmax == DUK_RE_QUANTIFIER_INFINITE) { /* reuse last emitted atom for remaining 'infinite' quantifier */ if (re_ctx->curr_token.qmin == 0) { /* Special case: original qmin was zero so there is nothing * to repeat. Emit an atom copy but jump over it here. */ duk__append_u32(re_ctx, DUK_REOP_JUMP); duk__append_jump_offset(re_ctx, atom_code_length); duk__append_slice(re_ctx, atom_start_offset, atom_code_length); } if (re_ctx->curr_token.greedy) { duk__append_u32(re_ctx, DUK_REOP_SPLIT2); /* prefer jump */ } else { duk__append_u32(re_ctx, DUK_REOP_SPLIT1); /* prefer direct */ } duk__append_jump_offset(re_ctx, -atom_code_length - 1); /* -1 for opcode */ } else { /* * The remaining matches are emitted as sequence of SPLITs and atom * copies; the SPLITs skip the remaining copies and match the sequel. * This sequence needs to be emitted starting from the last copy * because the SPLITs are variable length due to the variable length * skip offset. This causes a lot of memory copying now. * * Example structure (greedy, match maximum # atoms): * * SPLIT1 LSEQ * (atom) * SPLIT1 LSEQ ; <- the byte length of this instruction is needed * (atom) ; to encode the above SPLIT1 correctly * ... * LSEQ: */ duk_uint32_t offset = (duk_uint32_t) DUK__RE_BUFLEN(re_ctx); while (tmp_qmax > 0) { duk__insert_slice(re_ctx, offset, atom_start_offset, atom_code_length); if (re_ctx->curr_token.greedy) { duk__insert_u32(re_ctx, offset, DUK_REOP_SPLIT1); /* prefer direct */ } else { duk__insert_u32(re_ctx, offset, DUK_REOP_SPLIT2); /* prefer jump */ } duk__insert_jump_offset(re_ctx, offset + 1, /* +1 for opcode */ (duk_int32_t) (DUK__RE_BUFLEN(re_ctx) - (offset + 1))); tmp_qmax--; } } /* remove the original 'template' atom */ duk__remove_slice(re_ctx, atom_start_offset, atom_code_length); } /* 'taint' result as complex */ res_charlen = -1; break; } case DUK_RETOK_ASSERT_START: { duk__append_u32(re_ctx, DUK_REOP_ASSERT_START); break; } case DUK_RETOK_ASSERT_END: { duk__append_u32(re_ctx, DUK_REOP_ASSERT_END); break; } case DUK_RETOK_ASSERT_WORD_BOUNDARY: { duk__append_u32(re_ctx, DUK_REOP_ASSERT_WORD_BOUNDARY); break; } case DUK_RETOK_ASSERT_NOT_WORD_BOUNDARY: { duk__append_u32(re_ctx, DUK_REOP_ASSERT_NOT_WORD_BOUNDARY); break; } case DUK_RETOK_ASSERT_START_POS_LOOKAHEAD: case DUK_RETOK_ASSERT_START_NEG_LOOKAHEAD: { duk_uint32_t offset; duk_uint32_t opcode = (re_ctx->curr_token.t == DUK_RETOK_ASSERT_START_POS_LOOKAHEAD) ? DUK_REOP_LOOKPOS : DUK_REOP_LOOKNEG; offset = (duk_uint32_t) DUK__RE_BUFLEN(re_ctx); duk__parse_disjunction(re_ctx, 0, &tmp_disj); duk__append_u32(re_ctx, DUK_REOP_MATCH); (void) duk__insert_u32(re_ctx, offset, opcode); (void) duk__insert_jump_offset(re_ctx, offset + 1, /* +1 for opcode */ (duk_int32_t) (DUK__RE_BUFLEN(re_ctx) - (offset + 1))); /* 'taint' result as complex -- this is conservative, * as lookaheads do not backtrack. */ res_charlen = -1; break; } case DUK_RETOK_ATOM_PERIOD: { new_atom_char_length = 1; new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); duk__append_u32(re_ctx, DUK_REOP_PERIOD); break; } case DUK_RETOK_ATOM_CHAR: { /* Note: successive characters could be joined into string matches * but this is not trivial (consider e.g. '/xyz+/); see docs for * more discussion. */ duk_uint32_t ch; new_atom_char_length = 1; new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); duk__append_u32(re_ctx, DUK_REOP_CHAR); ch = re_ctx->curr_token.num; if (re_ctx->re_flags & DUK_RE_FLAG_IGNORE_CASE) { ch = duk_unicode_re_canonicalize_char(re_ctx->thr, ch); } duk__append_u32(re_ctx, ch); break; } case DUK_RETOK_ATOM_DIGIT: case DUK_RETOK_ATOM_NOT_DIGIT: { new_atom_char_length = 1; new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); duk__append_u32(re_ctx, (re_ctx->curr_token.t == DUK_RETOK_ATOM_DIGIT) ? DUK_REOP_RANGES : DUK_REOP_INVRANGES); duk__append_u32(re_ctx, sizeof(duk_unicode_re_ranges_digit) / (2 * sizeof(duk_uint16_t))); duk__append_u16_list(re_ctx, duk_unicode_re_ranges_digit, sizeof(duk_unicode_re_ranges_digit) / sizeof(duk_uint16_t)); break; } case DUK_RETOK_ATOM_WHITE: case DUK_RETOK_ATOM_NOT_WHITE: { new_atom_char_length = 1; new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); duk__append_u32(re_ctx, (re_ctx->curr_token.t == DUK_RETOK_ATOM_WHITE) ? DUK_REOP_RANGES : DUK_REOP_INVRANGES); duk__append_u32(re_ctx, sizeof(duk_unicode_re_ranges_white) / (2 * sizeof(duk_uint16_t))); duk__append_u16_list(re_ctx, duk_unicode_re_ranges_white, sizeof(duk_unicode_re_ranges_white) / sizeof(duk_uint16_t)); break; } case DUK_RETOK_ATOM_WORD_CHAR: case DUK_RETOK_ATOM_NOT_WORD_CHAR: { new_atom_char_length = 1; new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); duk__append_u32(re_ctx, (re_ctx->curr_token.t == DUK_RETOK_ATOM_WORD_CHAR) ? DUK_REOP_RANGES : DUK_REOP_INVRANGES); duk__append_u32(re_ctx, sizeof(duk_unicode_re_ranges_wordchar) / (2 * sizeof(duk_uint16_t))); duk__append_u16_list(re_ctx, duk_unicode_re_ranges_wordchar, sizeof(duk_unicode_re_ranges_wordchar) / sizeof(duk_uint16_t)); break; } case DUK_RETOK_ATOM_BACKREFERENCE: { duk_uint32_t backref = (duk_uint32_t) re_ctx->curr_token.num; if (backref > re_ctx->highest_backref) { re_ctx->highest_backref = backref; } new_atom_char_length = -1; /* mark as complex */ new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); duk__append_u32(re_ctx, DUK_REOP_BACKREFERENCE); duk__append_u32(re_ctx, backref); break; } case DUK_RETOK_ATOM_START_CAPTURE_GROUP: { duk_uint32_t cap; new_atom_char_length = -1; /* mark as complex (capture handling) */ new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); cap = ++re_ctx->captures; duk__append_u32(re_ctx, DUK_REOP_SAVE); duk__append_u32(re_ctx, cap * 2); duk__parse_disjunction(re_ctx, 0, &tmp_disj); /* retval (sub-atom char length) unused, tainted as complex above */ duk__append_u32(re_ctx, DUK_REOP_SAVE); duk__append_u32(re_ctx, cap * 2 + 1); break; } case DUK_RETOK_ATOM_START_NONCAPTURE_GROUP: { new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); duk__parse_disjunction(re_ctx, 0, &tmp_disj); new_atom_char_length = tmp_disj.charlen; break; } case DUK_RETOK_ATOM_START_CHARCLASS: case DUK_RETOK_ATOM_START_CHARCLASS_INVERTED: { /* * Range parsing is done with a special lexer function which calls * us for every range parsed. This is different from how rest of * the parsing works, but avoids a heavy, arbitrary size intermediate * value type to hold the ranges. * * Another complication is the handling of character ranges when * case insensitive matching is used (see docs for discussion). * The range handler callback given to the lexer takes care of this * as well. * * Note that duplicate ranges are not eliminated when parsing character * classes, so that canonicalization of * * [0-9a-fA-Fx-{] * * creates the result (note the duplicate ranges): * * [0-9A-FA-FX-Z{-{] * * where [x-{] is split as a result of canonicalization. The duplicate * ranges are not a semantics issue: they work correctly. */ duk_uint32_t offset; DUK_DD(DUK_DDPRINT("character class")); /* insert ranges instruction, range count patched in later */ new_atom_char_length = 1; new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); duk__append_u32(re_ctx, (re_ctx->curr_token.t == DUK_RETOK_ATOM_START_CHARCLASS) ? DUK_REOP_RANGES : DUK_REOP_INVRANGES); offset = (duk_uint32_t) DUK__RE_BUFLEN(re_ctx); /* patch in range count later */ /* parse ranges until character class ends */ re_ctx->nranges = 0; /* note: ctx-wide temporary */ duk_lexer_parse_re_ranges(&re_ctx->lex, duk__generate_ranges, (void *) re_ctx); /* insert range count */ duk__insert_u32(re_ctx, offset, re_ctx->nranges); break; } case DUK_RETOK_ATOM_END_GROUP: { if (expect_eof) { DUK_ERROR_SYNTAX(re_ctx->thr, DUK_STR_UNEXPECTED_CLOSING_PAREN); } goto done; } case DUK_RETOK_EOF: { if (!expect_eof) { DUK_ERROR_SYNTAX(re_ctx->thr, DUK_STR_UNEXPECTED_END_OF_PATTERN); } goto done; } default: { DUK_ERROR_SYNTAX(re_ctx->thr, DUK_STR_UNEXPECTED_REGEXP_TOKEN); } } /* a complex (new) atom taints the result */ if (new_atom_start_offset >= 0) { if (new_atom_char_length < 0) { res_charlen = -1; } else if (res_charlen >= 0) { /* only advance if not tainted */ res_charlen += new_atom_char_length; } } /* record previous atom info in case next token is a quantifier */ atom_start_offset = new_atom_start_offset; atom_char_length = new_atom_char_length; atom_start_captures = new_atom_start_captures; } done: /* finish up pending jump and split for last alternative */ if (unpatched_disjunction_jump >= 0) { duk_uint32_t offset; DUK_ASSERT(unpatched_disjunction_split >= 0); offset = unpatched_disjunction_jump; offset += duk__insert_jump_offset(re_ctx, offset, (duk_int32_t) (DUK__RE_BUFLEN(re_ctx) - offset)); /* offset is now target of the pending split (right after jump) */ duk__insert_jump_offset(re_ctx, unpatched_disjunction_split, offset - unpatched_disjunction_split); } #if 0 out_atom_info->end_captures = re_ctx->captures; #endif out_atom_info->charlen = res_charlen; DUK_DDD(DUK_DDDPRINT("parse disjunction finished: charlen=%ld", (long) out_atom_info->charlen)); re_ctx->recursion_depth--; }
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_DD(DUK_DDPRINT("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_SYNTAX(thr, 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_DD(DUK_DDPRINT("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__remove_matching_hstring_probe(duk_heap *heap, duk_uint16_t *entries16, duk_uint32_t size, duk_hstring *h) { #else DUK_LOCAL void duk__remove_matching_hstring_probe(duk_heap *heap, duk_hstring **entries, duk_uint32_t size, duk_hstring *h) { #endif duk_uint32_t i; duk_uint32_t step; duk_uint32_t hash; #if defined(DUK_USE_HEAPPTR16) duk_uint16_t null16 = heap->heapptr_null16; duk_uint16_t h16 = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h); #endif DUK_ASSERT(size > 0); hash = DUK_HSTRING_GET_HASH(h); i = DUK__HASH_INITIAL(hash, size); step = DUK__HASH_PROBE_STEP(hash); for (;;) { #if defined(DUK_USE_HEAPPTR16) duk_uint16_t e16 = entries16[i]; #else duk_hstring *e = entries[i]; #endif #if defined(DUK_USE_HEAPPTR16) if (e16 == null16) { #else if (!e) { #endif DUK_UNREACHABLE(); break; } #if defined(DUK_USE_HEAPPTR16) if (e16 == h16) { #else if (e == h) { #endif /* st_used remains the same, DELETED is counted as used */ DUK_DDD(DUK_DDDPRINT("free matching hit: %ld", (long) i)); #if defined(DUK_USE_HEAPPTR16) entries16[i] = heap->heapptr_deleted16; #else entries[i] = DUK__DELETED_MARKER(heap); #endif break; } DUK_DDD(DUK_DDDPRINT("free matching miss: %ld", (long) i)); i = (i + step) % size; /* looping should never happen */ DUK_ASSERT(i != DUK__HASH_INITIAL(hash, size)); } } DUK_LOCAL duk_bool_t duk__resize_strtab_raw_probe(duk_heap *heap, duk_uint32_t new_size) { #ifdef DUK_USE_MARK_AND_SWEEP duk_small_uint_t prev_mark_and_sweep_base_flags; #endif #ifdef DUK_USE_DEBUG duk_uint32_t old_used = heap->st_used; #endif duk_uint32_t old_size = heap->st_size; #if defined(DUK_USE_HEAPPTR16) duk_uint16_t *old_entries = heap->strtable16; duk_uint16_t *new_entries = NULL; #else duk_hstring **old_entries = heap->strtable; duk_hstring **new_entries = NULL; #endif duk_uint32_t new_used = 0; duk_uint32_t i; #ifdef DUK_USE_DEBUG DUK_UNREF(old_used); /* unused with some debug level combinations */ #endif #ifdef DUK_USE_DDDPRINT DUK_DDD(DUK_DDDPRINT("attempt to resize stringtable: %ld entries, %ld bytes, %ld used, %ld%% load -> %ld entries, %ld bytes, %ld used, %ld%% load", (long) old_size, (long) (sizeof(duk_hstring *) * old_size), (long) old_used, (long) (((double) old_used) / ((double) old_size) * 100.0), (long) new_size, (long) (sizeof(duk_hstring *) * new_size), (long) duk__count_used_probe(heap), (long) (((double) duk__count_used_probe(heap)) / ((double) new_size) * 100.0))); #endif DUK_ASSERT(new_size > (duk_uint32_t) duk__count_used_probe(heap)); /* required for rehash to succeed, equality not that useful */ DUK_ASSERT(old_entries); #ifdef DUK_USE_MARK_AND_SWEEP DUK_ASSERT((heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE) == 0); #endif /* * The attempt to allocate may cause a GC. Such a GC must not attempt to resize * the stringtable (though it can be swept); finalizer execution and object * compaction must also be postponed to avoid the pressure to add strings to the * string table. */ #ifdef DUK_USE_MARK_AND_SWEEP prev_mark_and_sweep_base_flags = heap->mark_and_sweep_base_flags; heap->mark_and_sweep_base_flags |= \ DUK_MS_FLAG_NO_STRINGTABLE_RESIZE | /* avoid recursive call here */ DUK_MS_FLAG_NO_FINALIZERS | /* avoid pressure to add/remove strings */ DUK_MS_FLAG_NO_OBJECT_COMPACTION; /* avoid array abandoning which interns strings */ #endif #if defined(DUK_USE_HEAPPTR16) new_entries = (duk_uint16_t *) DUK_ALLOC(heap, sizeof(duk_uint16_t) * new_size); #else new_entries = (duk_hstring **) DUK_ALLOC(heap, sizeof(duk_hstring *) * new_size); #endif #ifdef DUK_USE_MARK_AND_SWEEP heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags; #endif if (!new_entries) { goto resize_error; } #ifdef DUK_USE_EXPLICIT_NULL_INIT for (i = 0; i < new_size; i++) { #if defined(DUK_USE_HEAPPTR16) new_entries[i] = heap->heapptr_null16; #else new_entries[i] = NULL; #endif } #else #if defined(DUK_USE_HEAPPTR16) /* Relies on NULL encoding to zero. */ DUK_MEMZERO(new_entries, sizeof(duk_uint16_t) * new_size); #else DUK_MEMZERO(new_entries, sizeof(duk_hstring *) * new_size); #endif #endif /* Because new_size > duk__count_used_probe(heap), guaranteed to work */ for (i = 0; i < old_size; i++) { duk_hstring *e; #if defined(DUK_USE_HEAPPTR16) e = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, old_entries[i]); #else e = old_entries[i]; #endif if (e == NULL || e == DUK__DELETED_MARKER(heap)) { continue; } /* checking for DUK__DELETED_MARKER is not necessary here, but helper does it now */ duk__insert_hstring_probe(heap, new_entries, new_size, &new_used, e); } #ifdef DUK_USE_DDPRINT DUK_DD(DUK_DDPRINT("resized stringtable: %ld entries, %ld bytes, %ld used, %ld%% load -> %ld entries, %ld bytes, %ld used, %ld%% load", (long) old_size, (long) (sizeof(duk_hstring *) * old_size), (long) old_used, (long) (((double) old_used) / ((double) old_size) * 100.0), (long) new_size, (long) (sizeof(duk_hstring *) * new_size), (long) new_used, (long) (((double) new_used) / ((double) new_size) * 100.0))); #endif #if defined(DUK_USE_HEAPPTR16) DUK_FREE(heap, heap->strtable16); heap->strtable16 = new_entries; #else DUK_FREE(heap, heap->strtable); heap->strtable = new_entries; #endif heap->st_size = new_size; heap->st_used = new_used; /* may be less, since DELETED entries are NULLed by rehash */ return 0; /* OK */ resize_error: DUK_FREE(heap, new_entries); return 1; /* FAIL */ } DUK_LOCAL duk_bool_t duk__resize_strtab_probe(duk_heap *heap) { duk_uint32_t new_size; duk_bool_t ret; new_size = (duk_uint32_t) duk__count_used_probe(heap); if (new_size >= 0x80000000UL) { new_size = DUK_STRTAB_HIGHEST_32BIT_PRIME; } else { new_size = duk_util_get_hash_prime(DUK_STRTAB_GROW_ST_SIZE(new_size)); new_size = duk_util_get_hash_prime(new_size); } DUK_ASSERT(new_size > 0); /* rehash even if old and new sizes are the same to get rid of * DELETED entries. */ ret = duk__resize_strtab_raw_probe(heap, new_size); return ret; }
DUK_INTERNAL void duk_heap_dump_strtab(duk_heap *heap) { duk_strtab_entry *e; duk_small_uint_t i; duk_size_t j, n, used; #if defined(DUK_USE_HEAPPTR16) duk_uint16_t *lst; duk_uint16_t null16 = heap->heapptr_null16; #else duk_hstring **lst; #endif DUK_ASSERT(heap != NULL); for (i = 0; i < DUK_STRTAB_CHAIN_SIZE; i++) { e = heap->strtable + i; if (e->listlen == 0) { #if defined(DUK_USE_HEAPPTR16) DUK_DD(DUK_DDPRINT("[%03d] -> plain %d", (int) i, (int) (e->u.str16 != null16 ? 1 : 0))); #else DUK_DD(DUK_DDPRINT("[%03d] -> plain %d", (int) i, (int) (e->u.str ? 1 : 0))); #endif } else { used = 0; #if defined(DUK_USE_HEAPPTR16) lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16); #else lst = e->u.strlist; #endif DUK_ASSERT(lst != NULL); for (j = 0, n = e->listlen; j < n; j++) { #if defined(DUK_USE_HEAPPTR16) if (lst[j] != null16) { #else if (lst[j] != NULL) { #endif used++; } } DUK_DD(DUK_DDPRINT("[%03d] -> array %d/%d", (int) i, (int) used, (int) e->listlen)); } } } #endif /* DUK_USE_DEBUG */ #endif /* DUK_USE_STRTAB_CHAIN */ /* * String table algorithm: closed hashing with a probe sequence * * This is the default algorithm and works fine for environments with * minimal memory constraints. */ #if defined(DUK_USE_STRTAB_PROBE) /* Count actually used (non-NULL, non-DELETED) entries. */ DUK_LOCAL duk_int_t duk__count_used_probe(duk_heap *heap) { duk_int_t res = 0; duk_uint_fast32_t i, n; #if defined(DUK_USE_HEAPPTR16) duk_uint16_t null16 = heap->heapptr_null16; duk_uint16_t deleted16 = heap->heapptr_deleted16; #endif n = (duk_uint_fast32_t) heap->st_size; for (i = 0; i < n; i++) { #if defined(DUK_USE_HEAPPTR16) if (heap->strtable16[i] != null16 && heap->strtable16[i] != deleted16) { #else if (heap->strtable[i] != NULL && heap->strtable[i] != DUK__DELETED_MARKER(heap)) { #endif res++; } } return res; } #if defined(DUK_USE_HEAPPTR16) DUK_LOCAL void duk__insert_hstring_probe(duk_heap *heap, duk_uint16_t *entries16, duk_uint32_t size, duk_uint32_t *p_used, duk_hstring *h) { #else DUK_LOCAL void duk__insert_hstring_probe(duk_heap *heap, duk_hstring **entries, duk_uint32_t size, duk_uint32_t *p_used, duk_hstring *h) { #endif duk_uint32_t i; duk_uint32_t step; #if defined(DUK_USE_HEAPPTR16) duk_uint16_t null16 = heap->heapptr_null16; duk_uint16_t deleted16 = heap->heapptr_deleted16; #endif DUK_ASSERT(size > 0); i = DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(h), size); step = DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(h)); for (;;) { #if defined(DUK_USE_HEAPPTR16) duk_uint16_t e16 = entries16[i]; #else duk_hstring *e = entries[i]; #endif #if defined(DUK_USE_HEAPPTR16) /* XXX: could check for e16 == 0 because NULL is guaranteed to * encode to zero. */ if (e16 == null16) { #else if (e == NULL) { #endif DUK_DDD(DUK_DDDPRINT("insert hit (null): %ld", (long) i)); #if defined(DUK_USE_HEAPPTR16) entries16[i] = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h); #else entries[i] = h; #endif (*p_used)++; break; #if defined(DUK_USE_HEAPPTR16) } else if (e16 == deleted16) { #else } else if (e == DUK__DELETED_MARKER(heap)) { #endif /* st_used remains the same, DELETED is counted as used */ DUK_DDD(DUK_DDDPRINT("insert hit (deleted): %ld", (long) i)); #if defined(DUK_USE_HEAPPTR16) entries16[i] = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h); #else entries[i] = h; #endif break; } DUK_DDD(DUK_DDDPRINT("insert miss: %ld", (long) i)); i = (i + step) % size; /* looping should never happen */ DUK_ASSERT(i != DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(h), size)); } } #if defined(DUK_USE_HEAPPTR16) DUK_LOCAL duk_hstring *duk__find_matching_string_probe(duk_heap *heap, duk_uint16_t *entries16, duk_uint32_t size, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) { #else DUK_LOCAL duk_hstring *duk__find_matching_string_probe(duk_heap *heap, duk_hstring **entries, duk_uint32_t size, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) { #endif duk_uint32_t i; duk_uint32_t step; DUK_ASSERT(size > 0); i = DUK__HASH_INITIAL(strhash, size); step = DUK__HASH_PROBE_STEP(strhash); for (;;) { duk_hstring *e; #if defined(DUK_USE_HEAPPTR16) e = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, entries16[i]); #else e = entries[i]; #endif if (!e) { return NULL; } if (e != DUK__DELETED_MARKER(heap) && DUK_HSTRING_GET_BYTELEN(e) == blen) { if (DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(e), (size_t) blen) == 0) { DUK_DDD(DUK_DDDPRINT("find matching hit: %ld (step %ld, size %ld)", (long) i, (long) step, (long) size)); return e; } } DUK_DDD(DUK_DDDPRINT("find matching miss: %ld (step %ld, size %ld)", (long) i, (long) step, (long) size)); i = (i + step) % size; /* looping should never happen */ DUK_ASSERT(i != DUK__HASH_INITIAL(strhash, size)); } DUK_UNREACHABLE(); }