shithub: libmujs

Download patch

ref: 4eae3ec10c4a874fe4b021b01362f6f3362a18b0
parent: 7bb55ad3567ab7c11986727622662c3d1317080d
author: Tor Andersson <tor.andersson@artifex.com>
date: Sat Nov 29 08:59:09 EST 2014

Make js_toprimitive (and by consequence js_tonumber/string/...) in-place.

Any coercion between types may overwrite the stack slot with the coerced
value. This is usually not a problem, but if you need to preserve the
original value, you should copy it to another stack slot before running
any functions that may coerce the type (anything involving ToPrimitive).

This change lets us avoid interning the result of toString().

If we later add short strings (embedded in js_Value and js_Property
structs) then we can also avoid creating a garbage collected string or
interning the result of js_tostring on a number.

--- a/jsdate.c
+++ b/jsdate.c
@@ -407,19 +407,16 @@
 {
 	unsigned int top = js_gettop(J);
 	js_Object *obj;
-	js_Value v;
 	double t;
 
 	if (top == 1)
 		t = Now();
 	else if (top == 2) {
-		v = js_toprimitive(J, 1, JS_HNONE);
-		if (v.type == JS_TLITERAL)
-			t = parseDateTime(v.u.literal);
-		else if (v.type == JS_TSTRING)
-			t = parseDateTime(v.u.string->p);
+		js_toprimitive(J, 1, JS_HNONE);
+		if (js_isstring(J, 1))
+			t = parseDateTime(js_tostring(J, 1));
 		else
-			t = TimeClip(jsV_tonumber(J, &v));
+			t = TimeClip(js_tonumber(J, 1));
 	} else {
 		double y, m, d, H, M, S, ms;
 		y = js_tonumber(J, 1);
@@ -726,17 +723,18 @@
 
 static void Dp_toJSON(js_State *J)
 {
-	js_Object *obj = js_toobject(J, 0);
-	js_Value tv = js_toprimitive(J, 0, JS_HNUMBER);
-	if (tv.type == JS_TNULL && !isfinite(tv.u.number)) {
+	js_copy(J, 0);
+	js_toprimitive(J, -1, JS_HNUMBER);
+	if (js_isnumber(J, -1) && !isfinite(js_tonumber(J, -1))) {
 		js_pushnull(J);
 		return;
 	}
-	js_pushobject(J, obj);
-	js_getproperty(J, -1, "toISOString");
+	js_pop(J, 1);
+
+	js_getproperty(J, 0, "toISOString");
 	if (!js_iscallable(J, -1))
 		js_typeerror(J, "Date.prototype.toJSON: this.toISOString not a function");
-	js_pushobject(J, obj);
+	js_copy(J, 0);
 	js_call(J, 0);
 }
 
--- a/jsrun.c
+++ b/jsrun.c
@@ -155,7 +155,7 @@
 
 /* Read values from stack */
 
-static const js_Value *stackidx(js_State *J, int idx)
+static js_Value *stackidx(js_State *J, int idx)
 {
 	static js_Value undefined = { JS_TUNDEFINED, { 0 } };
 	idx = idx < 0 ? TOP + idx : BOT + idx;
@@ -164,6 +164,11 @@
 	return STACK + idx;
 }
 
+js_Value *js_tovalue(js_State *J, int idx)
+{
+	return stackidx(J, idx);
+}
+
 int js_isdefined(js_State *J, int idx) { return stackidx(J, idx)->type != JS_TUNDEFINED; }
 int js_isundefined(js_State *J, int idx) { return stackidx(J, idx)->type == JS_TUNDEFINED; }
 int js_isnull(js_State *J, int idx) { return stackidx(J, idx)->type == JS_TNULL; }
@@ -175,7 +180,7 @@
 
 int js_iscallable(js_State *J, int idx)
 {
-	const js_Value *v = stackidx(J, idx);
+	js_Value *v = stackidx(J, idx);
 	if (v->type == JS_TOBJECT)
 		return v->u.object->type == JS_CFUNCTION ||
 			v->u.object->type == JS_CSCRIPT ||
@@ -185,25 +190,25 @@
 
 int js_isarray(js_State *J, int idx)
 {
-	const js_Value *v = stackidx(J, idx);
+	js_Value *v = stackidx(J, idx);
 	return v->type == JS_TOBJECT && v->u.object->type == JS_CARRAY;
 }
 
 int js_isregexp(js_State *J, int idx)
 {
-	const js_Value *v = stackidx(J, idx);
+	js_Value *v = stackidx(J, idx);
 	return v->type == JS_TOBJECT && v->u.object->type == JS_CREGEXP;
 }
 
 static int js_isiterator(js_State *J, int idx)
 {
-	const js_Value *v = stackidx(J, idx);
+	js_Value *v = stackidx(J, idx);
 	return v->type == JS_TOBJECT && v->u.object->type == JS_CITERATOR;
 }
 
 int js_isuserdata(js_State *J, int idx, const char *tag)
 {
-	const js_Value *v = stackidx(J, idx);
+	js_Value *v = stackidx(J, idx);
 	if (v->type == JS_TOBJECT && v->u.object->type == JS_CUSERDATA)
 		return !strcmp(tag, v->u.object->u.user.tag);
 	return 0;
@@ -211,7 +216,7 @@
 
 static const char *js_typeof(js_State *J, int idx)
 {
-	const js_Value *v = stackidx(J, idx);
+	js_Value *v = stackidx(J, idx);
 	switch (v->type) {
 	default:
 	case JS_TUNDEFINED: return "undefined";
@@ -227,11 +232,6 @@
 	}
 }
 
-js_Value js_tovalue(js_State *J, int idx)
-{
-	return *stackidx(J, idx);
-}
-
 int js_toboolean(js_State *J, int idx)
 {
 	return jsV_toboolean(J, stackidx(J, idx));
@@ -282,14 +282,14 @@
 	return jsV_toobject(J, stackidx(J, idx));
 }
 
-js_Value js_toprimitive(js_State *J, int idx, int hint)
+void js_toprimitive(js_State *J, int idx, int hint)
 {
-	return jsV_toprimitive(J, stackidx(J, idx), hint);
+	jsV_toprimitive(J, stackidx(J, idx), hint);
 }
 
 js_Regexp *js_toregexp(js_State *J, int idx)
 {
-	const js_Value *v = stackidx(J, idx);
+	js_Value *v = stackidx(J, idx);
 	if (v->type == JS_TOBJECT && v->u.object->type == JS_CREGEXP)
 		return &v->u.object->u.r;
 	js_typeerror(J, "not a regexp");
@@ -297,7 +297,7 @@
 
 void *js_touserdata(js_State *J, int idx, const char *tag)
 {
-	const js_Value *v = stackidx(J, idx);
+	js_Value *v = stackidx(J, idx);
 	if (v->type == JS_TOBJECT && v->u.object->type == JS_CUSERDATA)
 		if (!strcmp(tag, v->u.object->u.user.tag))
 			return v->u.object->u.user.data;
@@ -306,7 +306,7 @@
 
 static js_Object *jsR_tofunction(js_State *J, int idx)
 {
-	const js_Value *v = stackidx(J, idx);
+	js_Value *v = stackidx(J, idx);
 	if (v->type == JS_TUNDEFINED || v->type == JS_TNULL)
 		return NULL;
 	if (v->type == JS_TOBJECT)
@@ -502,7 +502,7 @@
 		js_pushundefined(J);
 }
 
-static void jsR_setproperty(js_State *J, js_Object *obj, const char *name, const js_Value *value)
+static void jsR_setproperty(js_State *J, js_Object *obj, const char *name, js_Value *value)
 {
 	js_Property *ref;
 	unsigned int k;
@@ -561,7 +561,7 @@
 }
 
 static void jsR_defproperty(js_State *J, js_Object *obj, const char *name,
-	int atts, const js_Value *value, js_Object *getter, js_Object *setter)
+	int atts, js_Value *value, js_Object *getter, js_Object *setter)
 {
 	js_Property *ref;
 	unsigned int k;
@@ -635,7 +635,7 @@
 
 const char *js_ref(js_State *J)
 {
-	const js_Value *v = stackidx(J, -1);
+	js_Value *v = stackidx(J, -1);
 	const char *s;
 	char buf[32];
 	switch (v->type) {
@@ -800,7 +800,7 @@
 				return;
 			}
 			if (!(ref->atts & JS_READONLY))
-				ref->value = js_tovalue(J, -1);
+				ref->value = *stackidx(J, -1);
 			return;
 		}
 		E = E->outer;
@@ -854,7 +854,7 @@
 		js_pushundefined(J);
 
 	jsR_run(J, F);
-	v = js_tovalue(J, -1);
+	v = *stackidx(J, -1);
 	TOP = --BOT; /* clear stack */
 	js_pushvalue(J, v);
 
@@ -896,7 +896,7 @@
 	js_pop(J, n);
 
 	jsR_run(J, F);
-	v = js_tovalue(J, -1);
+	v = *stackidx(J, -1);
 	TOP = --BOT; /* clear stack */
 	js_pushvalue(J, v);
 
@@ -912,7 +912,7 @@
 
 	js_pop(J, n);
 	jsR_run(J, F);
-	v = js_tovalue(J, -1);
+	v = *stackidx(J, -1);
 	TOP = --BOT; /* clear stack */
 	js_pushvalue(J, v);
 
@@ -929,7 +929,7 @@
 		js_pushundefined(J);
 
 	F(J);
-	v = js_tovalue(J, -1);
+	v = *stackidx(J, -1);
 	TOP = --BOT; /* clear stack */
 	js_pushvalue(J, v);
 }
@@ -1041,7 +1041,7 @@
 void js_throw(js_State *J)
 {
 	if (J->trylen > 0) {
-		js_Value v = js_tovalue(J, -1);
+		js_Value v = *stackidx(J, -1);
 		--J->trylen;
 		J->E = J->trybuf[J->trylen].E;
 		J->envtop = J->trybuf[J->trylen].envtop;
--- a/jsvalue.c
+++ b/jsvalue.c
@@ -4,8 +4,8 @@
 #include "jsvalue.h"
 #include "utf.h"
 
-#define JSV_ISSTRING(v) (v.type==JS_TSTRING || v.type==JS_TLITERAL)
-#define JSV_TOSTRING(v) (v.type==JS_TSTRING ? v.u.string->p : v.type==JS_TLITERAL ? v.u.literal : NULL)
+#define JSV_ISSTRING(v) (v->type==JS_TSTRING || v->type==JS_TLITERAL)
+#define JSV_TOSTRING(v) (v->type==JS_TSTRING ? v->u.string->p : v->type==JS_TLITERAL ? v->u.literal : NULL)
 
 double jsV_numbertointeger(double n)
 {
@@ -81,13 +81,12 @@
 }
 
 /* ToPrimitive() on a value */
-js_Value jsV_toprimitive(js_State *J, const js_Value *v, int preferred)
+void jsV_toprimitive(js_State *J, js_Value *v, int preferred)
 {
-	js_Value vv;
 	js_Object *obj;
 
 	if (v->type != JS_TOBJECT)
-		return *v;
+		return;
 
 	obj = v->u.object;
 
@@ -96,23 +95,15 @@
 
 	if (preferred == JS_HSTRING) {
 		if (jsV_toString(J, obj) || jsV_valueOf(J, obj)) {
-			vv = js_tovalue(J, -1);
-			if (vv.type == JS_TSTRING) { /* don't return gc'd string not on stack */
-				vv.type = JS_TLITERAL;
-				vv.u.literal = js_intern(J, vv.u.string->p);
-			}
+			*v = *js_tovalue(J, -1);
 			js_pop(J, 1);
-			return vv;
+			return;
 		}
 	} else {
 		if (jsV_valueOf(J, obj) || jsV_toString(J, obj)) {
-			vv = js_tovalue(J, -1);
-			if (vv.type == JS_TSTRING) { /* don't return gc'd string not on stack */
-				vv.type = JS_TLITERAL;
-				vv.u.literal = js_intern(J, vv.u.string->p);
-			}
+			*v = *js_tovalue(J, -1);
 			js_pop(J, 1);
-			return vv;
+			return;
 		}
 	}
 
@@ -120,7 +111,7 @@
 }
 
 /* ToBoolean() on a value */
-int jsV_toboolean(js_State *J, const js_Value *v)
+int jsV_toboolean(js_State *J, js_Value *v)
 {
 	switch (v->type) {
 	default:
@@ -200,7 +191,7 @@
 }
 
 /* ToNumber() on a value */
-double jsV_tonumber(js_State *J, const js_Value *v)
+double jsV_tonumber(js_State *J, js_Value *v)
 {
 	switch (v->type) {
 	default:
@@ -211,14 +202,12 @@
 	case JS_TLITERAL: return jsV_stringtonumber(J, v->u.literal);
 	case JS_TSTRING: return jsV_stringtonumber(J, v->u.string->p);
 	case JS_TOBJECT:
-		{
-			js_Value vv = jsV_toprimitive(J, v, JS_HNUMBER);
-			return jsV_tonumber(J, &vv);
-		}
+		jsV_toprimitive(J, v, JS_HNUMBER);
+		return jsV_tonumber(J, v);
 	}
 }
 
-double jsV_tointeger(js_State *J, const js_Value *v)
+double jsV_tointeger(js_State *J, js_Value *v)
 {
 	return jsV_numbertointeger(jsV_tonumber(J, v));
 }
@@ -275,7 +264,7 @@
 }
 
 /* ToString() on a value */
-const char *jsV_tostring(js_State *J, const js_Value *v)
+const char *jsV_tostring(js_State *J, js_Value *v)
 {
 	char buf[32];
 	switch (v->type) {
@@ -283,14 +272,13 @@
 	case JS_TUNDEFINED: return "undefined";
 	case JS_TNULL: return "null";
 	case JS_TBOOLEAN: return v->u.boolean ? "true" : "false";
-	case JS_TNUMBER: return js_intern(J, jsV_numbertostring(J, buf, v->u.number)); /* TODO: no intern here */
 	case JS_TLITERAL: return v->u.literal;
 	case JS_TSTRING: return v->u.string->p;
+	case JS_TNUMBER:
+		return js_intern(J, jsV_numbertostring(J, buf, v->u.number));
 	case JS_TOBJECT:
-		{
-			js_Value vv = jsV_toprimitive(J, v, JS_HSTRING);
-			return jsV_tostring(J, &vv);
-		}
+		jsV_toprimitive(J, v, JS_HSTRING);
+		return jsV_tostring(J, v);
 	}
 }
 
@@ -319,7 +307,7 @@
 }
 
 /* ToObject() on a value */
-js_Object *jsV_toobject(js_State *J, const js_Value *v)
+js_Object *jsV_toobject(js_State *J, js_Value *v)
 {
 	switch (v->type) {
 	default:
@@ -465,11 +453,12 @@
 
 void js_concat(js_State *J)
 {
-	js_Value va = js_toprimitive(J, -2, JS_HNONE);
-	js_Value vb = js_toprimitive(J, -1, JS_HNONE);
-	if (JSV_ISSTRING(va) || JSV_ISSTRING(vb)) {
-		const char *sa = jsV_tostring(J, &va);
-		const char *sb = jsV_tostring(J, &vb);
+	js_toprimitive(J, -2, JS_HNONE);
+	js_toprimitive(J, -1, JS_HNONE);
+
+	if (js_isstring(J, -2) || js_isstring(J, -1)) {
+		const char *sa = js_tostring(J, -2);
+		const char *sb = js_tostring(J, -1);
 		/* TODO: create js_String directly */
 		char *sab = js_malloc(J, strlen(sa) + strlen(sb) + 1);
 		strcpy(sab, sa);
@@ -483,8 +472,8 @@
 		js_endtry(J);
 		js_free(J, sab);
 	} else {
-		double x = jsV_tonumber(J, &va);
-		double y = jsV_tonumber(J, &vb);
+		double x = js_tonumber(J, -2);
+		double y = js_tonumber(J, -1);
 		js_pop(J, 2);
 		js_pushnumber(J, x + y);
 	}
@@ -492,15 +481,15 @@
 
 int js_compare(js_State *J, int *okay)
 {
-	js_Value va = js_toprimitive(J, -2, JS_HNUMBER);
-	js_Value vb = js_toprimitive(J, -1, JS_HNUMBER);
+	js_toprimitive(J, -2, JS_HNUMBER);
+	js_toprimitive(J, -1, JS_HNUMBER);
 
 	*okay = 1;
-	if (JSV_ISSTRING(va) && JSV_ISSTRING(vb)) {
-		return strcmp(JSV_TOSTRING(va), JSV_TOSTRING(vb));
+	if (js_isstring(J, -2) && js_isstring(J, -1)) {
+		return strcmp(js_tostring(J, -2), js_tostring(J, -1));
 	} else {
-		double x = jsV_tonumber(J, &va);
-		double y = jsV_tonumber(J, &vb);
+		double x = js_tonumber(J, -2);
+		double y = js_tonumber(J, -1);
 		if (isnan(x) || isnan(y))
 			*okay = 0;
 		return x < y ? -1 : x > y ? 1 : 0;
@@ -509,45 +498,45 @@
 
 int js_equal(js_State *J)
 {
-	js_Value x = js_tovalue(J, -2);
-	js_Value y = js_tovalue(J, -1);
+	js_Value *x = js_tovalue(J, -2);
+	js_Value *y = js_tovalue(J, -1);
 
 retry:
-	if (JSV_ISSTRING(x) && JSV_ISSTRING(y)) {
+	if (JSV_ISSTRING(x) && JSV_ISSTRING(y))
 		return !strcmp(JSV_TOSTRING(x), JSV_TOSTRING(y));
-	} else if (x.type == y.type) {
-		if (x.type == JS_TUNDEFINED) return 1;
-		if (x.type == JS_TNULL) return 1;
-		if (x.type == JS_TNUMBER) return x.u.number == y.u.number;
-		if (x.type == JS_TBOOLEAN) return x.u.boolean == y.u.boolean;
-		if (x.type == JS_TOBJECT) return x.u.object == y.u.object;
+	if (x->type == y->type) {
+		if (x->type == JS_TUNDEFINED) return 1;
+		if (x->type == JS_TNULL) return 1;
+		if (x->type == JS_TNUMBER) return x->u.number == y->u.number;
+		if (x->type == JS_TBOOLEAN) return x->u.boolean == y->u.boolean;
+		if (x->type == JS_TOBJECT) return x->u.object == y->u.object;
 		return 0;
 	}
 
-	if (x.type == JS_TNULL && y.type == JS_TUNDEFINED) return 1;
-	if (x.type == JS_TUNDEFINED && y.type == JS_TNULL) return 1;
+	if (x->type == JS_TNULL && y->type == JS_TUNDEFINED) return 1;
+	if (x->type == JS_TUNDEFINED && y->type == JS_TNULL) return 1;
 
-	if (x.type == JS_TNUMBER && JSV_ISSTRING(y))
-		return x.u.number == jsV_tonumber(J, &y);
-	if (JSV_ISSTRING(x) && y.type == JS_TNUMBER)
-		return jsV_tonumber(J, &x) == y.u.number;
+	if (x->type == JS_TNUMBER && JSV_ISSTRING(y))
+		return x->u.number == jsV_tonumber(J, y);
+	if (JSV_ISSTRING(x) && y->type == JS_TNUMBER)
+		return jsV_tonumber(J, x) == y->u.number;
 
-	if (x.type == JS_TBOOLEAN) {
-		x.type = JS_TNUMBER;
-		x.u.number = x.u.boolean;
+	if (x->type == JS_TBOOLEAN) {
+		x->type = JS_TNUMBER;
+		x->u.number = x->u.boolean;
 		goto retry;
 	}
-	if (y.type == JS_TBOOLEAN) {
-		y.type = JS_TNUMBER;
-		y.u.number = y.u.boolean;
+	if (y->type == JS_TBOOLEAN) {
+		y->type = JS_TNUMBER;
+		y->u.number = y->u.boolean;
 		goto retry;
 	}
-	if ((JSV_ISSTRING(x) || x.type == JS_TNUMBER) && y.type == JS_TOBJECT) {
-		y = jsV_toprimitive(J, &y, JS_HNONE);
+	if ((JSV_ISSTRING(x) || x->type == JS_TNUMBER) && y->type == JS_TOBJECT) {
+		jsV_toprimitive(J, y, JS_HNONE);
 		goto retry;
 	}
-	if (x.type == JS_TOBJECT && (JSV_ISSTRING(y) || y.type == JS_TNUMBER)) {
-		x = jsV_toprimitive(J, &x, JS_HNONE);
+	if (x->type == JS_TOBJECT && (JSV_ISSTRING(y) || y->type == JS_TNUMBER)) {
+		jsV_toprimitive(J, x, JS_HNONE);
 		goto retry;
 	}
 
@@ -556,17 +545,17 @@
 
 int js_strictequal(js_State *J)
 {
-	js_Value va = js_tovalue(J, -2);
-	js_Value vb = js_tovalue(J, -1);
+	js_Value *x = js_tovalue(J, -2);
+	js_Value *y = js_tovalue(J, -1);
 
-	if (JSV_ISSTRING(va) && JSV_ISSTRING(vb))
-		return !strcmp(JSV_TOSTRING(va), JSV_TOSTRING(vb));
+	if (JSV_ISSTRING(x) && JSV_ISSTRING(y))
+		return !strcmp(JSV_TOSTRING(x), JSV_TOSTRING(y));
 
-	if (va.type != vb.type) return 0;
-	if (va.type == JS_TUNDEFINED) return 1;
-	if (va.type == JS_TNULL) return 1;
-	if (va.type == JS_TNUMBER) return va.u.number == vb.u.number;
-	if (va.type == JS_TBOOLEAN) return va.u.boolean == vb.u.boolean;
-	if (va.type == JS_TOBJECT) return va.u.object == vb.u.object;
+	if (x->type != y->type) return 0;
+	if (x->type == JS_TUNDEFINED) return 1;
+	if (x->type == JS_TNULL) return 1;
+	if (x->type == JS_TNUMBER) return x->u.number == y->u.number;
+	if (x->type == JS_TBOOLEAN) return x->u.boolean == y->u.boolean;
+	if (x->type == JS_TOBJECT) return x->u.object == y->u.object;
 	return 0;
 }
--- a/jsvalue.h
+++ b/jsvalue.h
@@ -126,19 +126,19 @@
 };
 
 /* jsrun.c */
-js_Value js_tovalue(js_State *J, int idx);
-js_Value js_toprimitive(js_State *J, int idx, int hint);
+js_Value *js_tovalue(js_State *J, int idx);
+void js_toprimitive(js_State *J, int idx, int hint);
 js_Object *js_toobject(js_State *J, int idx);
 void js_pushvalue(js_State *J, js_Value v);
 void js_pushobject(js_State *J, js_Object *v);
 
 /* jsvalue.c */
-int jsV_toboolean(js_State *J, const js_Value *v);
-double jsV_tonumber(js_State *J, const js_Value *v);
-double jsV_tointeger(js_State *J, const js_Value *v);
-const char *jsV_tostring(js_State *J, const js_Value *v);
-js_Object *jsV_toobject(js_State *J, const js_Value *v);
-js_Value jsV_toprimitive(js_State *J, const js_Value *v, int preferred);
+int jsV_toboolean(js_State *J, js_Value *v);
+double jsV_tonumber(js_State *J, js_Value *v);
+double jsV_tointeger(js_State *J, js_Value *v);
+const char *jsV_tostring(js_State *J, js_Value *v);
+js_Object *jsV_toobject(js_State *J, js_Value *v);
+void jsV_toprimitive(js_State *J, js_Value *v, int preferred);
 
 const char *js_itoa(char buf[32], unsigned int a);
 double js_stringtofloat(const char *s, char **ep);