From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
We store the hres in the ctx to minimize the amount of parameters to the recursive function, since it propagates up the call stack anyway. --- dlls/jscript/array.c | 2 +- dlls/jscript/jscript.h | 1 + dlls/jscript/json.c | 120 ++++++++++++++++++++++++++++++++++++-- dlls/jscript/tests/api.js | 57 +++++++++++++++++- 4 files changed, 173 insertions(+), 7 deletions(-)
diff --git a/dlls/jscript/array.c b/dlls/jscript/array.c index 5a8131f092c..d69c9f7edf4 100644 --- a/dlls/jscript/array.c +++ b/dlls/jscript/array.c @@ -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'; diff --git a/dlls/jscript/jscript.h b/dlls/jscript/jscript.h index 0f8baea0188..757abf8aa6d 100644 --- a/dlls/jscript/jscript.h +++ b/dlls/jscript/jscript.h @@ -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) { diff --git a/dlls/jscript/json.c b/dlls/jscript/json.c index dfcc39daa2a..d354768b486 100644 --- a/dlls/jscript/json.c +++ b/dlls/jscript/json.c @@ -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 diff --git a/dlls/jscript/tests/api.js b/dlls/jscript/tests/api.js index e47e2759abb..c33791f1fb1 100644 --- a/dlls/jscript/tests/api.js +++ b/dlls/jscript/tests/api.js @@ -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) {