Files
mujs/jsarray.c
Tor Andersson b06a5e9b02 Bug 704238: Limit max string lengths.
Check string length when creating strings to not exceed a maximum,
so we avoid integer overflows when concatenating strings.

The string limit must be small enough that we'll not integer overflow
in one concatenation (A + B + 1 must not overflow while still
exceeding the string limit).

Set the limit to 64KB for now.

If we need 2GB strings then we will have to use double or int64 variables
when calculating string lengths.
2021-09-07 18:02:28 +02:00

764 lines
14 KiB
C

#include "jsi.h"
#include "jsvalue.h"
#include "jsbuiltin.h"
int js_getlength(js_State *J, int idx)
{
int len;
js_getproperty(J, idx, "length");
len = js_tointeger(J, -1);
js_pop(J, 1);
return len;
}
void js_setlength(js_State *J, int idx, int len)
{
js_pushnumber(J, len);
js_setproperty(J, idx < 0 ? idx - 1 : idx, "length");
}
int js_hasindex(js_State *J, int idx, int i)
{
char buf[32];
return js_hasproperty(J, idx, js_itoa(buf, i));
}
void js_getindex(js_State *J, int idx, int i)
{
char buf[32];
js_getproperty(J, idx, js_itoa(buf, i));
}
void js_setindex(js_State *J, int idx, int i)
{
char buf[32];
js_setproperty(J, idx, js_itoa(buf, i));
}
void js_delindex(js_State *J, int idx, int i)
{
char buf[32];
js_delproperty(J, idx, js_itoa(buf, i));
}
static void jsB_new_Array(js_State *J)
{
int i, top = js_gettop(J);
js_newarray(J);
if (top == 2) {
if (js_isnumber(J, 1)) {
js_copy(J, 1);
js_setproperty(J, -2, "length");
} else {
js_copy(J, 1);
js_setindex(J, -2, 0);
}
} else {
for (i = 1; i < top; ++i) {
js_copy(J, i);
js_setindex(J, -2, i - 1);
}
}
}
static void Ap_concat(js_State *J)
{
int i, top = js_gettop(J);
int n, k, len;
js_newarray(J);
n = 0;
for (i = 0; i < top; ++i) {
js_copy(J, i);
if (js_isarray(J, -1)) {
len = js_getlength(J, -1);
for (k = 0; k < len; ++k)
if (js_hasindex(J, -1, k))
js_setindex(J, -3, n++);
js_pop(J, 1);
} else {
js_setindex(J, -2, n++);
}
}
}
static void Ap_join(js_State *J)
{
char * volatile out = NULL;
const char *sep;
const char *r;
int seplen;
int k, n, len;
len = js_getlength(J, 0);
if (js_isdefined(J, 1)) {
sep = js_tostring(J, 1);
seplen = strlen(sep);
} else {
sep = ",";
seplen = 1;
}
if (len <= 0) {
js_pushliteral(J, "");
return;
}
if (js_try(J)) {
js_free(J, out);
js_throw(J);
}
n = 1;
for (k = 0; k < len; ++k) {
js_getindex(J, 0, k);
if (js_isundefined(J, -1) || js_isnull(J, -1))
r = "";
else
r = js_tostring(J, -1);
n += strlen(r);
if (k == 0) {
if (n > JS_STRLIMIT)
js_rangeerror(J, "invalid string length");
out = js_malloc(J, (int)n);
strcpy(out, r);
} else {
n += seplen;
if (n > JS_STRLIMIT)
js_rangeerror(J, "invalid string length");
out = js_realloc(J, out, (int)n);
strcat(out, sep);
strcat(out, r);
}
js_pop(J, 1);
}
js_pushstring(J, out);
js_endtry(J);
js_free(J, out);
}
static void Ap_pop(js_State *J)
{
int n;
n = js_getlength(J, 0);
if (n > 0) {
js_getindex(J, 0, n - 1);
js_delindex(J, 0, n - 1);
js_setlength(J, 0, n - 1);
} else {
js_setlength(J, 0, 0);
js_pushundefined(J);
}
}
static void Ap_push(js_State *J)
{
int i, top = js_gettop(J);
int n;
n = js_getlength(J, 0);
for (i = 1; i < top; ++i, ++n) {
js_copy(J, i);
js_setindex(J, 0, n);
}
js_setlength(J, 0, n);
js_pushnumber(J, n);
}
static void Ap_reverse(js_State *J)
{
int len, middle, lower;
len = js_getlength(J, 0);
middle = len / 2;
lower = 0;
while (lower != middle) {
int upper = len - lower - 1;
int haslower = js_hasindex(J, 0, lower);
int hasupper = js_hasindex(J, 0, upper);
if (haslower && hasupper) {
js_setindex(J, 0, lower);
js_setindex(J, 0, upper);
} else if (hasupper) {
js_setindex(J, 0, lower);
js_delindex(J, 0, upper);
} else if (haslower) {
js_setindex(J, 0, upper);
js_delindex(J, 0, lower);
}
++lower;
}
js_copy(J, 0);
}
static void Ap_shift(js_State *J)
{
int k, len;
len = js_getlength(J, 0);
if (len == 0) {
js_setlength(J, 0, 0);
js_pushundefined(J);
return;
}
js_getindex(J, 0, 0);
for (k = 1; k < len; ++k) {
if (js_hasindex(J, 0, k))
js_setindex(J, 0, k - 1);
else
js_delindex(J, 0, k - 1);
}
js_delindex(J, 0, len - 1);
js_setlength(J, 0, len - 1);
}
static void Ap_slice(js_State *J)
{
int len, s, e, n;
double sv, ev;
js_newarray(J);
len = js_getlength(J, 0);
sv = js_tointeger(J, 1);
ev = js_isdefined(J, 2) ? js_tointeger(J, 2) : len;
if (sv < 0) sv = sv + len;
if (ev < 0) ev = ev + len;
s = sv < 0 ? 0 : sv > len ? len : sv;
e = ev < 0 ? 0 : ev > len ? len : ev;
for (n = 0; s < e; ++s, ++n)
if (js_hasindex(J, 0, s))
js_setindex(J, -2, n);
}
struct sortslot {
js_Value v;
js_State *J;
};
static int sortcmp(const void *avoid, const void *bvoid)
{
const struct sortslot *aslot = avoid, *bslot = bvoid;
const js_Value *a = &aslot->v, *b = &bslot->v;
js_State *J = aslot->J;
const char *sx, *sy;
double v;
int c;
int unx = (a->type == JS_TUNDEFINED);
int uny = (b->type == JS_TUNDEFINED);
if (unx) return !uny;
if (uny) return -1;
if (js_iscallable(J, 1)) {
js_copy(J, 1); /* copy function */
js_pushundefined(J);
js_pushvalue(J, *a);
js_pushvalue(J, *b);
js_call(J, 2);
v = js_tonumber(J, -1);
c = (v == 0) ? 0 : (v < 0) ? -1 : 1;
js_pop(J, 1);
} else {
js_pushvalue(J, *a);
js_pushvalue(J, *b);
sx = js_tostring(J, -2);
sy = js_tostring(J, -1);
c = strcmp(sx, sy);
js_pop(J, 2);
}
return c;
}
static void Ap_sort(js_State *J)
{
struct sortslot * volatile array = NULL;
int i, n, len;
len = js_getlength(J, 0);
if (len <= 0) {
js_copy(J, 0);
return;
}
if (len >= INT_MAX / (int)sizeof(*array))
js_rangeerror(J, "array is too large to sort");
/* Holding objects where the GC cannot see them is illegal, but if we
* don't allow the GC to run we can use qsort() on a temporary array of
* js_Values for fast sorting.
*/
++J->gcpause;
if (js_try(J)) {
--J->gcpause;
js_free(J, array);
js_throw(J);
}
array = js_malloc(J, len * sizeof *array);
n = 0;
for (i = 0; i < len; ++i) {
if (js_hasindex(J, 0, i)) {
array[n].v = *js_tovalue(J, -1);
array[n].J = J;
js_pop(J, 1);
++n;
}
}
qsort(array, n, sizeof *array, sortcmp);
for (i = 0; i < n; ++i) {
js_pushvalue(J, array[i].v);
js_setindex(J, 0, i);
}
for (i = n; i < len; ++i) {
js_delindex(J, 0, i);
}
--J->gcpause;
js_endtry(J);
js_free(J, array);
js_copy(J, 0);
}
static void Ap_splice(js_State *J)
{
int top = js_gettop(J);
int len, start, del, add, k;
double f;
js_newarray(J);
len = js_getlength(J, 0);
f = js_tointeger(J, 1);
if (f < 0) f = f + len;
start = f < 0 ? 0 : f > len ? len : f;
f = js_tointeger(J, 2);
del = f < 0 ? 0 : f > len - start ? len - start : f;
/* copy deleted items to return array */
for (k = 0; k < del; ++k)
if (js_hasindex(J, 0, start + k))
js_setindex(J, -2, k);
js_setlength(J, -1, del);
/* shift the tail to resize the hole left by deleted items */
add = top - 3;
if (add < del) {
for (k = start; k < len - del; ++k) {
if (js_hasindex(J, 0, k + del))
js_setindex(J, 0, k + add);
else
js_delindex(J, 0, k + add);
}
for (k = len; k > len - del + add; --k)
js_delindex(J, 0, k - 1);
} else if (add > del) {
for (k = len - del; k > start; --k) {
if (js_hasindex(J, 0, k + del - 1))
js_setindex(J, 0, k + add - 1);
else
js_delindex(J, 0, k + add - 1);
}
}
/* copy new items into the hole */
for (k = 0; k < add; ++k) {
js_copy(J, 3 + k);
js_setindex(J, 0, start + k);
}
js_setlength(J, 0, len - del + add);
}
static void Ap_unshift(js_State *J)
{
int i, top = js_gettop(J);
int k, len;
len = js_getlength(J, 0);
for (k = len; k > 0; --k) {
int from = k - 1;
int to = k + top - 2;
if (js_hasindex(J, 0, from))
js_setindex(J, 0, to);
else
js_delindex(J, 0, to);
}
for (i = 1; i < top; ++i) {
js_copy(J, i);
js_setindex(J, 0, i - 1);
}
js_setlength(J, 0, len + top - 1);
js_pushnumber(J, len + top - 1);
}
static void Ap_toString(js_State *J)
{
if (!js_iscoercible(J, 0))
js_typeerror(J, "'this' is not an object");
js_getproperty(J, 0, "join");
if (!js_iscallable(J, -1)) {
js_pop(J, 1);
// TODO: call Object.prototype.toString implementation directly
js_getglobal(J, "Object");
js_getproperty(J, -1, "prototype");
js_rot2pop1(J);
js_getproperty(J, -1, "toString");
js_rot2pop1(J);
}
js_copy(J, 0);
js_call(J, 0);
}
static void Ap_indexOf(js_State *J)
{
int k, len, from;
len = js_getlength(J, 0);
from = js_isdefined(J, 2) ? js_tointeger(J, 2) : 0;
if (from < 0) from = len + from;
if (from < 0) from = 0;
js_copy(J, 1);
for (k = from; k < len; ++k) {
if (js_hasindex(J, 0, k)) {
if (js_strictequal(J)) {
js_pushnumber(J, k);
return;
}
js_pop(J, 1);
}
}
js_pushnumber(J, -1);
}
static void Ap_lastIndexOf(js_State *J)
{
int k, len, from;
len = js_getlength(J, 0);
from = js_isdefined(J, 2) ? js_tointeger(J, 2) : len - 1;
if (from > len - 1) from = len - 1;
if (from < 0) from = len + from;
js_copy(J, 1);
for (k = from; k >= 0; --k) {
if (js_hasindex(J, 0, k)) {
if (js_strictequal(J)) {
js_pushnumber(J, k);
return;
}
js_pop(J, 1);
}
}
js_pushnumber(J, -1);
}
static void Ap_every(js_State *J)
{
int hasthis = js_gettop(J) >= 3;
int k, len;
if (!js_iscallable(J, 1))
js_typeerror(J, "callback is not a function");
len = js_getlength(J, 0);
for (k = 0; k < len; ++k) {
if (js_hasindex(J, 0, k)) {
js_copy(J, 1);
if (hasthis)
js_copy(J, 2);
else
js_pushundefined(J);
js_copy(J, -3);
js_pushnumber(J, k);
js_copy(J, 0);
js_call(J, 3);
if (!js_toboolean(J, -1))
return;
js_pop(J, 2);
}
}
js_pushboolean(J, 1);
}
static void Ap_some(js_State *J)
{
int hasthis = js_gettop(J) >= 3;
int k, len;
if (!js_iscallable(J, 1))
js_typeerror(J, "callback is not a function");
len = js_getlength(J, 0);
for (k = 0; k < len; ++k) {
if (js_hasindex(J, 0, k)) {
js_copy(J, 1);
if (hasthis)
js_copy(J, 2);
else
js_pushundefined(J);
js_copy(J, -3);
js_pushnumber(J, k);
js_copy(J, 0);
js_call(J, 3);
if (js_toboolean(J, -1))
return;
js_pop(J, 2);
}
}
js_pushboolean(J, 0);
}
static void Ap_forEach(js_State *J)
{
int hasthis = js_gettop(J) >= 3;
int k, len;
if (!js_iscallable(J, 1))
js_typeerror(J, "callback is not a function");
len = js_getlength(J, 0);
for (k = 0; k < len; ++k) {
if (js_hasindex(J, 0, k)) {
js_copy(J, 1);
if (hasthis)
js_copy(J, 2);
else
js_pushundefined(J);
js_copy(J, -3);
js_pushnumber(J, k);
js_copy(J, 0);
js_call(J, 3);
js_pop(J, 2);
}
}
js_pushundefined(J);
}
static void Ap_map(js_State *J)
{
int hasthis = js_gettop(J) >= 3;
int k, len;
if (!js_iscallable(J, 1))
js_typeerror(J, "callback is not a function");
js_newarray(J);
len = js_getlength(J, 0);
for (k = 0; k < len; ++k) {
if (js_hasindex(J, 0, k)) {
js_copy(J, 1);
if (hasthis)
js_copy(J, 2);
else
js_pushundefined(J);
js_copy(J, -3);
js_pushnumber(J, k);
js_copy(J, 0);
js_call(J, 3);
js_setindex(J, -3, k);
js_pop(J, 1);
}
}
}
static void Ap_filter(js_State *J)
{
int hasthis = js_gettop(J) >= 3;
int k, to, len;
if (!js_iscallable(J, 1))
js_typeerror(J, "callback is not a function");
js_newarray(J);
to = 0;
len = js_getlength(J, 0);
for (k = 0; k < len; ++k) {
if (js_hasindex(J, 0, k)) {
js_copy(J, 1);
if (hasthis)
js_copy(J, 2);
else
js_pushundefined(J);
js_copy(J, -3);
js_pushnumber(J, k);
js_copy(J, 0);
js_call(J, 3);
if (js_toboolean(J, -1)) {
js_pop(J, 1);
js_setindex(J, -2, to++);
} else {
js_pop(J, 2);
}
}
}
}
static void Ap_reduce(js_State *J)
{
int hasinitial = js_gettop(J) >= 3;
int k, len;
if (!js_iscallable(J, 1))
js_typeerror(J, "callback is not a function");
len = js_getlength(J, 0);
k = 0;
if (len == 0 && !hasinitial)
js_typeerror(J, "no initial value");
/* initial value of accumulator */
if (hasinitial)
js_copy(J, 2);
else {
while (k < len)
if (js_hasindex(J, 0, k++))
break;
if (k == len)
js_typeerror(J, "no initial value");
}
while (k < len) {
if (js_hasindex(J, 0, k)) {
js_copy(J, 1);
js_pushundefined(J);
js_rot(J, 4); /* accumulator on top */
js_rot(J, 4); /* property on top */
js_pushnumber(J, k);
js_copy(J, 0);
js_call(J, 4); /* calculate new accumulator */
}
++k;
}
/* return accumulator */
}
static void Ap_reduceRight(js_State *J)
{
int hasinitial = js_gettop(J) >= 3;
int k, len;
if (!js_iscallable(J, 1))
js_typeerror(J, "callback is not a function");
len = js_getlength(J, 0);
k = len - 1;
if (len == 0 && !hasinitial)
js_typeerror(J, "no initial value");
/* initial value of accumulator */
if (hasinitial)
js_copy(J, 2);
else {
while (k >= 0)
if (js_hasindex(J, 0, k--))
break;
if (k < 0)
js_typeerror(J, "no initial value");
}
while (k >= 0) {
if (js_hasindex(J, 0, k)) {
js_copy(J, 1);
js_pushundefined(J);
js_rot(J, 4); /* accumulator on top */
js_rot(J, 4); /* property on top */
js_pushnumber(J, k);
js_copy(J, 0);
js_call(J, 4); /* calculate new accumulator */
}
--k;
}
/* return accumulator */
}
static void A_isArray(js_State *J)
{
if (js_isobject(J, 1)) {
js_Object *T = js_toobject(J, 1);
js_pushboolean(J, T->type == JS_CARRAY);
} else {
js_pushboolean(J, 0);
}
}
void jsB_initarray(js_State *J)
{
js_pushobject(J, J->Array_prototype);
{
jsB_propf(J, "Array.prototype.toString", Ap_toString, 0);
jsB_propf(J, "Array.prototype.concat", Ap_concat, 0); /* 1 */
jsB_propf(J, "Array.prototype.join", Ap_join, 1);
jsB_propf(J, "Array.prototype.pop", Ap_pop, 0);
jsB_propf(J, "Array.prototype.push", Ap_push, 0); /* 1 */
jsB_propf(J, "Array.prototype.reverse", Ap_reverse, 0);
jsB_propf(J, "Array.prototype.shift", Ap_shift, 0);
jsB_propf(J, "Array.prototype.slice", Ap_slice, 2);
jsB_propf(J, "Array.prototype.sort", Ap_sort, 1);
jsB_propf(J, "Array.prototype.splice", Ap_splice, 0); /* 2 */
jsB_propf(J, "Array.prototype.unshift", Ap_unshift, 0); /* 1 */
/* ES5 */
jsB_propf(J, "Array.prototype.indexOf", Ap_indexOf, 1);
jsB_propf(J, "Array.prototype.lastIndexOf", Ap_lastIndexOf, 1);
jsB_propf(J, "Array.prototype.every", Ap_every, 1);
jsB_propf(J, "Array.prototype.some", Ap_some, 1);
jsB_propf(J, "Array.prototype.forEach", Ap_forEach, 1);
jsB_propf(J, "Array.prototype.map", Ap_map, 1);
jsB_propf(J, "Array.prototype.filter", Ap_filter, 1);
jsB_propf(J, "Array.prototype.reduce", Ap_reduce, 1);
jsB_propf(J, "Array.prototype.reduceRight", Ap_reduceRight, 1);
}
js_newcconstructor(J, jsB_new_Array, jsB_new_Array, "Array", 0); /* 1 */
{
/* ES5 */
jsB_propf(J, "Array.isArray", A_isArray, 1);
}
js_defglobal(J, "Array", JS_DONTENUM);
}