changeset 29:1dfa92299033

duktape: upgrade to 1.6.0
author David Demelier <markand@malikania.fr>
date Wed, 21 Dec 2016 11:41:11 +0100
parents c374a84a1fa9
children 0e9f184646f0
files VERSION.duktape.txt duktape/duk_config.h duktape/duktape.cpp duktape/duktape.h
diffstat 4 files changed, 438 insertions(+), 174 deletions(-) [+]
line wrap: on
line diff
--- a/VERSION.duktape.txt	Thu Dec 01 13:09:43 2016 +0100
+++ b/VERSION.duktape.txt	Wed Dec 21 11:41:11 2016 +0100
@@ -1,1 +1,1 @@
-1.5.1
+1.6.0
--- a/duktape/duk_config.h	Thu Dec 01 13:09:43 2016 +0100
+++ b/duktape/duk_config.h	Wed Dec 21 11:41:11 2016 +0100
@@ -1,8 +1,8 @@
 /*
  *  duk_config.h configuration header generated by genconfig.py.
  *
- *  Git commit: 2cc76e9ff1f64869e1146ad7317d8cbe33bbd27e
- *  Git describe: v1.5.1
+ *  Git commit: 17e3d86cf8b4788bd0d37658f833ab440ce43a1c
+ *  Git describe: v1.6.0
  *  Git branch: HEAD
  *
  *  Supported platforms:
--- a/duktape/duktape.cpp	Thu Dec 01 13:09:43 2016 +0100
+++ b/duktape/duktape.cpp	Wed Dec 21 11:41:11 2016 +0100
@@ -1,8 +1,8 @@
 /*
- *  Single source autogenerated distributable for Duktape 1.5.0.
- *
- *  Git commit 83d557704ee63f68ab40b6fcb00995c9b3d6777c (v1.5.0).
- *  Git branch master.
+ *  Single source autogenerated distributable for Duktape 1.6.0.
+ *
+ *  Git commit 17e3d86cf8b4788bd0d37658f833ab440ce43a1c (v1.6.0).
+ *  Git branch HEAD.
  *
  *  See Duktape AUTHORS.rst and LICENSE.txt for copyright and
  *  licensing information.
@@ -1629,9 +1629,9 @@
 #endif  /* !DUK_SINGLE_FILE */
 #if defined(DUK_USE_BUILTIN_INITJS)
 #if !defined(DUK_SINGLE_FILE)
-DUK_INTERNAL_DECL const duk_uint8_t duk_initjs_data[187];
+DUK_INTERNAL_DECL const duk_uint8_t duk_initjs_data[204];
 #endif  /* !DUK_SINGLE_FILE */
-#define DUK_BUILTIN_INITJS_DATA_LENGTH                                187
+#define DUK_BUILTIN_INITJS_DATA_LENGTH                                204
 #endif  /* DUK_USE_BUILTIN_INITJS */
 #define DUK_BIDX_GLOBAL                                               0
 #define DUK_BIDX_GLOBAL_ENV                                           1
@@ -7889,9 +7889,11 @@
 #define DUK_ERROR_UNSUPPORTED(thr,msg) do { \
 		DUK_ERROR((thr), DUK_ERR_UNSUPPORTED_ERROR, (msg)); \
 	} while (0)
+#if !defined(DUK_USE_BYTECODE_DUMP_SUPPORT)
 #define DUK_ERROR_UNSUPPORTED_DEFMSG(thr) do { \
 		duk_err_unsupported_defmsg((thr), DUK_FILE_MACRO, (duk_int_t) DUK_LINE_MACRO); \
 	} while (0)
+#endif
 #define DUK_ERROR_INTERNAL(thr,msg) do { \
 		duk_err_internal((thr), DUK_FILE_MACRO, (duk_int_t) DUK_LINE_MACRO, (msg)); \
 	} while (0)
@@ -8009,7 +8011,9 @@
 DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_api(duk_hthread *thr, const char *filename, duk_int_t linenumber, const char *message));
 DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_range(duk_hthread *thr, const char *filename, duk_int_t linenumber, const char *message));
 DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_unimplemented_defmsg(duk_hthread *thr, const char *filename, duk_int_t linenumber));
+#if !defined(DUK_USE_BYTECODE_DUMP_SUPPORT)
 DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_unsupported_defmsg(duk_hthread *thr, const char *filename, duk_int_t linenumber));
+#endif
 DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_internal_defmsg(duk_hthread *thr, const char *filename, duk_int_t linenumber));
 DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_internal(duk_hthread *thr, const char *filename, duk_int_t linenumber, const char *message));
 DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_alloc(duk_hthread *thr, const char *filename, duk_int_t linenumber, const char *message));
@@ -9215,7 +9219,7 @@
 	duk_bi_typedarray_set,
 };
 #if defined(DUK_USE_BUILTIN_INITJS)
-DUK_INTERNAL const duk_uint8_t duk_initjs_data[187] = {
+DUK_INTERNAL const duk_uint8_t duk_initjs_data[204] = {
 40,102,117,110,99,116,105,111,110,40,100,44,97,41,123,102,117,110,99,116,
 105,111,110,32,98,40,97,44,98,44,99,41,123,79,98,106,101,99,116,46,100,101,
 102,105,110,101,80,114,111,112,101,114,116,121,40,97,44,98,44,123,118,97,
@@ -9223,8 +9227,9 @@
 109,101,114,97,98,108,101,58,33,49,44,99,111,110,102,105,103,117,114,97,98,
 108,101,58,33,48,125,41,125,98,40,97,46,76,111,103,103,101,114,44,34,99,
 108,111,103,34,44,110,101,119,32,97,46,76,111,103,103,101,114,40,34,67,34,
-41,41,59,98,40,97,44,34,109,111,100,76,111,97,100,101,100,34,44,123,125,41,
-125,41,40,116,104,105,115,44,68,117,107,116,97,112,101,41,59,10,0,
+41,41,59,98,40,97,44,34,109,111,100,76,111,97,100,101,100,34,44,79,98,106,
+101,99,116,46,99,114,101,97,116,101,40,110,117,108,108,41,41,125,41,40,116,
+104,105,115,44,68,117,107,116,97,112,101,41,59,10,0,
 };
 #endif  /* DUK_USE_BUILTIN_INITJS */
 #if defined(DUK_USE_DOUBLE_LE)
@@ -9348,7 +9353,7 @@
 88,119,100,223,181,68,16,94,91,250,238,200,160,80,0,152,31,61,59,148,10,0,
 21,4,231,199,151,69,2,128,5,192,250,97,220,160,80,0,192,127,255,128,20,23,
 134,30,92,242,164,34,19,207,167,45,59,179,233,205,229,37,129,127,255,0,0,
-191,255,128,0,63,255,197,131,246,203,203,158,157,251,160,0,0,0,0,0,65,98,
+191,255,128,0,63,255,197,131,246,203,203,158,157,251,160,0,0,0,0,0,90,98,
 32,3,166,156,30,53,32,249,165,131,76,223,159,62,94,70,172,114,16,176,144,
 60,56,250,19,18,5,159,25,89,32,121,180,238,42,30,129,229,221,140,164,122,7,
 147,46,50,129,232,62,61,251,120,97,199,208,156,129,83,127,0,50,250,69,3,
@@ -9528,7 +9533,7 @@
 88,119,100,223,181,68,16,94,91,250,238,200,160,80,0,152,31,61,59,148,10,0,
 21,4,231,199,151,69,2,128,5,192,250,97,220,160,80,0,192,127,255,128,20,23,
 134,30,92,242,164,34,19,207,167,45,59,179,233,205,229,37,129,127,255,0,0,
-191,255,128,0,63,255,197,131,246,203,203,158,157,251,160,32,98,65,0,0,0,0,
+191,255,128,0,63,255,197,131,246,203,203,158,157,251,160,32,98,90,0,0,0,0,
 0,3,166,156,30,53,32,249,165,131,76,223,159,62,94,70,172,114,16,176,144,60,
 56,250,19,18,5,159,25,89,32,121,180,238,42,30,129,229,221,140,164,122,7,
 147,46,50,129,232,62,61,251,120,97,199,208,156,129,83,127,0,50,250,69,3,
@@ -9708,7 +9713,7 @@
 88,119,100,223,181,68,16,94,91,250,238,200,160,80,0,152,31,61,59,148,10,0,
 21,4,231,199,151,69,2,128,5,192,250,97,220,160,80,0,192,127,255,128,20,23,
 134,30,92,242,164,34,19,207,167,45,59,179,233,205,229,37,129,127,255,0,0,
-191,255,128,0,63,255,197,131,246,203,203,158,157,251,160,0,65,98,32,0,0,0,
+191,255,128,0,63,255,197,131,246,203,203,158,157,251,160,0,90,98,32,0,0,0,
 0,3,166,156,30,53,32,249,165,131,76,223,159,62,94,70,172,114,16,176,144,60,
 56,250,19,18,5,159,25,89,32,121,180,238,42,30,129,229,221,140,164,122,7,
 147,46,50,129,232,62,61,251,120,97,199,208,156,129,83,127,0,50,250,69,3,
@@ -9832,9 +9837,11 @@
 DUK_INTERNAL void duk_err_unimplemented_defmsg(duk_hthread *thr, const char *filename, duk_int_t linenumber) {
 	DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_UNIMPLEMENTED_ERROR, DUK_STR_UNIMPLEMENTED);
 }
+#if !defined(DUK_USE_BYTECODE_DUMP_SUPPORT)
 DUK_INTERNAL void duk_err_unsupported_defmsg(duk_hthread *thr, const char *filename, duk_int_t linenumber) {
 	DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_UNSUPPORTED_ERROR, DUK_STR_UNSUPPORTED);
 }
+#endif
 DUK_INTERNAL void duk_err_internal_defmsg(duk_hthread *thr, const char *filename, duk_int_t linenumber) {
 	DUK_ERROR_RAW(thr, filename, linenumber, DUK_ERR_INTERNAL_ERROR, DUK_STR_INTERNAL_ERROR);
 }
@@ -12667,21 +12674,41 @@
 
 	duk_dup(ctx, idx_cons);
 	for (;;) {
-		cons = duk_get_hobject(ctx, -1);
-		if (cons == NULL || !DUK_HOBJECT_HAS_CONSTRUCTABLE(cons)) {
-			/* Checking constructability from anything else than the
-			 * initial constructor is not strictly necessary, but a
-			 * nice sanity check.
-			 */
+		duk_tval *tv;
+		tv = DUK_GET_TVAL_NEGIDX(ctx, -1);
+		DUK_ASSERT(tv != NULL);
+
+		if (DUK_TVAL_IS_OBJECT(tv)) {
+			cons = DUK_TVAL_GET_OBJECT(tv);
+			DUK_ASSERT(cons != NULL);
+			if (!DUK_HOBJECT_IS_CALLABLE(cons) || !DUK_HOBJECT_HAS_CONSTRUCTABLE(cons)) {
+				/* Checking callability of the immediate target
+				 * is important, same for constructability.
+				 * Checking it for functions down the bound
+				 * function chain is not strictly necessary
+				 * because .bind() should normally reject them.
+				 * But it's good to check anyway because it's
+				 * technically possible to edit the bound function
+				 * chain via internal keys.
+				 */
+				goto not_constructable;
+			}
+			if (!DUK_HOBJECT_HAS_BOUND(cons)) {
+				break;
+			}
+		} else if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
+			/* Lightfuncs cannot be bound. */
+			break;
+		} else {
+			/* Anything else is not constructable. */
 			goto not_constructable;
 		}
-		if (!DUK_HOBJECT_HAS_BOUND(cons)) {
-			break;
-		}
 		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET);  /* -> [... cons target] */
 		duk_remove(ctx, -2);                                  /* -> [... target] */
 	}
-	DUK_ASSERT(cons != NULL && !DUK_HOBJECT_HAS_BOUND(cons));
+	DUK_ASSERT(duk_is_callable(ctx, -1));
+	DUK_ASSERT(duk_is_lightfunc(ctx, -1) ||
+	           (duk_get_hobject(ctx, -1) != NULL && !DUK_HOBJECT_HAS_BOUND(duk_get_hobject(ctx, -1))));
 
 	/* [... constructor arg1 ... argN final_cons] */
 
@@ -14019,6 +14046,15 @@
 
 /* include removed: duk_internal.h */
 
+typedef struct duk_internal_thread_state duk_internal_thread_state;
+
+struct duk_internal_thread_state {
+	duk_ljstate lj;
+	duk_bool_t handling_error;
+	duk_hthread *curr_thread;
+	duk_int_t call_recursion_depth;
+};
+
 DUK_EXTERNAL
 duk_context *duk_create_heap(duk_alloc_function alloc_func,
                              duk_realloc_function realloc_func,
@@ -14084,6 +14120,57 @@
 	duk_heap_free(heap);
 }
 
+DUK_EXTERNAL void duk_suspend(duk_context *ctx, duk_thread_state *state) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	duk_internal_thread_state *snapshot = (duk_internal_thread_state *) (void *) state;
+	duk_heap *heap;
+	duk_ljstate *lj;
+
+	DUK_ASSERT_CTX_VALID(ctx);
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(state != NULL);  /* unvalidated */
+
+	heap = thr->heap;
+	lj = &heap->lj;
+
+	duk_push_tval(ctx, &lj->value1);
+	duk_push_tval(ctx, &lj->value2);
+
+	DUK_MEMCPY((void *) &snapshot->lj, (const void *) lj, sizeof(duk_ljstate));
+	snapshot->handling_error = heap->handling_error;
+	snapshot->curr_thread = heap->curr_thread;
+	snapshot->call_recursion_depth = heap->call_recursion_depth;
+
+	lj->jmpbuf_ptr = NULL;
+	lj->type = DUK_LJ_TYPE_UNKNOWN;
+	DUK_TVAL_SET_UNDEFINED(&lj->value1);
+	DUK_TVAL_SET_UNDEFINED(&lj->value2);
+	heap->handling_error = 0;
+	heap->curr_thread = NULL;
+	heap->call_recursion_depth = 0;
+}
+
+DUK_EXTERNAL void duk_resume(duk_context *ctx, const duk_thread_state *state) {
+	duk_hthread *thr = (duk_hthread *) ctx;
+	const duk_internal_thread_state *snapshot = (const duk_internal_thread_state *) (const void *) state;
+	duk_heap *heap;
+
+	DUK_ASSERT_CTX_VALID(ctx);
+	DUK_ASSERT(thr != NULL);
+	DUK_ASSERT(thr->heap != NULL);
+	DUK_ASSERT(state != NULL);  /* unvalidated */
+
+	heap = thr->heap;
+
+	DUK_MEMCPY((void *) &heap->lj, (const void *) &snapshot->lj, sizeof(duk_ljstate));
+	heap->handling_error = snapshot->handling_error;
+	heap->curr_thread = snapshot->curr_thread;
+	heap->call_recursion_depth = snapshot->call_recursion_depth;
+
+	duk_pop_2(ctx);
+}
+
 /* XXX: better place for this */
 DUK_EXTERNAL void duk_set_global_object(duk_context *ctx) {
 	duk_hthread *thr = (duk_hthread *) ctx;
@@ -14389,7 +14476,7 @@
 	return rc;
 }
 
-DUK_EXTERNAL duk_bool_t duk_put_prop(duk_context *ctx, duk_idx_t obj_index) {
+DUK_LOCAL duk_bool_t duk__put_prop_shared(duk_context *ctx, duk_idx_t obj_idx, duk_idx_t idx_key) {
 	duk_hthread *thr = (duk_hthread *) ctx;
 	duk_tval *tv_obj;
 	duk_tval *tv_key;
@@ -14397,16 +14484,19 @@
 	duk_small_int_t throw_flag;
 	duk_bool_t rc;
 
-	DUK_ASSERT_CTX_VALID(ctx);
-
 	/* Note: copying tv_obj and tv_key to locals to shield against a valstack
 	 * resize is not necessary for a property put right now (putprop protects
 	 * against it internally).
 	 */
 
-	tv_obj = duk_require_tval(ctx, obj_index);
-	tv_key = duk_require_tval(ctx, -2);
-	tv_val = duk_require_tval(ctx, -1);
+	/* Key and value indices are either (-2, -1) or (-1, -2).  Given idx_key,
+	 * idx_val is always (idx_key ^ 0x01).
+	 */
+	DUK_ASSERT((idx_key == -2 && (idx_key ^ 1) == -1) ||
+	           (idx_key == -1 && (idx_key ^ 1) == -2));
+	tv_obj = duk_require_tval(ctx, obj_idx);
+	tv_key = duk_require_tval(ctx, idx_key);
+	tv_val = duk_require_tval(ctx, idx_key ^ 1);
 	throw_flag = duk_is_strict_call(ctx);
 
 	rc = duk_hobject_putprop(thr, tv_obj, tv_key, tv_val, throw_flag);
@@ -14416,26 +14506,33 @@
 	return rc;  /* 1 if property found, 0 otherwise */
 }
 
-DUK_EXTERNAL duk_bool_t duk_put_prop_string(duk_context *ctx, duk_idx_t obj_index, const char *key) {
+DUK_EXTERNAL duk_bool_t duk_put_prop(duk_context *ctx, duk_idx_t obj_idx) {
+	DUK_ASSERT_CTX_VALID(ctx);
+	return duk__put_prop_shared(ctx, obj_idx, -2);
+}
+
+DUK_EXTERNAL duk_bool_t duk_put_prop_string(duk_context *ctx, duk_idx_t obj_idx, const char *key) {
 	DUK_ASSERT_CTX_VALID(ctx);
 	DUK_ASSERT(key != NULL);
 
-	obj_index = duk_require_normalize_index(ctx, obj_index);
-	duk_push_string(ctx, key);
-	duk_swap_top(ctx, -2);  /* [val key] -> [key val] */
-	return duk_put_prop(ctx, obj_index);
-}
-
-DUK_EXTERNAL duk_bool_t duk_put_prop_index(duk_context *ctx, duk_idx_t obj_index, duk_uarridx_t arr_index) {
-	DUK_ASSERT_CTX_VALID(ctx);
-
-	obj_index = duk_require_normalize_index(ctx, obj_index);
-	duk_push_uarridx(ctx, arr_index);
-	duk_swap_top(ctx, -2);  /* [val key] -> [key val] */
-	return duk_put_prop(ctx, obj_index);
-}
-
-DUK_INTERNAL duk_bool_t duk_put_prop_stridx(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx) {
+	/* Careful here and with other duk_put_prop_xxx() helpers: the
+	 * target object and the property value may be in the same value
+	 * stack slot (unusual, but still conceptually clear).
+	 */
+	obj_idx = duk_normalize_index(ctx, obj_idx);
+	(void) duk_push_string(ctx, key);
+	return duk__put_prop_shared(ctx, obj_idx, -1);
+}
+
+DUK_EXTERNAL duk_bool_t duk_put_prop_index(duk_context *ctx, duk_idx_t obj_idx, duk_uarridx_t arr_idx) {
+	DUK_ASSERT_CTX_VALID(ctx);
+
+	obj_idx = duk_require_normalize_index(ctx, obj_idx);
+	duk_push_uarridx(ctx, arr_idx);
+	return duk__put_prop_shared(ctx, obj_idx, -1);
+}
+
+DUK_INTERNAL duk_bool_t duk_put_prop_stridx(duk_context *ctx, duk_idx_t obj_idx, duk_small_int_t stridx) {
 	duk_hthread *thr = (duk_hthread *) ctx;
 
 	DUK_ASSERT_CTX_VALID(ctx);
@@ -14443,10 +14540,9 @@
 	DUK_ASSERT(stridx < DUK_HEAP_NUM_STRINGS);
 	DUK_UNREF(thr);
 
-	obj_index = duk_require_normalize_index(ctx, obj_index);
+	obj_idx = duk_require_normalize_index(ctx, obj_idx);
 	duk_push_hstring(ctx, DUK_HTHREAD_GET_STRING(thr, stridx));
-	duk_swap_top(ctx, -2);  /* [val key] -> [key val] */
-	return duk_put_prop(ctx, obj_index);
+	return duk__put_prop_shared(ctx, obj_idx, -1);
 }
 
 DUK_EXTERNAL duk_bool_t duk_del_prop(duk_context *ctx, duk_idx_t obj_index) {
@@ -26592,6 +26688,10 @@
 			hdr_size = (duk_small_uint_t) sizeof(duk_hnativefunction);
 		} else if (DUK_HOBJECT_IS_THREAD(h_obj)) {
 			hdr_size = (duk_small_uint_t) sizeof(duk_hthread);
+#if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
+		} else if (DUK_HOBJECT_IS_BUFFEROBJECT(h_obj)) {
+			hdr_size = (duk_small_uint_t) sizeof(duk_hbufferobject);
+#endif
 		} else {
 			hdr_size = (duk_small_uint_t) sizeof(duk_hobject);
 		}
@@ -30792,7 +30892,7 @@
 DUK_LOCAL duk_bool_t duk__enc_value(duk_json_enc_ctx *js_ctx, duk_idx_t idx_holder) {
 	duk_context *ctx = (duk_context *) js_ctx->thr;
 	duk_hthread *thr = (duk_hthread *) ctx;
-	duk_hobject *h;
+	duk_hobject *h_tmp;
 	duk_tval *tv;
 	duk_tval *tv_holder;
 	duk_tval *tv_key;
@@ -30814,12 +30914,12 @@
 
 	DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(ctx, -1)));
 
-	h = duk_get_hobject_or_lfunc_coerce(ctx, -1);
-	if (h != NULL) {
+	h_tmp = duk_get_hobject_or_lfunc_coerce(ctx, -1);
+	if (h_tmp != NULL) {
 		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_TO_JSON);
-		h = duk_get_hobject_or_lfunc_coerce(ctx, -1);  /* toJSON() can also be a lightfunc */
-
-		if (h != NULL && DUK_HOBJECT_IS_CALLABLE(h)) {
+		h_tmp = duk_get_hobject_or_lfunc_coerce(ctx, -1);  /* toJSON() can also be a lightfunc */
+
+		if (h_tmp != NULL && DUK_HOBJECT_IS_CALLABLE(h_tmp)) {
 			DUK_DDD(DUK_DDDPRINT("value is object, has callable toJSON() -> call it"));
 			/* XXX: duk_dup_unvalidated(ctx, -2) etc. */
 			duk_dup(ctx, -2);         /* -> [ ... key val toJSON val ] */
@@ -30852,6 +30952,8 @@
 
 	tv = DUK_GET_TVAL_NEGIDX(ctx, -1);
 	if (DUK_TVAL_IS_OBJECT(tv)) {
+		duk_hobject *h;
+
 		h = DUK_TVAL_GET_OBJECT(tv);
 		DUK_ASSERT(h != NULL);
 
@@ -31357,12 +31459,17 @@
 					 * standard JSON (and no JX/JC support here now).
 					 */
 					DUK_D(DUK_DPRINT("gap in array, no conflicting inherited property, remain on fast path"));
+#if defined(DUK_USE_JX)
+					DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_undefined);
+#else
 					DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL);
+#endif
 				} else {
 					if (duk__json_stringify_fast_value(js_ctx, tv_val) == 0) {
 						DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL);
 					}
 				}
+
 				DUK__EMIT_1(js_ctx, DUK_ASC_COMMA);
 				emitted = 1;
 			}
@@ -31695,6 +31802,7 @@
 	 * combinations properly.
 	 */
 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
+	js_ctx->stridx_custom_undefined = DUK_STRIDX_LC_NULL;  /* standard JSON; array gaps */
 #if defined(DUK_USE_JX)
 	if (flags & DUK_JSON_FLAG_EXT_CUSTOM) {
 		js_ctx->stridx_custom_undefined = DUK_STRIDX_LC_UNDEFINED;
@@ -33897,7 +34005,7 @@
 		cp = (duk_ucodepoint_t) duk_to_uint32(ctx, i);
 		DUK_BW_WRITE_ENSURE_XUTF8(thr, bw, cp);
 #else
-		cp = (duk_ucodepoint_t) duk_to_uint32(ctx, i);
+		cp = (duk_ucodepoint_t) duk_to_uint16(ctx, i);
 		DUK_BW_WRITE_ENSURE_CESU8(thr, bw, cp);
 #endif
 	}
@@ -38604,7 +38712,7 @@
 
 	for (i = 0; i < heap->st_size; i++) {
 #if defined(DUK_USE_HEAPPTR16)
-		h = DUK_USE_HEAPPTR_DEC16(heap->strtable16[i]);
+		h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]);
 #else
 		h = heap->strtable[i];
 #endif
@@ -42002,18 +42110,23 @@
 
 		duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HCOMPILEDFUNCTION_GET_DATA(heap, f));
 
-		tv = DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(heap, f);
-		tv_end = DUK_HCOMPILEDFUNCTION_GET_CONSTS_END(heap, f);
-		while (tv < tv_end) {
-			duk__mark_tval(heap, tv);
-			tv++;
-		}
-
-		fn = DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(heap, f);
-		fn_end = DUK_HCOMPILEDFUNCTION_GET_FUNCS_END(heap, f);
-		while (fn < fn_end) {
-			duk__mark_heaphdr(heap, (duk_heaphdr *) *fn);
-			fn++;
+		if (DUK_HCOMPILEDFUNCTION_GET_DATA(heap, f) != NULL) {
+			tv = DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(heap, f);
+			tv_end = DUK_HCOMPILEDFUNCTION_GET_CONSTS_END(heap, f);
+			while (tv < tv_end) {
+				duk__mark_tval(heap, tv);
+				tv++;
+			}
+
+			fn = DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(heap, f);
+			fn_end = DUK_HCOMPILEDFUNCTION_GET_FUNCS_END(heap, f);
+			while (fn < fn_end) {
+				duk__mark_heaphdr(heap, (duk_heaphdr *) *fn);
+				fn++;
+			}
+		} else {
+			/* May happen in some out-of-memory corner cases. */
+			DUK_D(DUK_DPRINT("duk_hcompiledfunction 'data' is NULL, skipping marking"));
 		}
 	} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
 		duk_hnativefunction *f = (duk_hnativefunction *) h;
@@ -42570,7 +42683,7 @@
 
 	for (i = 0; i < heap->st_size; i++) {
 #if defined(DUK_USE_HEAPPTR16)
-		h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->strtable16[i]);
+		h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]);
 #else
 		h = heap->strtable[i];
 #endif
@@ -43228,6 +43341,9 @@
 
 	/* XXX: stringtable emergency compaction? */
 
+	/* XXX: remove this feature entirely? it would only matter for
+	 * emergency GC.  Disable for lowest memory builds.
+	 */
 #if defined(DUK_USE_MS_STRINGTABLE_RESIZE)
 	if (!(flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE)) {
 		DUK_DD(DUK_DDPRINT("resize stringtable: %p", (void *) heap));
@@ -43890,20 +44006,23 @@
 		duk_tval *tv, *tv_end;
 		duk_hobject **funcs, **funcs_end;
 
-		DUK_ASSERT(DUK_HCOMPILEDFUNCTION_GET_DATA(thr->heap, f) != NULL);  /* compiled functions must be created 'atomically' */
-
-		tv = DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(thr->heap, f);
-		tv_end = DUK_HCOMPILEDFUNCTION_GET_CONSTS_END(thr->heap, f);
-		while (tv < tv_end) {
-			duk_tval_decref(thr, tv);
-			tv++;
-		}
-
-		funcs = DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(thr->heap, f);
-		funcs_end = DUK_HCOMPILEDFUNCTION_GET_FUNCS_END(thr->heap, f);
-		while (funcs < funcs_end) {
-			duk_heaphdr_decref(thr, (duk_heaphdr *) *funcs);
-			funcs++;
+		if (DUK_HCOMPILEDFUNCTION_GET_DATA(thr->heap, f) != NULL) {
+			tv = DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(thr->heap, f);
+			tv_end = DUK_HCOMPILEDFUNCTION_GET_CONSTS_END(thr->heap, f);
+			while (tv < tv_end) {
+				duk_tval_decref(thr, tv);
+				tv++;
+			}
+
+			funcs = DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(thr->heap, f);
+			funcs_end = DUK_HCOMPILEDFUNCTION_GET_FUNCS_END(thr->heap, f);
+			while (funcs < funcs_end) {
+				duk_heaphdr_decref(thr, (duk_heaphdr *) *funcs);
+				funcs++;
+			}
+		} else {
+			/* May happen in some out-of-memory corner cases. */
+			DUK_D(DUK_DPRINT("duk_hcompiledfunction 'data' is NULL, skipping decref"));
 		}
 
 		duk_heaphdr_decref(thr, (duk_heaphdr *) DUK_HCOMPILEDFUNCTION_GET_DATA(thr->heap, f));
@@ -44722,6 +44841,15 @@
 #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.
@@ -44758,7 +44886,7 @@
 			goto alloc_error;
 		}
 		DUK_MEMZERO(res, sizeof(duk_hstring_external));
-#ifdef DUK_USE_EXPLICIT_NULL_INIT
+#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);
@@ -44772,7 +44900,7 @@
 			goto alloc_error;
 		}
 		DUK_MEMZERO(res, sizeof(duk_hstring));
-#ifdef DUK_USE_EXPLICIT_NULL_INIT
+#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);
@@ -45348,10 +45476,7 @@
 }
 
 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
+#if defined(DUK_USE_DEBUG)
 	duk_uint32_t old_used = heap->st_used;
 #endif
 	duk_uint32_t old_size = heap->st_size;
@@ -45365,7 +45490,7 @@
 	duk_uint32_t new_used = 0;
 	duk_uint32_t i;
 
-#ifdef DUK_USE_DEBUG
+#if defined(DUK_USE_DEBUG)
 	DUK_UNREF(old_used);  /* unused with some debug level combinations */
 #endif
 
@@ -45379,23 +45504,18 @@
 
 	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 */
+	 *  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)
@@ -45404,15 +45524,11 @@
 	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
+#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;
@@ -45548,10 +45664,24 @@
 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)) {
-		return NULL;
+		goto failed;
 	}
 #endif
 
@@ -45579,14 +45709,14 @@
 #endif
 	res = duk__alloc_init_hstring(heap, str, blen, strhash, extdata);
 	if (!res) {
-		return NULL;
+		goto failed;
 	}
 
 #if defined(DUK_USE_STRTAB_CHAIN)
 	if (duk__insert_hstring_chain(heap, res)) {
 		/* failed */
 		DUK_FREE(heap, res);
-		return NULL;
+		goto failed;
 	}
 #elif defined(DUK_USE_STRTAB_PROBE)
 	/* guaranteed to succeed */
@@ -45608,7 +45738,15 @@
 	 * 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) {
@@ -45744,16 +45882,24 @@
 
 #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)
-	duk__resize_strtab_probe(heap);
-#endif
+	(void) duk__resize_strtab_probe(heap);
+#endif
+
+	heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
 }
 #endif
 
@@ -46806,8 +46952,13 @@
 	duk_uint_t sanity;
 
 	DUK_ASSERT(thr != NULL);
-	DUK_ASSERT(h != NULL);
-	/* allow 'p' to be NULL; then the result is always false */
+
+	/* False if the object is NULL or the prototype 'p' is NULL.
+	 * In particular, false if both are NULL (don't compare equal).
+	 */
+	if (h == NULL || p == NULL) {
+		return 0;
+	}
 
 	sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
 	do {
@@ -51294,8 +51445,17 @@
 		DUK_TVAL_SET_UNUSED_UPDREF(thr, tv);  /* side effects */
 		goto success;
 	} else {
+		duk_hobject *h_get = NULL;
+		duk_hobject *h_set = NULL;
+		duk_tval tv_tmp;
+
 		DUK_ASSERT(desc.a_idx < 0);
 
+		/* Set property slot to an empty state.  Careful not to invoke
+		 * any side effects while using desc.e_idx so that it doesn't
+		 * get invalidated by a finalizer mutating our object.
+		 */
+
 		/* remove hash entry (no decref) */
 #if defined(DUK_USE_HOBJECT_HASH_PART)
 		if (desc.h_idx >= 0) {
@@ -51316,21 +51476,17 @@
 		DUK_DDD(DUK_DDDPRINT("before removing value, e_idx %ld, key %p, key at slot %p",
 		                     (long) desc.e_idx, (void *) key, (void *) DUK_HOBJECT_E_GET_KEY(thr->heap, obj, desc.e_idx)));
 		DUK_DDD(DUK_DDDPRINT("removing value at e_idx %ld", (long) desc.e_idx));
+		DUK_MEMSET((void *) &tv_tmp, 0, sizeof(tv_tmp));
+		DUK_TVAL_SET_UNDEFINED(&tv_tmp);
 		if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, obj, desc.e_idx)) {
-			duk_hobject *tmp;
-
-			tmp = DUK_HOBJECT_E_GET_VALUE_GETTER(thr->heap, obj, desc.e_idx);
+			h_get = DUK_HOBJECT_E_GET_VALUE_GETTER(thr->heap, obj, desc.e_idx);
+			h_set = DUK_HOBJECT_E_GET_VALUE_SETTER(thr->heap, obj, desc.e_idx);
 			DUK_HOBJECT_E_SET_VALUE_GETTER(thr->heap, obj, desc.e_idx, NULL);
-			DUK_UNREF(tmp);
-			DUK_HOBJECT_DECREF_ALLOWNULL(thr, tmp);  /* side effects */
-
-			tmp = DUK_HOBJECT_E_GET_VALUE_SETTER(thr->heap, obj, desc.e_idx);
 			DUK_HOBJECT_E_SET_VALUE_SETTER(thr->heap, obj, desc.e_idx, NULL);
-			DUK_UNREF(tmp);
-			DUK_HOBJECT_DECREF_ALLOWNULL(thr, tmp);  /* side effects */
 		} else {
 			tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, desc.e_idx);
-			DUK_TVAL_SET_UNDEFINED_UPDREF(thr, tv);  /* side effects */
+			DUK_TVAL_SET_TVAL(&tv_tmp, tv);
+			DUK_TVAL_SET_UNDEFINED(tv);
 		}
 #if 0
 		/* Not strictly necessary because if key == NULL, flag MUST be ignored. */
@@ -51343,7 +51499,14 @@
 		DUK_DDD(DUK_DDDPRINT("removing key at e_idx %ld", (long) desc.e_idx));
 		DUK_ASSERT(key == DUK_HOBJECT_E_GET_KEY(thr->heap, obj, desc.e_idx));
 		DUK_HOBJECT_E_SET_KEY(thr->heap, obj, desc.e_idx, NULL);
-		DUK_HSTRING_DECREF(thr, key);  /* side effects */
+
+		/* Do decrefs only with safe pointers to avoid side effects
+		 * disturbing e_idx.
+		 */
+		DUK_TVAL_DECREF(thr, &tv_tmp);
+		DUK_HOBJECT_DECREF_ALLOWNULL(thr, h_get);
+		DUK_HOBJECT_DECREF_ALLOWNULL(thr, h_set);
+		DUK_HSTRING_DECREF(thr, key);
 		goto success;
 	}
 
@@ -52517,6 +52680,7 @@
 		} else {
 			duk_bool_t rc;
 			duk_tval *tv1;
+			duk_tval tv_tmp;
 
 			/* curr is data, desc is accessor */
 			if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) && !force_flag) {
@@ -52536,9 +52700,12 @@
 
 			DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, obj, curr.e_idx));
 
+			/* Avoid side effects that might disturb curr.e_idx until
+			 * we're done editing the slot.
+			 */
 			tv1 = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, curr.e_idx);
-			/* XXX: just decref */
-			DUK_TVAL_SET_UNDEFINED_UPDREF(thr, tv1);  /* side effects */
+			DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
+			DUK_TVAL_SET_UNDEFINED(tv1);
 
 			DUK_HOBJECT_E_SET_VALUE_GETTER(thr->heap, obj, curr.e_idx, NULL);
 			DUK_HOBJECT_E_SET_VALUE_SETTER(thr->heap, obj, curr.e_idx, NULL);
@@ -52548,6 +52715,8 @@
 			DUK_DDD(DUK_DDDPRINT("flags after data->accessor conversion: 0x%02lx",
 			                     (unsigned long) DUK_HOBJECT_E_GET_FLAGS(thr->heap, obj, curr.e_idx)));
 
+			DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
+
 			/* re-lookup to update curr.flags
 			 * XXX: would be faster to update directly
 			 */
@@ -52563,7 +52732,8 @@
 
 		if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
 			duk_bool_t rc;
-			duk_hobject *tmp;
+			duk_hobject *h_get;
+			duk_hobject *h_set;
 
 			/* curr is accessor, desc is data */
 			if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) && !force_flag) {
@@ -52575,15 +52745,14 @@
 
 			DUK_DDD(DUK_DDDPRINT("convert property to data property"));
 
+			/* Avoid side effects that might disturb curr.e_idx until
+			 * we're done editing the slot.
+			 */
 			DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, obj, curr.e_idx));
-			tmp = DUK_HOBJECT_E_GET_VALUE_GETTER(thr->heap, obj, curr.e_idx);
-			DUK_UNREF(tmp);
+			h_get = DUK_HOBJECT_E_GET_VALUE_GETTER(thr->heap, obj, curr.e_idx);
 			DUK_HOBJECT_E_SET_VALUE_GETTER(thr->heap, obj, curr.e_idx, NULL);
-			DUK_HOBJECT_DECREF_ALLOWNULL(thr, tmp);  /* side effects */
-			tmp = DUK_HOBJECT_E_GET_VALUE_SETTER(thr->heap, obj, curr.e_idx);
-			DUK_UNREF(tmp);
+			h_set = DUK_HOBJECT_E_GET_VALUE_SETTER(thr->heap, obj, curr.e_idx);
 			DUK_HOBJECT_E_SET_VALUE_SETTER(thr->heap, obj, curr.e_idx, NULL);
-			DUK_HOBJECT_DECREF_ALLOWNULL(thr, tmp);  /* side effects */
 
 			DUK_TVAL_SET_UNDEFINED(DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, curr.e_idx));
 			DUK_HOBJECT_E_SLOT_CLEAR_WRITABLE(thr->heap, obj, curr.e_idx);
@@ -52592,6 +52761,9 @@
 			DUK_DDD(DUK_DDDPRINT("flags after accessor->data conversion: 0x%02lx",
 			                     (unsigned long) DUK_HOBJECT_E_GET_FLAGS(thr->heap, obj, curr.e_idx)));
 
+			DUK_HOBJECT_DECREF_ALLOWNULL(thr, h_get);  /* side effects */
+			DUK_HOBJECT_DECREF_ALLOWNULL(thr, h_set);  /* side effects */
+
 			/* re-lookup to update curr.flags
 			 * XXX: would be faster to update directly
 			 */
@@ -59702,6 +59874,7 @@
 	}
 	case DUK_IVAL_NONE:
 	default: {
+		DUK_D(DUK_DPRINT("invalid ivalue type: %ld", (long) x->t));
 		break;
 	}
 	}
@@ -61671,13 +61844,24 @@
 	 *  left-hand-side values (e.g. as in "f() = 1") must NOT cause a
 	 *  SyntaxError, but rather a run-time ReferenceError.
 	 *
-	 *  Assignment expression value is conceptually the LHS/RHS value
-	 *  copied into a fresh temporary so that it won't change even if
-	 *  LHS/RHS values change (e.g. when they're identifiers).  Doing this
-	 *  concretely produces inefficient bytecode, so we try to avoid the
-	 *  extra temporary for some known-to-be-safe cases.  Currently the
-	 *  only safe case we detect is a "top level assignment", for example
-	 *  "x = y + z;", where the assignment expression value is ignored.
+	 *  When evaluating X <op>= Y, the LHS (X) is conceptually evaluated
+	 *  to a temporary first.  The RHS is then evaluated.  Finally, the
+	 *  <op> is applied to the initial value of RHS (not the value after
+	 *  RHS evaluation), and written to X.  Doing so concretely generates
+	 *  inefficient code so we'd like to avoid the temporary when possible.
+	 *  See: https://github.com/svaarala/duktape/pull/992.
+	 *
+	 *  The expression value (final LHS value, written to RHS) is
+	 *  conceptually copied into a fresh temporary so that it won't
+	 *  change even if the LHS/RHS values change in outer expressions.
+	 *  For example, it'd be generally incorrect for the expression value
+	 *  to be the RHS register binding, unless there's a guarantee that it
+	 *  won't change during further expression evaluation.  Using the
+	 *  temporary concretely produces inefficient bytecode, so we try to
+	 *  avoid the extra temporary for some known-to-be-safe cases.
+	 *  Currently the only safe case we detect is a "top level assignment",
+	 *  for example "x = y + z;", where the assignment expression value is
+	 *  ignored.
 	 *  See: test-dev-assign-expr.js and test-bug-assign-mutate-gh381.js.
 	 */
 
@@ -61697,7 +61881,9 @@
 		 * is a reg-bound identifier.  The RHS ('res') is right associative
 		 * so it has consumed all other assignment level operations; the
 		 * only relevant lower binding power construct is comma operator
-		 * which will ignore the expression value provided here.
+		 * which will ignore the expression value provided here.  Usually
+		 * the top level assignment expression value is ignored, but it
+		 * is relevant for e.g. eval code.
 		 */
 		toplevel_assign = (comp_ctx->curr_func.nud_count == 1 && /* one token before */
 		                   comp_ctx->curr_func.led_count == 1);  /* one operator (= assign) */
@@ -61713,23 +61899,17 @@
 
 			DUK_ASSERT(left->x1.t == DUK_ISPEC_VALUE);  /* LHS is already side effect free */
 
-			/* Keep the RHS as an unresolved ivalue for now, so it
-			 * can be a plain value or a unary/binary operation here.
-			 * We resolve it before finishing but doing it later allows
-			 * better bytecode in some cases.
-			 */
-			duk__expr(comp_ctx, res, args_rbp /*rbp_flags*/);
-
 			h_varname = duk_get_hstring(ctx, left->x1.valstack_idx);
 			DUK_ASSERT(h_varname != NULL);
 			if (duk__hstring_is_eval_or_arguments_in_strict_mode(comp_ctx, h_varname)) {
-				/* E5 Section 11.13.1 (and others for other assignments), step 4 */
+				/* E5 Section 11.13.1 (and others for other assignments), step 4. */
 				goto syntax_error_lvalue;
 			}
 			duk_dup(ctx, left->x1.valstack_idx);
 			(void) duk__lookup_lhs(comp_ctx, &reg_varbind, &rc_varname);
 
 			if (args_op == DUK_OP_NONE) {
+				duk__expr(comp_ctx, res, args_rbp /*rbp_flags*/);
 				if (toplevel_assign) {
 					/* Any 'res' will do. */
 					DUK_DDD(DUK_DDDPRINT("plain assignment, toplevel assign, use as is"));
@@ -61743,42 +61923,98 @@
 					}
 				}
 			} else {
-				duk__ivalue_toregconst(comp_ctx, res);
-				DUK_ASSERT(res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_REGCONST);
+				/* For X <op>= Y we need to evaluate the pre-op
+				 * value of X before evaluating the RHS: the RHS
+				 * can change X, but when we do <op> we must use
+				 * the pre-op value.
+				 */
+				duk_reg_t reg_temp;
+
+				reg_temp = DUK__ALLOCTEMP(comp_ctx);
 
 				if (reg_varbind >= 0) {
 					duk_reg_t reg_res;
+					duk_reg_t reg_src;
+					duk_int_t pc_temp_load;
+					duk_int_t pc_before_rhs;
+					duk_int_t pc_after_rhs;
 
 					if (toplevel_assign) {
 						/* 'reg_varbind' is the operation result and can also
 						 * become the expression value for top level assignments
 						 * such as: "var x; x += y;".
 						 */
+						DUK_DD(DUK_DDPRINT("<op>= expression is top level, write directly to reg_varbind"));
 						reg_res = reg_varbind;
 					} else {
 						/* Not safe to use 'reg_varbind' as assignment expression
 						 * value, so go through a temp.
 						 */
-						reg_res = DUK__ALLOCTEMP(comp_ctx);
+						DUK_DD(DUK_DDPRINT("<op>= expression is not top level, write to reg_temp"));
+						reg_res = reg_temp;  /* reg_res should be smallest possible */
+						reg_temp = DUK__ALLOCTEMP(comp_ctx);
+					}
+
+					/* Try to optimize X <op>= Y for reg-bound
+					 * variables.  Detect side-effect free RHS
+					 * narrowly by seeing whether it emits code.
+					 * If not, rewind the code emitter and overwrite
+					 * the unnecessary temp reg load.
+					 */
+
+					pc_temp_load = duk__get_current_pc(comp_ctx);
+					duk__emit_a_bc(comp_ctx,
+					               DUK_OP_LDREG,
+					               (duk_regconst_t) reg_temp,
+					               reg_varbind);
+
+					pc_before_rhs = duk__get_current_pc(comp_ctx);
+					duk__expr_toregconst(comp_ctx, res, args_rbp /*rbp_flags*/);
+					DUK_ASSERT(res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_REGCONST);
+					pc_after_rhs = duk__get_current_pc(comp_ctx);
+
+					DUK_DD(DUK_DDPRINT("pc_temp_load=%ld, pc_before_rhs=%ld, pc_after_rhs=%ld",
+					                   (long) pc_temp_load, (long) pc_before_rhs,
+					                   (long) pc_after_rhs));
+
+					if (pc_after_rhs == pc_before_rhs) {
+						/* Note: if the reg_temp load generated shuffling
+						 * instructions, we may need to rewind more than
+						 * one instruction, so use explicit PC computation.
+						 */
+						DUK_DD(DUK_DDPRINT("rhs is side effect free, rewind and avoid unnecessary temp for reg-based <op>="));
+						DUK_BW_ADD_PTR(comp_ctx->thr, &comp_ctx->curr_func.bw_code, (pc_temp_load - pc_before_rhs) * sizeof(duk_compiler_instr));
+						reg_src = reg_varbind;
+					} else {
+						DUK_DD(DUK_DDPRINT("rhs evaluation emitted code, not sure if rhs is side effect free; use temp reg for LHS"));
+						reg_src = reg_temp;
 					}
 
 					duk__emit_a_b_c(comp_ctx,
 					                args_op,
 					                (duk_regconst_t) reg_res,
-					                (duk_regconst_t) reg_varbind,
+					                (duk_regconst_t) reg_src,
 					                res->x1.regconst);
+
 					res->x1.regconst = (duk_regconst_t) reg_res;
+
+					/* Ensure compact use of temps. */
+					if (DUK__ISTEMP(comp_ctx, reg_res)) {
+						DUK__SETTEMP(comp_ctx, reg_res + 1);
+					}
 				} else {
 					/* When LHS is not register bound, always go through a
 					 * temporary.  No optimization for top level assignment.
 					 */
-					duk_reg_t reg_temp;
-					reg_temp = DUK__ALLOCTEMP(comp_ctx);
 
 					duk__emit_a_bc(comp_ctx,
 					               DUK_OP_GETVAR,
 					               (duk_regconst_t) reg_temp,
 					               rc_varname);
+
+					duk__expr_toregconst(comp_ctx, res, args_rbp /*rbp_flags*/);
+					DUK_ASSERT(res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_REGCONST);
+
 					duk__emit_a_b_c(comp_ctx,
 					                args_op,
 					                (duk_regconst_t) reg_temp,
@@ -61864,10 +62100,10 @@
 			                                   DUK__IVAL_FLAG_REQUIRE_TEMP | DUK__IVAL_FLAG_ALLOW_CONST /*flags*/);
 
 			/* Evaluate RHS only when LHS is safe. */
-			duk__expr_toregconst(comp_ctx, res, args_rbp /*rbp_flags*/);
-			DUK_ASSERT(res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_REGCONST);
 
 			if (args_op == DUK_OP_NONE) {
+				duk__expr_toregconst(comp_ctx, res, args_rbp /*rbp_flags*/);
+				DUK_ASSERT(res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_REGCONST);
 				rc_res = res->x1.regconst;
 			} else {
 				reg_temp = DUK__ALLOCTEMP(comp_ctx);
@@ -61876,6 +62112,10 @@
 				                (duk_regconst_t) reg_temp,
 				                (duk_regconst_t) reg_obj,
 				                rc_key);
+
+				duk__expr_toregconst(comp_ctx, res, args_rbp /*rbp_flags*/);
+				DUK_ASSERT(res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_REGCONST);
+
 				duk__emit_a_b_c(comp_ctx,
 				                args_op,
 				                (duk_regconst_t) reg_temp,
@@ -61907,17 +62147,18 @@
 
 			duk_regconst_t rc_res;
 
-			/* first evaluate LHS fully to ensure all side effects are out */
+			/* First evaluate LHS fully to ensure all side effects are out. */
 			duk__ivalue_toplain_ignore(comp_ctx, left);
 
-			/* then evaluate RHS fully (its value becomes the expression value too) */
+			/* Then evaluate RHS fully (its value becomes the expression value too).
+			 * Technically we'd need the side effect safety check here too, but because
+			 * we always throw using INVLHS the result doesn't matter.
+			 */
 			rc_res = duk__expr_toregconst(comp_ctx, res, args_rbp /*rbp_flags*/);
 
 			duk__emit_extraop_only(comp_ctx,
 			                       DUK_EXTRAOP_INVLHS);
 
-			/* XXX: this value is irrelevant because of INVLHS? */
-
 			res->t = DUK_IVAL_PLAIN;
 			res->x1.t = DUK_ISPEC_REGCONST;
 			res->x1.regconst = rc_res;
@@ -71119,7 +71360,12 @@
 	duk_tval *tv, *tv_end;
 	duk_hobject **funcs, **funcs_end;
 
-	DUK_ASSERT(DUK_HCOMPILEDFUNCTION_GET_DATA(thr->heap, f) != NULL);  /* compiled functions must be created 'atomically' */
+	/* If function creation fails due to out-of-memory, the data buffer
+	 * pointer may be NULL in some cases.  That's actually possible for
+	 * GC code, but shouldn't be possible here because the incomplete
+	 * function will be unwound from the value stack and never instantiated.
+	 */
+	DUK_ASSERT(DUK_HCOMPILEDFUNCTION_GET_DATA(thr->heap, f) != NULL);
 	DUK_UNREF(thr);
 
 	tv = DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(thr->heap, f);
@@ -78933,6 +79179,7 @@
 	duk_hobject *h_regexp;
 	duk_hstring *h_bytecode;
 	duk_hstring *h_input;
+	duk_uint8_t *p_buf;
 	const duk_uint8_t *pc;
 	const duk_uint8_t *sp;
 	duk_small_int_t match = 0;
@@ -79003,17 +79250,21 @@
 	DUK_ASSERT(re_ctx.nsaved >= 2);
 	DUK_ASSERT((re_ctx.nsaved % 2) == 0);
 
-	duk_push_fixed_buffer(ctx, sizeof(duk_uint8_t *) * re_ctx.nsaved);
+	p_buf = (duk_uint8_t *) duk_push_fixed_buffer(ctx, sizeof(duk_uint8_t *) * re_ctx.nsaved);
+	DUK_UNREF(p_buf);
 	re_ctx.saved = (const duk_uint8_t **) duk_get_buffer(ctx, -1, NULL);
 	DUK_ASSERT(re_ctx.saved != NULL);
 
 	/* [ ... re_obj input bc saved_buf ] */
 
-	/* buffer is automatically zeroed */
-#ifdef DUK_USE_EXPLICIT_NULL_INIT
+#if defined(DUK_USE_EXPLICIT_NULL_INIT)
 	for (i = 0; i < re_ctx.nsaved; i++) {
 		re_ctx.saved[i] = (duk_uint8_t *) NULL;
 	}
+#elif defined(DUK_USE_ZERO_BUFFER_DATA)
+	/* buffer is automatically zeroed */
+#else
+	DUK_MEMZERO((void *) p_buf, sizeof(duk_uint8_t *) * re_ctx.nsaved);
 #endif
 
 	DUK_DDD(DUK_DDDPRINT("regexp ctx initialized, flags=0x%08lx, nsaved=%ld, recursion_limit=%ld, steps_limit=%ld",
--- a/duktape/duktape.h	Thu Dec 01 13:09:43 2016 +0100
+++ b/duktape/duktape.h	Wed Dec 21 11:41:11 2016 +0100
@@ -1,12 +1,12 @@
 /*
- *  Duktape public API for Duktape 1.5.1.
+ *  Duktape public API for Duktape 1.6.0.
  *
  *  See the API reference for documentation on call semantics.
  *  The exposed API is inside the DUK_API_PUBLIC_H_INCLUDED
  *  include guard.  Other parts of the header are Duktape
  *  internal and related to platform/compiler/feature detection.
  *
- *  Git commit 2cc76e9ff1f64869e1146ad7317d8cbe33bbd27e (v1.5.1).
+ *  Git commit 17e3d86cf8b4788bd0d37658f833ab440ce43a1c (v1.6.0).
  *  Git branch HEAD.
  *
  *  See Duktape AUTHORS.rst and LICENSE.txt for copyright and
@@ -163,6 +163,7 @@
  *  in Duktape web documentation.
  */
 
+struct duk_thread_state;
 struct duk_memory_functions;
 struct duk_function_list_entry;
 struct duk_number_list_entry;
@@ -170,6 +171,7 @@
 /* duk_context is now defined in duk_config.h because it may also be
  * referenced there by prototypes.
  */
+typedef struct duk_thread_state duk_thread_state;
 typedef struct duk_memory_functions duk_memory_functions;
 typedef struct duk_function_list_entry duk_function_list_entry;
 typedef struct duk_number_list_entry duk_number_list_entry;
@@ -190,6 +192,14 @@
 typedef duk_idx_t (*duk_debug_request_function) (duk_context *ctx, void *udata, duk_idx_t nvalues);
 typedef void (*duk_debug_detached_function) (void *udata);
 
+struct duk_thread_state {
+	/* XXX: Enough space to hold internal suspend/resume structure.
+	 * This is rather awkward and to be fixed when the internal
+	 * structure is visible for the public API header.
+	 */
+	char data[128];
+};
+
 struct duk_memory_functions {
 	duk_alloc_function alloc_func;
 	duk_realloc_function realloc_func;
@@ -218,15 +228,15 @@
  * have 99 for patch level (e.g. 0.10.99 would be a development version
  * after 0.10.0 but before the next official release).
  */
-#define DUK_VERSION                       10501L
+#define DUK_VERSION                       10600L
 
 /* Git commit, describe, and branch for Duktape build.  Useful for
  * non-official snapshot builds so that application code can easily log
  * which Duktape snapshot was used.  Not available in the Ecmascript
  * environment.
  */
-#define DUK_GIT_COMMIT                    "2cc76e9ff1f64869e1146ad7317d8cbe33bbd27e"
-#define DUK_GIT_DESCRIBE                  "v1.5.1"
+#define DUK_GIT_COMMIT                    "17e3d86cf8b4788bd0d37658f833ab440ce43a1c"
+#define DUK_GIT_DESCRIBE                  "v1.6.0"
 #define DUK_GIT_BRANCH                    "HEAD"
 
 /* Duktape debug protocol version used by this build. */
@@ -397,6 +407,9 @@
                              duk_fatal_function fatal_handler);
 DUK_EXTERNAL_DECL void duk_destroy_heap(duk_context *ctx);
 
+DUK_EXTERNAL_DECL void duk_suspend(duk_context *ctx, duk_thread_state *state);
+DUK_EXTERNAL_DECL void duk_resume(duk_context *ctx, const duk_thread_state *state);
+
 #define duk_create_heap_default() \
 	duk_create_heap(NULL, NULL, NULL, NULL, NULL)