jscript: Implement reviver argument for JSON.parse.

Signed-off-by: Gabriel Ivăncescu <gabrielopcode@gmail.com>
This commit is contained in:
Gabriel Ivăncescu 2022-10-14 22:14:43 +03:00 committed by Alexandre Julliard
parent a4697901cc
commit 2dcc5a7026
4 changed files with 173 additions and 7 deletions

View file

@ -93,7 +93,7 @@ static HRESULT set_length(jsdisp_t *obj, DWORD length)
return jsdisp_propput_name(obj, L"length", jsval_number(length));
}
static WCHAR *idx_to_str(DWORD idx, WCHAR *ptr)
WCHAR *idx_to_str(DWORD idx, WCHAR *ptr)
{
if(!idx) {
*ptr = '0';

View file

@ -318,6 +318,7 @@ HRESULT variant_date_to_string(script_ctx_t*,double,jsstr_t**) DECLSPEC_HIDDEN;
HRESULT decode_source(WCHAR*) DECLSPEC_HIDDEN;
HRESULT double_to_string(double,jsstr_t**) DECLSPEC_HIDDEN;
WCHAR *idx_to_str(DWORD,WCHAR*) DECLSPEC_HIDDEN;
static inline BOOL is_digit(WCHAR c)
{

View file

@ -267,20 +267,104 @@ static HRESULT parse_json_value(json_parse_ctx_t *ctx, jsval_t *r)
return E_FAIL;
}
struct transform_json_object_ctx
{
script_ctx_t *ctx;
IDispatch *reviver;
HRESULT hres;
};
static jsval_t transform_json_object(struct transform_json_object_ctx *proc_ctx, jsdisp_t *holder, jsstr_t *name)
{
jsval_t res, args[2];
const WCHAR *str;
if(!(str = jsstr_flatten(name)))
proc_ctx->hres = E_OUTOFMEMORY;
else
proc_ctx->hres = jsdisp_propget_name(holder, str, &args[1]);
if(FAILED(proc_ctx->hres))
return jsval_undefined();
if(is_object_instance(args[1])) {
jsdisp_t *obj = to_jsdisp(get_object(args[1]));
jsstr_t *jsstr;
DISPID id;
BOOL b;
if(!obj) {
FIXME("non-JS obj in JSON object: %p\n", get_object(args[1]));
proc_ctx->hres = E_NOTIMPL;
return jsval_undefined();
}else if(is_class(obj, JSCLASS_ARRAY)) {
unsigned i, length = array_get_length(obj);
WCHAR buf[14], *buf_end;
buf_end = buf + ARRAY_SIZE(buf) - 1;
*buf_end-- = 0;
for(i = 0; i < length; i++) {
str = idx_to_str(i, buf_end);
if(!(jsstr = jsstr_alloc(str))) {
proc_ctx->hres = E_OUTOFMEMORY;
return jsval_undefined();
}
res = transform_json_object(proc_ctx, obj, jsstr);
jsstr_release(jsstr);
if(is_undefined(res)) {
if(FAILED(proc_ctx->hres))
return jsval_undefined();
if(FAILED(jsdisp_get_id(obj, str, 0, &id)))
continue;
proc_ctx->hres = disp_delete((IDispatch*)&obj->IDispatchEx_iface, id, &b);
}else {
proc_ctx->hres = jsdisp_define_data_property(obj, str, PROPF_WRITABLE | PROPF_ENUMERABLE | PROPF_CONFIGURABLE, res);
jsval_release(res);
}
if(FAILED(proc_ctx->hres))
return jsval_undefined();
}
}else {
id = DISPID_STARTENUM;
for(;;) {
proc_ctx->hres = jsdisp_next_prop(obj, id, JSDISP_ENUM_OWN_ENUMERABLE, &id);
if(proc_ctx->hres == S_FALSE)
break;
if(FAILED(proc_ctx->hres) || FAILED(proc_ctx->hres = jsdisp_get_prop_name(obj, id, &jsstr)))
return jsval_undefined();
res = transform_json_object(proc_ctx, obj, jsstr);
if(is_undefined(res)) {
if(SUCCEEDED(proc_ctx->hres))
proc_ctx->hres = disp_delete((IDispatch*)&obj->IDispatchEx_iface, id, &b);
}else {
if(!(str = jsstr_flatten(jsstr)))
proc_ctx->hres = E_OUTOFMEMORY;
else
proc_ctx->hres = jsdisp_define_data_property(obj, str, PROPF_WRITABLE | PROPF_ENUMERABLE | PROPF_CONFIGURABLE, res);
jsval_release(res);
}
jsstr_release(jsstr);
if(FAILED(proc_ctx->hres))
return jsval_undefined();
}
}
}
args[0] = jsval_string(name);
proc_ctx->hres = disp_call_value(proc_ctx->ctx, proc_ctx->reviver, (IDispatch*)&holder->IDispatchEx_iface,
DISPATCH_METHOD, ARRAY_SIZE(args), args, &res);
return FAILED(proc_ctx->hres) ? jsval_undefined() : res;
}
/* ECMA-262 5.1 Edition 15.12.2 */
static HRESULT JSON_parse(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r)
{
json_parse_ctx_t parse_ctx;
const WCHAR *buf;
jsdisp_t *root;
jsstr_t *str;
jsval_t ret;
HRESULT hres;
if(argc != 1) {
FIXME("Unsupported args\n");
return E_INVALIDARG;
}
hres = to_flat_string(ctx, argv[0], &str, &buf);
if(FAILED(hres))
return hres;
@ -293,12 +377,38 @@ static HRESULT JSON_parse(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned
hres = parse_json_value(&parse_ctx, &ret);
if(SUCCEEDED(hres) && skip_spaces(&parse_ctx)) {
FIXME("syntax error\n");
jsval_release(ret);
hres = E_FAIL;
}
jsstr_release(str);
if(FAILED(hres))
return hres;
/* FIXME: check IsCallable */
if(argc > 1 && is_object_instance(argv[1])) {
hres = create_object(ctx, NULL, &root);
if(FAILED(hres)) {
jsval_release(ret);
return hres;
}
hres = jsdisp_define_data_property(root, L"", PROPF_WRITABLE | PROPF_ENUMERABLE | PROPF_CONFIGURABLE, ret);
jsval_release(ret);
if(SUCCEEDED(hres)) {
struct transform_json_object_ctx proc_ctx = { ctx, get_object(argv[1]), S_OK };
if(!(str = jsstr_alloc(L"")))
hres = E_OUTOFMEMORY;
else {
ret = transform_json_object(&proc_ctx, root, str);
jsstr_release(str);
hres = proc_ctx.hres;
}
}
jsdisp_release(root);
if(FAILED(hres))
return hres;
}
if(r)
*r = ret;
else

View file

@ -1960,7 +1960,7 @@ ok(isNaN(tmp), "Math.tan(-Infinity) is not NaN");
[[[,2,undefined,3,{prop:0},],undefined," "],"[\n null,\n 2,\n null,\n 3,\n {\n \"prop\": 0\n },\n null\n]"]
];
var i, s, v;
var i, s, v, t;
for(i=0; i < stringify_tests.length; i++) {
s = JSON.stringify.apply(null, stringify_tests[i][0]);
@ -2043,6 +2043,61 @@ ok(isNaN(tmp), "Math.tan(-Infinity) is not NaN");
v = JSON.parse(parse_tests[i][0]);
ok(json_cmp(v, parse_tests[i][1]), "parse[" + i + "] returned " + v + ", expected " + parse_tests[i][1]);
}
v = [ [-1, "b"], {"length": -2, "0": -4, "1": -5}, [{}], [{"x": [null]}] ];
s =
'{' +
'"foo": true,' +
'"bar": [],' +
'"baz": "remove_me",' +
'"obj": {' +
' "arr": [ [1, "b"], {"length": 2, "0": 4, "1": 5}, [{}], [{"x": [null]}] ],' +
' "": "empty"' +
'},' +
'"last": false' +
'}';
o = JSON.parse(s), t = JSON.parse(s), i = new Object();
i[""] = t;
delete t.baz; /* baz gets removed */
t.obj.arr = v; /* has negative values */
var walk_expect = [
[ o, "foo", true ],
[ o, "bar", [] ],
[ o, "baz", "remove_me" ],
[ [1, "b"], "0", 1 ],
[ [-1, "b"], "1", "b" ],
[ [ [-1, "b"], {"length": 2, "0": 4, "1": 5}, [{}], [{"x": [null]}] ], "0", [-1, "b"] ],
[ {"length": 2, "0": 4, "1": 5}, "length", 2 ],
[ {"length": -2, "0": 4, "1": 5}, "0", 4 ],
[ {"length": -2, "0": -4, "1": 5}, "1", 5 ],
[ v, "1", {"length": -2, "0": -4, "1": -5} ],
[ [{}], "0", {} ],
[ v, "2", [{}] ],
[ [null], "0", null ],
[ {"x": [null]}, "x", [null] ],
[ [{"x": [null]}], "0", {"x": [null]} ],
[ v, "3", [{"x": [null]}] ],
[ { "arr": v, "": "empty" }, "arr", v ],
[ { "arr": v, "": "empty" }, "", "empty" ],
[ t, "obj", { "arr": v, "": "empty" } ],
[ t, "last", false ],
[ i, "", t ]
];
i = 0;
v = JSON.parse(s, function(prop, value) {
var a = [this, prop, value];
ok(json_cmp(a, walk_expect[i]), "[walk step " + i + "] got [" + a + "], expected [" + walk_expect[i] + "]");
i++;
return (typeof value === 'number') ? -value : (value === "remove_me" ? undefined : value);
});
ok(i === walk_expect.length, "parse with reviver walked " + i + " steps, expected " + walk_expect.length);
ok(json_cmp(v, t), "parse with reviver returned wrong object");
v = JSON.parse('true', function(prop, value) { return prop === "" ? undefined : value; });
ok(v === undefined, "parse with reviver removing last prop returned " + v);
v = JSON.parse('true', function(prop, value) { return prop === "" ? false : value; });
ok(v === false, "parse with reviver setting last prop to false returned " + v);
})();
var func = function (a) {