]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/jaegertracing/opentelemetry-cpp/third_party/prometheus-cpp/3rdparty/civetweb/src/third_party/duktape-1.8.0/src-separate/duk_heap_stringtable.c
update ceph source to reef 18.1.2
[ceph.git] / ceph / src / jaegertracing / opentelemetry-cpp / third_party / prometheus-cpp / 3rdparty / civetweb / src / third_party / duktape-1.8.0 / src-separate / duk_heap_stringtable.c
diff --git a/ceph/src/jaegertracing/opentelemetry-cpp/third_party/prometheus-cpp/3rdparty/civetweb/src/third_party/duktape-1.8.0/src-separate/duk_heap_stringtable.c b/ceph/src/jaegertracing/opentelemetry-cpp/third_party/prometheus-cpp/3rdparty/civetweb/src/third_party/duktape-1.8.0/src-separate/duk_heap_stringtable.c
new file mode 100644 (file)
index 0000000..ad0a4fc
--- /dev/null
@@ -0,0 +1,1181 @@
+/*
+ *  Heap stringtable handling, string interning.
+ */
+
+#include "duk_internal.h"
+
+#if defined(DUK_USE_STRTAB_PROBE)
+#define DUK__HASH_INITIAL(hash,h_size)        DUK_STRTAB_HASH_INITIAL((hash),(h_size))
+#define DUK__HASH_PROBE_STEP(hash)            DUK_STRTAB_HASH_PROBE_STEP((hash))
+#define DUK__DELETED_MARKER(heap)             DUK_STRTAB_DELETED_MARKER((heap))
+#endif
+
+#if defined(DUK_USE_MARK_AND_SWEEP)
+#define DUK__PREVENT_MS_SIDE_EFFECTS(heap) do { \
+               (heap)->mark_and_sweep_base_flags |= \
+                       DUK_MS_FLAG_NO_STRINGTABLE_RESIZE |  /* avoid recursive string table call */ \
+                       DUK_MS_FLAG_NO_FINALIZERS |          /* avoid pressure to add/remove strings, invalidation of call data argument, etc. */ \
+                       DUK_MS_FLAG_NO_OBJECT_COMPACTION;    /* avoid array abandoning which interns strings */ \
+       } while (0)
+#endif
+
+/*
+ *  Create a hstring and insert into the heap.  The created object
+ *  is directly garbage collectable with reference count zero.
+ *
+ *  The caller must place the interned string into the stringtable
+ *  immediately (without chance of a longjmp); otherwise the string
+ *  is lost.
+ */
+
+DUK_LOCAL
+duk_hstring *duk__alloc_init_hstring(duk_heap *heap,
+                                     const duk_uint8_t *str,
+                                     duk_uint32_t blen,
+                                     duk_uint32_t strhash,
+                                     const duk_uint8_t *extdata) {
+       duk_hstring *res = NULL;
+       duk_uint8_t *data;
+       duk_size_t alloc_size;
+       duk_uarridx_t dummy;
+       duk_uint32_t clen;
+
+#if defined(DUK_USE_STRLEN16)
+       /* If blen <= 0xffffUL, clen is also guaranteed to be <= 0xffffUL. */
+       if (blen > 0xffffUL) {
+               DUK_D(DUK_DPRINT("16-bit string blen/clen active and blen over 16 bits, reject intern"));
+               return NULL;
+       }
+#endif
+
+       if (extdata) {
+               alloc_size = (duk_size_t) sizeof(duk_hstring_external);
+               res = (duk_hstring *) DUK_ALLOC(heap, alloc_size);
+               if (!res) {
+                       goto alloc_error;
+               }
+               DUK_MEMZERO(res, sizeof(duk_hstring_external));
+#if defined(DUK_USE_EXPLICIT_NULL_INIT)
+               DUK_HEAPHDR_STRING_INIT_NULLS(&res->hdr);
+#endif
+               DUK_HEAPHDR_SET_TYPE_AND_FLAGS(&res->hdr, DUK_HTYPE_STRING, DUK_HSTRING_FLAG_EXTDATA);
+
+               ((duk_hstring_external *) res)->extdata = extdata;
+       } else {
+               /* NUL terminate for convenient C access */
+               alloc_size = (duk_size_t) (sizeof(duk_hstring) + blen + 1);
+               res = (duk_hstring *) DUK_ALLOC(heap, alloc_size);
+               if (!res) {
+                       goto alloc_error;
+               }
+               DUK_MEMZERO(res, sizeof(duk_hstring));
+#if defined(DUK_USE_EXPLICIT_NULL_INIT)
+               DUK_HEAPHDR_STRING_INIT_NULLS(&res->hdr);
+#endif
+               DUK_HEAPHDR_SET_TYPE_AND_FLAGS(&res->hdr, DUK_HTYPE_STRING, 0);
+
+               data = (duk_uint8_t *) (res + 1);
+               DUK_MEMCPY(data, str, blen);
+               data[blen] = (duk_uint8_t) 0;
+       }
+
+       DUK_ASSERT(!DUK_HSTRING_HAS_ARRIDX(res));
+       if (duk_js_to_arrayindex_raw_string(str, blen, &dummy)) {
+               DUK_HSTRING_SET_ARRIDX(res);
+       }
+
+       /* All strings beginning with 0xff are treated as "internal",
+        * even strings interned by the user.  This allows user code to
+        * create internal properties too, and makes behavior consistent
+        * in case user code happens to use a string also used by Duktape
+        * (such as string has already been interned and has the 'internal'
+        * flag set).
+        */
+       DUK_ASSERT(!DUK_HSTRING_HAS_INTERNAL(res));
+       if (blen > 0 && str[0] == (duk_uint8_t) 0xff) {
+               DUK_HSTRING_SET_INTERNAL(res);
+       }
+
+       DUK_HSTRING_SET_HASH(res, strhash);
+       DUK_HSTRING_SET_BYTELEN(res, blen);
+
+       clen = (duk_uint32_t) duk_unicode_unvalidated_utf8_length(str, (duk_size_t) blen);
+       DUK_ASSERT(clen <= blen);
+#if defined(DUK_USE_HSTRING_CLEN)
+       DUK_HSTRING_SET_CHARLEN(res, clen);
+#endif
+
+       /* Using an explicit 'ASCII' flag has larger footprint (one call site
+        * only) but is quite useful for the case when there's no explicit
+        * 'clen' in duk_hstring.
+        */
+       DUK_ASSERT(!DUK_HSTRING_HAS_ASCII(res));
+       if (clen == blen) {
+               DUK_HSTRING_SET_ASCII(res);
+       }
+
+       DUK_DDD(DUK_DDDPRINT("interned string, hash=0x%08lx, blen=%ld, clen=%ld, has_arridx=%ld, has_extdata=%ld",
+                            (unsigned long) DUK_HSTRING_GET_HASH(res),
+                            (long) DUK_HSTRING_GET_BYTELEN(res),
+                            (long) DUK_HSTRING_GET_CHARLEN(res),
+                            (long) (DUK_HSTRING_HAS_ARRIDX(res) ? 1 : 0),
+                            (long) (DUK_HSTRING_HAS_EXTDATA(res) ? 1 : 0)));
+
+       return res;
+
+ alloc_error:
+       DUK_FREE(heap, res);
+       return NULL;
+}
+
+/*
+ *  String table algorithm: fixed size string table with array chaining
+ *
+ *  The top level string table has a fixed size, with each slot holding
+ *  either NULL, string pointer, or pointer to a separately allocated
+ *  string pointer list.
+ *
+ *  This is good for low memory environments using a pool allocator: the
+ *  top level allocation has a fixed size and the pointer lists have quite
+ *  small allocation size, which further matches the typical pool sizes
+ *  needed by objects, strings, property tables, etc.
+ */
+
+#if defined(DUK_USE_STRTAB_CHAIN)
+
+#if defined(DUK_USE_HEAPPTR16)
+DUK_LOCAL duk_bool_t duk__insert_hstring_chain(duk_heap *heap, duk_hstring *h) {
+       duk_small_uint_t slotidx;
+       duk_strtab_entry *e;
+       duk_uint16_t *lst;
+       duk_uint16_t *new_lst;
+       duk_size_t i, n;
+       duk_uint16_t null16 = heap->heapptr_null16;
+       duk_uint16_t h16 = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h);
+
+       DUK_ASSERT(heap != NULL);
+       DUK_ASSERT(h != NULL);
+
+       slotidx = DUK_HSTRING_GET_HASH(h) % DUK_STRTAB_CHAIN_SIZE;
+       DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
+
+       e = heap->strtable + slotidx;
+       if (e->listlen == 0) {
+               if (e->u.str16 == null16) {
+                       e->u.str16 = h16;
+               } else {
+                       /* Now two entries in the same slot, alloc list */
+                       lst = (duk_uint16_t *) DUK_ALLOC(heap, sizeof(duk_uint16_t) * 2);
+                       if (lst == NULL) {
+                               return 1;  /* fail */
+                       }
+                       lst[0] = e->u.str16;
+                       lst[1] = h16;
+                       e->u.strlist16 = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) lst);
+                       e->listlen = 2;
+               }
+       } else {
+               DUK_ASSERT(e->u.strlist16 != null16);
+               lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16);
+               DUK_ASSERT(lst != NULL);
+               for (i = 0, n = e->listlen; i < n; i++) {
+                       if (lst[i] == null16) {
+                               lst[i] = h16;
+                               return 0;
+                       }
+               }
+
+               if (e->listlen + 1 == 0) {
+                       /* Overflow, relevant mainly when listlen is 16 bits. */
+                       return 1;  /* fail */
+               }
+
+               new_lst = (duk_uint16_t *) DUK_REALLOC(heap, lst, sizeof(duk_uint16_t) * (e->listlen + 1));
+               if (new_lst == NULL) {
+                       return 1;  /* fail */
+               }
+               new_lst[e->listlen++] = h16;
+               e->u.strlist16 = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) new_lst);
+       }
+       return 0;
+}
+#else  /* DUK_USE_HEAPPTR16 */
+DUK_LOCAL duk_bool_t duk__insert_hstring_chain(duk_heap *heap, duk_hstring *h) {
+       duk_small_uint_t slotidx;
+       duk_strtab_entry *e;
+       duk_hstring **lst;
+       duk_hstring **new_lst;
+       duk_size_t i, n;
+
+       DUK_ASSERT(heap != NULL);
+       DUK_ASSERT(h != NULL);
+
+       slotidx = DUK_HSTRING_GET_HASH(h) % DUK_STRTAB_CHAIN_SIZE;
+       DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
+
+       e = heap->strtable + slotidx;
+       if (e->listlen == 0) {
+               if (e->u.str == NULL) {
+                       e->u.str = h;
+               } else {
+                       /* Now two entries in the same slot, alloc list */
+                       lst = (duk_hstring **) DUK_ALLOC(heap, sizeof(duk_hstring *) * 2);
+                       if (lst == NULL) {
+                               return 1;  /* fail */
+                       }
+                       lst[0] = e->u.str;
+                       lst[1] = h;
+                       e->u.strlist = lst;
+                       e->listlen = 2;
+               }
+       } else {
+               DUK_ASSERT(e->u.strlist != NULL);
+               lst = e->u.strlist;
+               for (i = 0, n = e->listlen; i < n; i++) {
+                       if (lst[i] == NULL) {
+                               lst[i] = h;
+                               return 0;
+                       }
+               }
+
+               if (e->listlen + 1 == 0) {
+                       /* Overflow, relevant mainly when listlen is 16 bits. */
+                       return 1;  /* fail */
+               }
+
+               new_lst = (duk_hstring **) DUK_REALLOC(heap, e->u.strlist, sizeof(duk_hstring *) * (e->listlen + 1));
+               if (new_lst == NULL) {
+                       return 1;  /* fail */
+               }
+               new_lst[e->listlen++] = h;
+               e->u.strlist = new_lst;
+       }
+       return 0;
+}
+#endif  /* DUK_USE_HEAPPTR16 */
+
+#if defined(DUK_USE_HEAPPTR16)
+DUK_LOCAL duk_hstring *duk__find_matching_string_chain(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) {
+       duk_small_uint_t slotidx;
+       duk_strtab_entry *e;
+       duk_uint16_t *lst;
+       duk_size_t i, n;
+       duk_uint16_t null16 = heap->heapptr_null16;
+
+       DUK_ASSERT(heap != NULL);
+
+       slotidx = strhash % DUK_STRTAB_CHAIN_SIZE;
+       DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
+
+       e = heap->strtable + slotidx;
+       if (e->listlen == 0) {
+               if (e->u.str16 != null16) {
+                       duk_hstring *h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.str16);
+                       DUK_ASSERT(h != NULL);
+                       if (DUK_HSTRING_GET_BYTELEN(h) == blen &&
+                           DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(h), (size_t) blen) == 0) {
+                               return h;
+                       }
+               }
+       } else {
+               DUK_ASSERT(e->u.strlist16 != null16);
+               lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16);
+               DUK_ASSERT(lst != NULL);
+               for (i = 0, n = e->listlen; i < n; i++) {
+                       if (lst[i] != null16) {
+                               duk_hstring *h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, lst[i]);
+                               DUK_ASSERT(h != NULL);
+                               if (DUK_HSTRING_GET_BYTELEN(h) == blen &&
+                                   DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(h), (size_t) blen) == 0) {
+                                       return h;
+                               }
+                       }
+               }
+       }
+
+       return NULL;
+}
+#else  /* DUK_USE_HEAPPTR16 */
+DUK_LOCAL duk_hstring *duk__find_matching_string_chain(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) {
+       duk_small_uint_t slotidx;
+       duk_strtab_entry *e;
+       duk_hstring **lst;
+       duk_size_t i, n;
+
+       DUK_ASSERT(heap != NULL);
+
+       slotidx = strhash % DUK_STRTAB_CHAIN_SIZE;
+       DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
+
+       e = heap->strtable + slotidx;
+       if (e->listlen == 0) {
+               if (e->u.str != NULL &&
+                  DUK_HSTRING_GET_BYTELEN(e->u.str) == blen &&
+                  DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(e->u.str), (size_t) blen) == 0) {
+                       return e->u.str;
+               }
+       } else {
+               DUK_ASSERT(e->u.strlist != NULL);
+               lst = e->u.strlist;
+               for (i = 0, n = e->listlen; i < n; i++) {
+                       if (lst[i] != NULL &&
+                          DUK_HSTRING_GET_BYTELEN(lst[i]) == blen &&
+                          DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(lst[i]), (size_t) blen) == 0) {
+                               return lst[i];
+                       }
+               }
+       }
+
+       return NULL;
+}
+#endif  /* DUK_USE_HEAPPTR16 */
+
+#if defined(DUK_USE_HEAPPTR16)
+DUK_LOCAL void duk__remove_matching_hstring_chain(duk_heap *heap, duk_hstring *h) {
+       duk_small_uint_t slotidx;
+       duk_strtab_entry *e;
+       duk_uint16_t *lst;
+       duk_size_t i, n;
+       duk_uint16_t h16;
+       duk_uint16_t null16 = heap->heapptr_null16;
+
+       DUK_ASSERT(heap != NULL);
+       DUK_ASSERT(h != NULL);
+
+       slotidx = DUK_HSTRING_GET_HASH(h) % DUK_STRTAB_CHAIN_SIZE;
+       DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
+
+       DUK_ASSERT(h != NULL);
+       h16 = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h);
+
+       e = heap->strtable + slotidx;
+       if (e->listlen == 0) {
+               if (e->u.str16 == h16) {
+                       e->u.str16 = null16;
+                       return;
+               }
+       } else {
+               DUK_ASSERT(e->u.strlist16 != null16);
+               lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16);
+               DUK_ASSERT(lst != NULL);
+               for (i = 0, n = e->listlen; i < n; i++) {
+                       if (lst[i] == h16) {
+                               lst[i] = null16;
+                               return;
+                       }
+               }
+       }
+
+       DUK_D(DUK_DPRINT("failed to find string that should be in stringtable"));
+       DUK_UNREACHABLE();
+       return;
+}
+#else  /* DUK_USE_HEAPPTR16 */
+DUK_LOCAL void duk__remove_matching_hstring_chain(duk_heap *heap, duk_hstring *h) {
+       duk_small_uint_t slotidx;
+       duk_strtab_entry *e;
+       duk_hstring **lst;
+       duk_size_t i, n;
+
+       DUK_ASSERT(heap != NULL);
+       DUK_ASSERT(h != NULL);
+
+       slotidx = DUK_HSTRING_GET_HASH(h) % DUK_STRTAB_CHAIN_SIZE;
+       DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
+
+       e = heap->strtable + slotidx;
+       if (e->listlen == 0) {
+               DUK_ASSERT(h != NULL);
+               if (e->u.str == h) {
+                       e->u.str = NULL;
+                       return;
+               }
+       } else {
+               DUK_ASSERT(e->u.strlist != NULL);
+               lst = e->u.strlist;
+               for (i = 0, n = e->listlen; i < n; i++) {
+                       DUK_ASSERT(h != NULL);
+                       if (lst[i] == h) {
+                               lst[i] = NULL;
+                               return;
+                       }
+               }
+       }
+
+       DUK_D(DUK_DPRINT("failed to find string that should be in stringtable"));
+       DUK_UNREACHABLE();
+       return;
+}
+#endif  /* DUK_USE_HEAPPTR16 */
+
+#if defined(DUK_USE_DEBUG)
+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();
+}
+
+#if defined(DUK_USE_HEAPPTR16)
+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) {
+#if defined(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;
+
+#if defined(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);
+
+       /*
+        *  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.  Call site must prevent these.
+        */
+
+#if defined(DUK_USE_MARK_AND_SWEEP)
+       DUK_ASSERT(heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE);
+       DUK_ASSERT(heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_FINALIZERS);
+       DUK_ASSERT(heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_OBJECT_COMPACTION);
+#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
+
+       if (!new_entries) {
+               goto resize_error;
+       }
+
+#if defined(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_LOCAL duk_bool_t duk__recheck_strtab_size_probe(duk_heap *heap, duk_uint32_t new_used) {
+       duk_uint32_t new_free;
+       duk_uint32_t tmp1;
+       duk_uint32_t tmp2;
+
+       DUK_ASSERT(new_used <= heap->st_size);  /* grow by at most one */
+       new_free = heap->st_size - new_used;    /* unsigned intentionally */
+
+       /* new_free / size <= 1 / DIV  <=>  new_free <= size / DIV */
+       /* new_used / size <= 1 / DIV  <=>  new_used <= size / DIV */
+
+       tmp1 = heap->st_size / DUK_STRTAB_MIN_FREE_DIVISOR;
+       tmp2 = heap->st_size / DUK_STRTAB_MIN_USED_DIVISOR;
+
+       if (new_free <= tmp1 || new_used <= tmp2) {
+               /* load factor too low or high, count actually used entries and resize */
+               return duk__resize_strtab_probe(heap);
+       } else {
+               return 0;  /* OK */
+       }
+}
+
+#if defined(DUK_USE_DEBUG)
+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
+       DUK_UNREF(h);
+
+       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));
+       }
+}
+#endif  /* DUK_USE_DEBUG */
+
+#endif  /* DUK_USE_STRTAB_PROBE */
+
+/*
+ *  Raw intern and lookup
+ */
+
+DUK_LOCAL duk_hstring *duk__do_intern(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) {
+       duk_hstring *res;
+       const duk_uint8_t *extdata;
+#if defined(DUK_USE_MARK_AND_SWEEP)
+       duk_small_uint_t prev_mark_and_sweep_base_flags;
+#endif
+
+       /* Prevent any side effects on the string table and the caller provided
+        * str/blen arguments while interning is in progress.  For example, if
+        * the caller provided str/blen from a dynamic buffer, a finalizer might
+        * resize that dynamic buffer, invalidating the call arguments.
+        */
+#if defined(DUK_USE_MARK_AND_SWEEP)
+       DUK_ASSERT((heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE) == 0);
+       prev_mark_and_sweep_base_flags = heap->mark_and_sweep_base_flags;
+       DUK__PREVENT_MS_SIDE_EFFECTS(heap);
+#endif
+
+#if defined(DUK_USE_STRTAB_PROBE)
+       if (duk__recheck_strtab_size_probe(heap, heap->st_used + 1)) {
+               goto failed;
+       }
+#endif
+
+       /* For manual testing only. */
+#if 0
+       {
+               duk_size_t i;
+               DUK_PRINTF("INTERN: \"");
+               for (i = 0; i < blen; i++) {
+                       duk_uint8_t x = str[i];
+                       if (x >= 0x20 && x <= 0x7e && x != '"' && x != '\\') {
+                               DUK_PRINTF("%c", (int) x);  /* char: use int cast */
+                       } else {
+                               DUK_PRINTF("\\x%02lx", (long) x);
+                       }
+               }
+               DUK_PRINTF("\"\n");
+       }
+#endif
+
+#if defined(DUK_USE_HSTRING_EXTDATA) && defined(DUK_USE_EXTSTR_INTERN_CHECK)
+       extdata = (const duk_uint8_t *) DUK_USE_EXTSTR_INTERN_CHECK(heap->heap_udata, (void *) DUK_LOSE_CONST(str), (duk_size_t) blen);
+#else
+       extdata = (const duk_uint8_t *) NULL;
+#endif
+       res = duk__alloc_init_hstring(heap, str, blen, strhash, extdata);
+       if (!res) {
+               goto failed;
+       }
+
+#if defined(DUK_USE_STRTAB_CHAIN)
+       if (duk__insert_hstring_chain(heap, res)) {
+               /* failed */
+               DUK_FREE(heap, res);
+               goto failed;
+       }
+#elif defined(DUK_USE_STRTAB_PROBE)
+       /* guaranteed to succeed */
+       duk__insert_hstring_probe(heap,
+#if defined(DUK_USE_HEAPPTR16)
+                                 heap->strtable16,
+#else
+                                 heap->strtable,
+#endif
+                                 heap->st_size,
+                                 &heap->st_used,
+                                 res);
+#else
+#error internal error, invalid strtab options
+#endif
+
+       /* Note: hstring is in heap but has refcount zero and is not strongly reachable.
+        * Caller should increase refcount and make the hstring reachable before any
+        * operations which require allocation (and possible gc).
+        */
+
+ done:
+#if defined(DUK_USE_MARK_AND_SWEEP)
+       heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
+#endif
+       return res;
+
+ failed:
+       res = NULL;
+       goto done;
+}
+
+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_LOSE_CONST(duk_rom_strings[i]);
+                       if (blen == DUK_HSTRING_GET_BYTELEN(romstr) &&
+                           DUK_MEMCMP((const void *) str, (const 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;
+}
+
+/*
+ *  Exposed calls
+ */
+
+#if 0  /*unused*/
+DUK_INTERNAL duk_hstring *duk_heap_string_lookup(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen) {
+       duk_uint32_t strhash;  /* dummy */
+       return duk__do_lookup(heap, str, blen, &strhash);
+}
+#endif
+
+DUK_INTERNAL duk_hstring *duk_heap_string_intern(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen) {
+       duk_hstring *res;
+       duk_uint32_t strhash;
+
+       /* caller is responsible for ensuring this */
+       DUK_ASSERT(blen <= DUK_HSTRING_MAX_BYTELEN);
+
+       res = duk__do_lookup(heap, str, blen, &strhash);
+       if (res) {
+               return res;
+       }
+
+       res = duk__do_intern(heap, str, blen, strhash);
+       return res;  /* may be NULL */
+}
+
+DUK_INTERNAL duk_hstring *duk_heap_string_intern_checked(duk_hthread *thr, const duk_uint8_t *str, duk_uint32_t blen) {
+       duk_hstring *res = duk_heap_string_intern(thr->heap, str, blen);
+       if (!res) {
+               DUK_ERROR_ALLOC_DEFMSG(thr);
+       }
+       return res;
+}
+
+#if 0  /*unused*/
+DUK_INTERNAL duk_hstring *duk_heap_string_lookup_u32(duk_heap *heap, duk_uint32_t val) {
+       char buf[DUK_STRTAB_U32_MAX_STRLEN+1];
+       DUK_SNPRINTF(buf, sizeof(buf), "%lu", (unsigned long) val);
+       buf[sizeof(buf) - 1] = (char) 0;
+       DUK_ASSERT(DUK_STRLEN(buf) <= DUK_UINT32_MAX);  /* formatted result limited */
+       return duk_heap_string_lookup(heap, (const duk_uint8_t *) buf, (duk_uint32_t) DUK_STRLEN(buf));
+}
+#endif
+
+DUK_INTERNAL duk_hstring *duk_heap_string_intern_u32(duk_heap *heap, duk_uint32_t val) {
+       char buf[DUK_STRTAB_U32_MAX_STRLEN+1];
+       DUK_SNPRINTF(buf, sizeof(buf), "%lu", (unsigned long) val);
+       buf[sizeof(buf) - 1] = (char) 0;
+       DUK_ASSERT(DUK_STRLEN(buf) <= DUK_UINT32_MAX);  /* formatted result limited */
+       return duk_heap_string_intern(heap, (const duk_uint8_t *) buf, (duk_uint32_t) DUK_STRLEN(buf));
+}
+
+DUK_INTERNAL duk_hstring *duk_heap_string_intern_u32_checked(duk_hthread *thr, duk_uint32_t val) {
+       duk_hstring *res = duk_heap_string_intern_u32(thr->heap, val);
+       if (!res) {
+               DUK_ERROR_ALLOC_DEFMSG(thr);
+       }
+       return res;
+}
+
+/* find and remove string from stringtable; caller must free the string itself */
+#if defined(DUK_USE_REFERENCE_COUNTING)
+DUK_INTERNAL void duk_heap_string_remove(duk_heap *heap, duk_hstring *h) {
+       DUK_DDD(DUK_DDDPRINT("remove string from stringtable: %!O", (duk_heaphdr *) h));
+
+#if defined(DUK_USE_STRTAB_CHAIN)
+       duk__remove_matching_hstring_chain(heap, h);
+#elif defined(DUK_USE_STRTAB_PROBE)
+       duk__remove_matching_hstring_probe(heap,
+#if defined(DUK_USE_HEAPPTR16)
+                                          heap->strtable16,
+#else
+                                          heap->strtable,
+#endif
+                                          heap->st_size,
+                                          h);
+#else
+#error internal error, invalid strtab options
+#endif
+}
+#endif
+
+#if defined(DUK_USE_MARK_AND_SWEEP) && defined(DUK_USE_MS_STRINGTABLE_RESIZE)
+DUK_INTERNAL void duk_heap_force_strtab_resize(duk_heap *heap) {
+       duk_small_uint_t prev_mark_and_sweep_base_flags;
+       /* Force a resize so that DELETED entries are eliminated.
+        * Another option would be duk__recheck_strtab_size_probe();
+        * but since that happens on every intern anyway, this whole
+        * check can now be disabled.
+        */
+
+       DUK_ASSERT((heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE) == 0);
+       prev_mark_and_sweep_base_flags = heap->mark_and_sweep_base_flags;
+       DUK__PREVENT_MS_SIDE_EFFECTS(heap);
+
+#if defined(DUK_USE_STRTAB_CHAIN)
+       DUK_UNREF(heap);
+#elif defined(DUK_USE_STRTAB_PROBE)
+       (void) duk__resize_strtab_probe(heap);
+#endif
+
+       heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
+}
+#endif
+
+#if defined(DUK_USE_STRTAB_CHAIN)
+DUK_INTERNAL void duk_heap_free_strtab(duk_heap *heap) {
+       /* Free strings in the stringtable and any allocations needed
+        * by the stringtable itself.
+        */
+       duk_uint_fast32_t i, j;
+       duk_strtab_entry *e;
+#if defined(DUK_USE_HEAPPTR16)
+       duk_uint16_t *lst;
+       duk_uint16_t null16 = heap->heapptr_null16;
+#else
+       duk_hstring **lst;
+#endif
+       duk_hstring *h;
+
+       for (i = 0; i < DUK_STRTAB_CHAIN_SIZE; i++) {
+               e = heap->strtable + i;
+               if (e->listlen > 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; j < e->listlen; j++) {
+#if defined(DUK_USE_HEAPPTR16)
+                               h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, lst[j]);
+                               lst[j] = null16;
+#else
+                               h = lst[j];
+                               lst[j] = NULL;
+#endif
+                               /* strings may have inner refs (extdata) in some cases */
+                               if (h != NULL) {
+                                       duk_free_hstring_inner(heap, h);
+                                       DUK_FREE(heap, h);
+                               }
+                       }
+#if defined(DUK_USE_HEAPPTR16)
+                       e->u.strlist16 = null16;
+#else
+                       e->u.strlist = NULL;
+#endif
+                       DUK_FREE(heap, lst);
+               } else {
+#if defined(DUK_USE_HEAPPTR16)
+                       h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.str16);
+                       e->u.str16 = null16;
+#else
+                       h = e->u.str;
+                       e->u.str = NULL;
+#endif
+                       if (h != NULL) {
+                               duk_free_hstring_inner(heap, h);
+                               DUK_FREE(heap, h);
+                       }
+               }
+               e->listlen = 0;
+       }
+}
+#endif  /* DUK_USE_STRTAB_CHAIN */
+
+#if defined(DUK_USE_STRTAB_PROBE)
+DUK_INTERNAL void duk_heap_free_strtab(duk_heap *heap) {
+       duk_uint_fast32_t i;
+       duk_hstring *h;
+
+#if defined(DUK_USE_HEAPPTR16)
+       if (heap->strtable16) {
+#else
+       if (heap->strtable) {
+#endif
+               for (i = 0; i < (duk_uint_fast32_t) heap->st_size; i++) {
+#if defined(DUK_USE_HEAPPTR16)
+                       h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]);
+#else
+                       h = heap->strtable[i];
+#endif
+                       if (h == NULL || h == DUK_STRTAB_DELETED_MARKER(heap)) {
+                               continue;
+                       }
+                       DUK_ASSERT(h != NULL);
+
+                       /* strings may have inner refs (extdata) in some cases */
+                       duk_free_hstring_inner(heap, h);
+                       DUK_FREE(heap, h);
+#if 0  /* not strictly necessary */
+                       heap->strtable[i] = NULL;
+#endif
+               }
+#if defined(DUK_USE_HEAPPTR16)
+               DUK_FREE(heap, heap->strtable16);
+#else
+               DUK_FREE(heap, heap->strtable);
+#endif
+#if 0  /* not strictly necessary */
+               heap->strtable = NULL;
+#endif
+       }
+}
+#endif  /* DUK_USE_STRTAB_PROBE */
+
+/* Undefine local defines */
+#undef DUK__HASH_INITIAL
+#undef DUK__HASH_PROBE_STEP
+#undef DUK__DELETED_MARKER