diff --git a/test/built-ins/JSON/isRawJSON/basic.js b/test/built-ins/JSON/isRawJSON/basic.js new file mode 100644 index 00000000000..2aa1054a039 --- /dev/null +++ b/test/built-ins/JSON/isRawJSON/basic.js @@ -0,0 +1,25 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-json.israwjson +description: Basic functionality of JSON.isRawJSON() +info: | + JSON.isRawJSON ( O ) + + 1. If Type(O) is Object and O has an [[IsRawJSON]] internal slot, return true. + 2. Return false. + +features: [json-parse-with-source] +---*/ + +const values = [1, 1.1, null, false, true, '123']; +for (const value of values) { + assert(!JSON.isRawJSON(value)); + assert(JSON.isRawJSON(JSON.rawJSON(value))); +} +assert(!JSON.isRawJSON(undefined)); +assert(!JSON.isRawJSON(Symbol('123'))); +assert(!JSON.isRawJSON([])); +assert(!JSON.isRawJSON({})); +assert(!JSON.isRawJSON({ rawJSON: '123' })); diff --git a/test/built-ins/JSON/parse/reviver-call-args-after-forward-modification.js b/test/built-ins/JSON/parse/reviver-call-args-after-forward-modification.js new file mode 100644 index 00000000000..b7e2892c375 --- /dev/null +++ b/test/built-ins/JSON/parse/reviver-call-args-after-forward-modification.js @@ -0,0 +1,47 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-json.parse +description: > + JSON.parse reviver is called with the correct arguments when the object is + modified +includes: [compareArray.js] +features: [json-parse-with-source] +---*/ + +// Test Array append +{ + let log = []; + const o = JSON.parse('[1,[]]', function reviver(k, v, { source }) { + log.push(`key: |${k}| value: ${JSON.stringify(v)} source: |${source}|`); + if (v === 1) { + this[1].push('barf'); + } + return this[k]; + }); + assert.compareArray(log, [ + 'key: |0| value: 1 source: |1|', + 'key: |0| value: "barf" source: |undefined|', + 'key: |1| value: ["barf"] source: |undefined|', + 'key: || value: [1,["barf"]] source: |undefined|', + ]); +} + +// Test Object add property +{ + let log = []; + const o = JSON.parse('{"p":1,"q":{}}', function (k, v, { source }) { + log.push(`key: |${k}| value: ${JSON.stringify(v)} source: |${source}|`); + if (v === 1) { + this.q.added = 'barf'; + } + return this[k]; + }); + assert.compareArray(log, [ + 'key: |p| value: 1 source: |1|', + 'key: |added| value: "barf" source: |undefined|', + 'key: |q| value: {"added":"barf"} source: |undefined|', + 'key: || value: {"p":1,"q":{"added":"barf"}} source: |undefined|', + ]); +} diff --git a/test/built-ins/JSON/parse/reviver-context-source-array-literal.js b/test/built-ins/JSON/parse/reviver-context-source-array-literal.js new file mode 100644 index 00000000000..45b2aa34314 --- /dev/null +++ b/test/built-ins/JSON/parse/reviver-context-source-array-literal.js @@ -0,0 +1,58 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-json.parse +description: > + Context argument and its source property behave as expected when parsing an + ArrayLiteral JSON string +includes: [compareArray.js, propertyHelper.js] +features: [json-parse-with-source] +---*/ + +function assertOnlyOwnProperties(object, props, message) { + assert.compareArray(Object.getOwnPropertyNames(object), props, `${message}: object should have no other properties than expected`); + assert.compareArray(Object.getOwnPropertySymbols(object), [], `${message}: object should have no own symbol properties`); +} + +function reviverWithExpectedSources(expectedSources) { + let i = 0; + return function reviver(key, value, context) { + assert.sameValue(typeof context, "object", "context should be an object"); + assert.sameValue(Object.getPrototypeOf(context), Object.prototype, "context should be a plain object"); + if (expectedSources[i] !== undefined) { + assertOnlyOwnProperties(context, ["source"], + "the JSON value is a primitve value, its context should only have a source property"); + verifyProperty(context, "source", { + value: expectedSources[i++], + configurable: true, + enumerable: true, + writable: true, + }, { restore: true }); + } else { + assertOnlyOwnProperties(context, [], + "the JSON value is an Array or Object, its context should have no property"); + i++; + } + return value; + }; +} + +assert.compareArray(JSON.parse('[1.0]', reviverWithExpectedSources(['1.0'])), [1]); +assert.compareArray( + JSON.parse('[1.1]', reviverWithExpectedSources(['1.1'])), + [1.1] +); +assert.compareArray(JSON.parse('[]', reviverWithExpectedSources([])), []); + +const longArray = JSON.parse( + '[1, "2", true, null, {"x": 1, "y": 1}]', + reviverWithExpectedSources(['1', '"2"', 'true', 'null', '1', '1']) +); +assert.sameValue(longArray[0], 1, "array, element 0"); +assert.sameValue(longArray[1], "2", "array, element 1"); +assert.sameValue(longArray[2], true, "array, element 2"); +assert.sameValue(longArray[3], null, "array, element 3"); +assertOnlyOwnProperties(longArray[4], ["x", "y"], "array, element 5"); +assert.sameValue(longArray[4].x, 1, "array, element 5, prop x"); +assert.sameValue(longArray[4].y, 1, "array, element 5, prop y"); diff --git a/test/built-ins/JSON/parse/reviver-context-source-object-literal.js b/test/built-ins/JSON/parse/reviver-context-source-object-literal.js new file mode 100644 index 00000000000..5fcf01df30d --- /dev/null +++ b/test/built-ins/JSON/parse/reviver-context-source-object-literal.js @@ -0,0 +1,73 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-json.parse +description: > + Context argument and its source property behave as expected when parsing an + ObjectLiteral JSON string +includes: [compareArray.js, propertyHelper.js] +features: [json-parse-with-source] +---*/ + +function assertOnlyOwnProperties(object, props, message) { + assert.compareArray(Object.getOwnPropertyNames(object), props, `${message}: object should have no other properties than expected`); + assert.compareArray(Object.getOwnPropertySymbols(object), [], `${message}: object should have no own symbol properties`); +} + +function reviverWithExpectedSources(expectedSources) { + let i = 0; + return function reviver(key, value, context) { + assert.sameValue(typeof context, "object", "context should be an object"); + assert.sameValue(Object.getPrototypeOf(context), Object.prototype, "context should be a plain object"); + if (expectedSources[i] !== undefined) { + assertOnlyOwnProperties(context, ["source"], + "the JSON value is a primitve value, its context should only have a source property"); + verifyProperty(context, "source", { + value: expectedSources[i++], + configurable: true, + enumerable: true, + writable: true, + }, { restore: true }); + } else { + assertOnlyOwnProperties(context, [], + "the JSON value is an Array or Object, its context should have no property"); + i++; + } + return value; + }; +} + +assertOnlyOwnProperties( + JSON.parse('{}', reviverWithExpectedSources([])), + [], + "empty object" +); + +const singleProp = JSON.parse('{"42":37}', reviverWithExpectedSources(['37'])); +assertOnlyOwnProperties(singleProp, ["42"], "single numeric property key"); +assert.sameValue(singleProp[42], 37, "value of single numeric property key"); + +const multipleProps = JSON.parse('{"x": 1, "y": 2}', reviverWithExpectedSources(['1', '2'])); +assertOnlyOwnProperties(multipleProps, ["x", "y"], "multiple properties"); +assert.sameValue(multipleProps.x, 1, "multiple properties, value of x"); +assert.sameValue(multipleProps.y, 2, "multiple properties, value of y"); + +// undefined means the json value is JSObject or JSArray and the passed +// context to the reviver function has no source property. +const arrayProps = JSON.parse( + '{"x": [1,2], "y": [2,3]}', + reviverWithExpectedSources(['1', '2', undefined, '2', '3', undefined]) +); +assertOnlyOwnProperties(arrayProps, ["x", "y"], "array-valued properties"); +assert.compareArray(arrayProps.x, [1, 2], "array-valued properties, value of x"); +assert.compareArray(arrayProps.y, [2, 3], "array-valued properties, value of y"); + +const objectProps = JSON.parse( + '{"x": {"x": 1, "y": 2}}', + reviverWithExpectedSources(['1', '2', undefined, undefined]) +); +assertOnlyOwnProperties(objectProps, ["x"], "object-valued properties"); +assertOnlyOwnProperties(objectProps.x, ["x", "y"], "object-valued properties, value of x"); +assert.sameValue(objectProps.x.x, 1, "object-valued properties, value of x.x"); +assert.sameValue(objectProps.x.y, 2, "object-valued properties, value of x.y"); diff --git a/test/built-ins/JSON/parse/reviver-context-source-primitive-literal.js b/test/built-ins/JSON/parse/reviver-context-source-primitive-literal.js new file mode 100644 index 00000000000..b2d324f67e6 --- /dev/null +++ b/test/built-ins/JSON/parse/reviver-context-source-primitive-literal.js @@ -0,0 +1,89 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-json.parse +description: > + Context argument and its source property behave as expected when parsing a + NumericLiteral, NullLiteral, BoolLiteral, or StringLiteral JSON string +includes: [compareArray.js, propertyHelper.js] +features: [json-parse-with-source] +---*/ + +function assertOnlyOwnProperties(object, props, message) { + assert.compareArray(Object.getOwnPropertyNames(object), props, `${message}: object should have no other properties than expected`); + assert.compareArray(Object.getOwnPropertySymbols(object), [], `${message}: object should have no own symbol properties`); +} + +function reviverWithExpectedSources(expectedSources) { + let i = 0; + return function reviver(key, value, context) { + assert.sameValue(typeof context, "object", "context should be an object"); + assert.sameValue(Object.getPrototypeOf(context), Object.prototype, "context should be a plain object"); + if (expectedSources[i] !== undefined) { + assertOnlyOwnProperties(context, ["source"], + "the JSON value is a primitve value, its context should only have a source property"); + verifyProperty(context, "source", { + value: expectedSources[i++], + configurable: true, + enumerable: true, + writable: true, + }, { restore: true }); + } else { + assertOnlyOwnProperties(context, [], + "the JSON value is an Array or Object, its context should have no property"); + i++; + } + return value; + }; +} + +assert.sameValue(1, JSON.parse('1', reviverWithExpectedSources(['1']))); +assert.sameValue(1.1, JSON.parse('1.1', reviverWithExpectedSources(['1.1']))); +assert.sameValue(-1, JSON.parse('-1', reviverWithExpectedSources(['-1']))); +assert.sameValue( + -1.1, + JSON.parse('-1.1', reviverWithExpectedSources(['-1.1'])) +); +assert.sameValue( + 11, + JSON.parse('1.1e1', reviverWithExpectedSources(['1.1e1'])) +); +assert.sameValue( + 11, + JSON.parse('1.1e+1', reviverWithExpectedSources(['1.1e+1'])) +); +assert.sameValue( + 0.11, + JSON.parse('1.1e-1', reviverWithExpectedSources(['1.1e-1'])) +); +assert.sameValue( + 11, + JSON.parse('1.1E1', reviverWithExpectedSources(['1.1E1'])) +); +assert.sameValue( + 11, + JSON.parse('1.1E+1', reviverWithExpectedSources(['1.1E+1'])) +); +assert.sameValue( + 0.11, + JSON.parse('1.1E-1', reviverWithExpectedSources(['1.1E-1'])) +); + +// Test NullLiteral, BoolLiteral, StringLiteral +assert.sameValue( + JSON.parse('null', reviverWithExpectedSources(['null'])), + null +); +assert.sameValue( + JSON.parse('true', reviverWithExpectedSources(['true'])), + true +); +assert.sameValue( + JSON.parse('false', reviverWithExpectedSources(['false'])), + false +); +assert.sameValue( + JSON.parse('"foo"', reviverWithExpectedSources(['"foo"'])), + "foo" +); diff --git a/test/built-ins/JSON/parse/reviver-forward-modifies-object.js b/test/built-ins/JSON/parse/reviver-forward-modifies-object.js new file mode 100644 index 00000000000..5577009a74c --- /dev/null +++ b/test/built-ins/JSON/parse/reviver-forward-modifies-object.js @@ -0,0 +1,129 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-json.parse +description: Codepaths involving InternaliseJSONProperty behave as expected + +includes: [compareArray.js] +features: [json-parse-with-source] +---*/ + +function assertOnlyOwnProperties(object, props, message) { + assert.compareArray(Object.getOwnPropertyNames(object), props, `${message}: object should have no other properties than expected`); + assert.compareArray(Object.getOwnPropertySymbols(object), [], `${message}: object should have no own symbol properties`); +} + +const replacements = [ + 42, + ["foo"], + { foo: "bar" }, + "foo" +]; + +// Test Array forward modify +for (const replacement of replacements) { + let alreadyReplaced = false; + let expectedKeys = ["0", "1", ""]; + // if the replacement is an object, add its keys to the expected keys + if (typeof replacement === "object") { + expectedKeys.splice(1, 0, ...Object.keys(replacement)); + } + const o = JSON.parse("[1, 2]", function (k, v, { source }) { + assert.sameValue(k, expectedKeys.shift()); + if (k === "0") { + if (!alreadyReplaced) { + this[1] = replacement; + alreadyReplaced = true; + } + } else if (k !== "") { + assert.sameValue(source, undefined); + } + return this[k]; + }); + assert.sameValue(expectedKeys.length, 0); + assert.compareArray(o, [1, replacement], `array forward-modified with ${replacement}`); +} + +function assertOnlyOwnProperties(object, props, message) { + assert.compareArray(Object.getOwnPropertyNames(object), props, `${message}: object should have no other properties than expected`); + assert.compareArray(Object.getOwnPropertySymbols(object), [], `${message}: object should have no own symbol properties`); +} + +// Test Object forward modify +for (const replacement of replacements) { + let alreadyReplaced = false; + let expectedKeys = ["p", "q", ""]; + if (typeof replacement === "object") { + expectedKeys.splice(1, 0, ...Object.keys(replacement)); + } + const o = JSON.parse('{"p":1, "q":2}', function (k, v, { source }) { + assert.sameValue(k, expectedKeys.shift()); + if (k === 'p') { + if (!alreadyReplaced) { + this.q = replacement; + alreadyReplaced = true; + } + } else if (k !== "") { + assert.sameValue(source, undefined); + } + return this[k]; + }); + assert.sameValue(expectedKeys.length, 0); + assertOnlyOwnProperties(o, ["p", "q"], `object forward-modified with ${replacement}`); + assert.sameValue(o.p, 1, "property p should not be replaced"); + assert.sameValue(o.q, replacement, `property q should be replaced with ${replacement}`); +} + +// Test combinations of possible JSON input with multiple forward modifications + +{ + let reviverCallIndex = 0; + const expectedKeys = ["a", "b", "c", ""]; + const reviver = function(key, value, {source}) { + assert.sameValue(key, expectedKeys[reviverCallIndex++]); + if (key === "a") { + this.b = 2; + assert.sameValue(source, "0"); + } else if (key === "b") { + this.c = 3; + assert.sameValue(value, 2); + assert.sameValue(source, undefined); + } else if (key === "c") { + assert.sameValue(value, 3); + assert.sameValue(source, undefined); + } + return value; + } + const parsed = JSON.parse('{"a": 0, "b": 1, "c": [1, 2]}', reviver); + assertOnlyOwnProperties(parsed, ["a", "b", "c"], "object with forward-modified properties"); + assert.sameValue(parsed.a, 0, "'a' property should be unmodified"); + assert.sameValue(parsed.b, 2, "'b' property should be modified to 2"); + assert.sameValue(parsed.c, 3, "'c' property should be modified to 3"); +} + +{ + let reviverCallIndex = 0; + const expectedKeys = ["0", "1", "2", "3", ""]; + const reviver = function(key, value, {source}) { + assert.sameValue(key, expectedKeys[reviverCallIndex++]); + if (key === "0") { + this[1] = 3; + assert.sameValue(value, 1); + assert.sameValue(source, "1"); + } else if (key === "1") { + this[2] = 4; + assert.sameValue(value, 3); + assert.sameValue(source, undefined); + } else if(key === "2") { + this[3] = 5; + assert.sameValue(value, 4); + assert.sameValue(source, undefined); + } else if(key === "5") { + assert.sameValue(value, 5); + assert.sameValue(source, undefined); + } + return value; + } + assert.compareArray(JSON.parse('[1, 2, 3, {"a": 1}]', reviver), [1, 3, 4, 5], "array with forward-modified elements"); +} diff --git a/test/built-ins/JSON/rawJSON/basic.js b/test/built-ins/JSON/rawJSON/basic.js new file mode 100644 index 00000000000..26720737126 --- /dev/null +++ b/test/built-ins/JSON/rawJSON/basic.js @@ -0,0 +1,56 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-json.rawjson +description: Basic functionality of JSON.rawJSON(). +info: | + JSON.rawJSON ( text ) + + 1. Let jsonString be ? ToString(text). + ... + 4. Let internalSlotsList be « [[IsRawJSON]] ». + 5. Let obj be OrdinaryObjectCreate(null, internalSlotsList). + 6. Perform ! CreateDataPropertyOrThrow(obj, "rawJSON", jsonString). + 7. Perform ! SetIntegrityLevel(obj, frozen). + 8. Return obj. + +features: [json-parse-with-source] +---*/ + +assert.sameValue(JSON.stringify(JSON.rawJSON(1)), '1'); +assert.sameValue(JSON.stringify(JSON.rawJSON(1.1)), '1.1'); +assert.sameValue(JSON.stringify(JSON.rawJSON(-1)), '-1'); +assert.sameValue(JSON.stringify(JSON.rawJSON(-1.1)), '-1.1'); +assert.sameValue(JSON.stringify(JSON.rawJSON(1.1e1)), '11'); +assert.sameValue(JSON.stringify(JSON.rawJSON(1.1e-1)), '0.11'); + +assert.sameValue(JSON.stringify(JSON.rawJSON(null)), 'null'); +assert.sameValue(JSON.stringify(JSON.rawJSON(true)), 'true'); +assert.sameValue(JSON.stringify(JSON.rawJSON(false)), 'false'); +assert.sameValue(JSON.stringify(JSON.rawJSON('"foo"')), '"foo"'); + +assert.sameValue(JSON.stringify({ 42: JSON.rawJSON(37) }), '{"42":37}'); +assert.sameValue( + JSON.stringify({ x: JSON.rawJSON(1), y: JSON.rawJSON(2) }), + '{"x":1,"y":2}' +); +assert.sameValue( + JSON.stringify({ x: { x: JSON.rawJSON(1), y: JSON.rawJSON(2) } }), + '{"x":{"x":1,"y":2}}' +); + +assert.sameValue(JSON.stringify([JSON.rawJSON(1), JSON.rawJSON(1.1)]), '[1,1.1]'); +assert.sameValue( + JSON.stringify([ + JSON.rawJSON('"1"'), + JSON.rawJSON(true), + JSON.rawJSON(null), + JSON.rawJSON(false), + ]), + '["1",true,null,false]' +); +assert.sameValue( + JSON.stringify([{ x: JSON.rawJSON(1), y: JSON.rawJSON(1) }]), + '[{"x":1,"y":1}]' +); diff --git a/test/built-ins/JSON/rawJSON/bigint-raw-json-can-be-stringified.js b/test/built-ins/JSON/rawJSON/bigint-raw-json-can-be-stringified.js new file mode 100644 index 00000000000..31e3ad1541f --- /dev/null +++ b/test/built-ins/JSON/rawJSON/bigint-raw-json-can-be-stringified.js @@ -0,0 +1,30 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-json.rawjson +description: BigInt rawJSON can be stringified. +info: | + JSON.rawJSON ( text ) + + 1. Let jsonString be ? ToString(text). + ... + 4. Let internalSlotsList be « [[IsRawJSON]] ». + 5. Let obj be OrdinaryObjectCreate(null, internalSlotsList). + 6. Perform ! CreateDataPropertyOrThrow(obj, "rawJSON", jsonString). + 7. Perform ! SetIntegrityLevel(obj, frozen). + 8. Return obj. + +features: [BigInt, json-parse-with-source] +---*/ + +const tooBigForNumber = BigInt(Number.MAX_SAFE_INTEGER) + 2n; +const intToBigInt = (key, val, { source }) => + typeof val === 'number' && val % 1 === 0 ? BigInt(source) : val; +const roundTripped = JSON.parse(String(tooBigForNumber), intToBigInt); +assert.sameValue(roundTripped, tooBigForNumber); + +const bigIntToRawJSON = (key, val) => + typeof val === 'bigint' ? JSON.rawJSON(val) : val; +const embedded = JSON.stringify({ tooBigForNumber }, bigIntToRawJSON); +assert.sameValue(embedded, '{"tooBigForNumber":9007199254740993}'); diff --git a/test/built-ins/JSON/rawJSON/illegal-empty-and-start-end-chars.js b/test/built-ins/JSON/rawJSON/illegal-empty-and-start-end-chars.js new file mode 100644 index 00000000000..e7af3828ee4 --- /dev/null +++ b/test/built-ins/JSON/rawJSON/illegal-empty-and-start-end-chars.js @@ -0,0 +1,31 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-json.rawjson +description: Throw SyntaxError on empty string, or illegal start/end chars +info: | + JSON.rawJSON ( text ) + + 1. Let jsonString be ? ToString(text). + 2. Throw a SyntaxError exception if jsonString is the empty String, or if + either the first or last code unit of jsonString is any of 0x0009 + (CHARACTER TABULATION), 0x000A (LINE FEED), 0x000D (CARRIAGE RETURN), or + 0x0020 (SPACE). + +features: [json-parse-with-source] +---*/ + +const ILLEGAL_END_CHARS = ['\n', '\t', '\r', ' ']; +for (const char of ILLEGAL_END_CHARS) { + assert.throws(SyntaxError, () => { + JSON.rawJSON(`${char}123`); + }); + assert.throws(SyntaxError, () => { + JSON.rawJSON(`123${char}`); + }); +} + +assert.throws(SyntaxError, () => { + JSON.rawJSON(''); +}); diff --git a/test/built-ins/JSON/rawJSON/invalid-JSON-text.js b/test/built-ins/JSON/rawJSON/invalid-JSON-text.js new file mode 100644 index 00000000000..5fc5e3f8b73 --- /dev/null +++ b/test/built-ins/JSON/rawJSON/invalid-JSON-text.js @@ -0,0 +1,35 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-json.rawjson +description: > + Inputs not convertible to string, or convertible to invalid JSON string +info: | + JSON.rawJSON ( text ) + + 1. Let jsonString be ? ToString(text). + ... + 3. Parse StringToCodePoints(jsonString) as a JSON text as specified in + ECMA-404. Throw a SyntaxError exception if it is not a valid JSON text as + defined in that specification, or if its outermost value is an object or + array as defined in that specification. + +features: [json-parse-with-source] +---*/ + +assert.throws(TypeError, () => { + JSON.rawJSON(Symbol('123')); +}); + +assert.throws(SyntaxError, () => { + JSON.rawJSON(undefined); +}); + +assert.throws(SyntaxError, () => { + JSON.rawJSON({}); +}); + +assert.throws(SyntaxError, () => { + JSON.rawJSON([]); +}); diff --git a/test/built-ins/JSON/rawJSON/returns-expected-object.js b/test/built-ins/JSON/rawJSON/returns-expected-object.js new file mode 100644 index 00000000000..76f7165a5e0 --- /dev/null +++ b/test/built-ins/JSON/rawJSON/returns-expected-object.js @@ -0,0 +1,31 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-json.rawjson +description: Object returned from JSON.rawJSON() is the expected shape. +info: | + JSON.rawJSON ( text ) + + 5. Let _obj_ be OrdinaryObjectCreate(*null*, _internalSlotsList_). + 6. Perform ! CreateDataPropertyOrThrow(_obj_, *"rawJSON"*, _jsonString_). + ... + 8. Return _obj_. + +includes: [compareArray.js] +features: [json-parse-with-source] +---*/ + +function assertIsRawJSON(rawJSON, expectedRawJSONValue) { + assert.sameValue(Object.getPrototypeOf(rawJSON), null, "RawJSON object should have null prototype"); + assert(Object.hasOwn(rawJSON, "rawJSON"), "RawJSON object should have rawJSON own property"); + assert.compareArray(Object.getOwnPropertyNames(rawJSON), ["rawJSON"], "RawJSON object should have only rawJSON own property"); + assert.compareArray(Object.getOwnPropertySymbols(rawJSON), [], "RawJSON object should have no own property symbols"); + assert.sameValue(rawJSON.rawJSON, expectedRawJSONValue, "rawJSON value"); +} + +assertIsRawJSON(JSON.rawJSON(1), "1"); +assertIsRawJSON(JSON.rawJSON(null), "null"); +assertIsRawJSON(JSON.rawJSON(true), "true"); +assertIsRawJSON(JSON.rawJSON(false), "false"); +assertIsRawJSON(JSON.rawJSON('"foo"'), '"foo"'); diff --git a/test/staging/JSON/json-parse-with-source-snapshot.js b/test/staging/JSON/json-parse-with-source-snapshot.js deleted file mode 100644 index 2a9692204c2..00000000000 --- a/test/staging/JSON/json-parse-with-source-snapshot.js +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (C) 2023 the V8 project authors. All rights reserved. -// This code is governed by the BSD license found in the LICENSE file. -/*--- -description: V8 mjsunit test for JSON.parse with source snapshotting -includes: [deepEqual.js] -features: [json-parse-with-source] ----*/ - -const replacements = [42, - ['foo'], - {foo:'bar'}, - 'foo']; - -function TestArrayForwardModify(replacement) { - let alreadyReplaced = false; - let expectedKeys = ['0','1','']; - // lol who designed reviver semantics - if (typeof replacement === 'object') { - expectedKeys.splice(1, 0, ...Object.keys(replacement)); - } - const o = JSON.parse('[1, 2]', function (k, v, { source }) { - assert.sameValue(expectedKeys.shift(), k); - if (k === '0') { - if (!alreadyReplaced) { - this[1] = replacement; - alreadyReplaced = true; - } - } else if (k !== '') { - assert.sameValue(undefined, source); - } - return this[k]; - }); - assert.sameValue(0, expectedKeys.length); - assert.deepEqual([1, replacement], o); -} - -function TestObjectForwardModify(replacement) { - let alreadyReplaced = false; - let expectedKeys = ['p','q','']; - if (typeof replacement === 'object') { - expectedKeys.splice(1, 0, ...Object.keys(replacement)); - } - const o = JSON.parse('{"p":1, "q":2}', function (k, v, { source }) { - assert.sameValue(expectedKeys.shift(), k); - if (k === 'p') { - if (!alreadyReplaced) { - this.q = replacement; - alreadyReplaced = true; - } - } else if (k !== '') { - assert.sameValue(undefined, source); - } - return this[k]; - }); - assert.sameValue(0, expectedKeys.length); - assert.deepEqual({p:1, q:replacement}, o); -} - -for (const r of replacements) { - TestArrayForwardModify(r); - TestObjectForwardModify(r); -} - -(function TestArrayAppend() { - let log = []; - const o = JSON.parse('[1,[]]', function (k, v, { source }) { - log.push([k, v, source]); - if (v === 1) { - this[1].push('barf'); - } - return this[k]; - }); - assert.deepEqual([['0', 1, '1'], - ['0', 'barf', undefined], - ['1', ['barf'], undefined], - ['', [1, ['barf']], undefined]], - log); -})(); - -(function TestObjectAddProperty() { - let log = []; - const o = JSON.parse('{"p":1,"q":{}}', function (k, v, { source }) { - log.push([k, v, source]); - if (v === 1) { - this.q.added = 'barf'; - } - return this[k]; - }); - assert.deepEqual([['p', 1, '1'], - ['added', 'barf', undefined], - ['q', {added:'barf'}, undefined], - ['', {p:1, q:{added:'barf'}}, undefined]], - log); -})(); diff --git a/test/staging/JSON/json-parse-with-source.js b/test/staging/JSON/json-parse-with-source.js deleted file mode 100644 index ff998e63640..00000000000 --- a/test/staging/JSON/json-parse-with-source.js +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright (C) 2023 the V8 project authors. All rights reserved. -// This code is governed by the BSD license found in the LICENSE file. -/*--- -description: V8 mjsunit test for JSON.parse with source -includes: [deepEqual.js] -features: [json-parse-with-source] ----*/ - -(function TestBigInt() { - const tooBigForNumber = BigInt(Number.MAX_SAFE_INTEGER) + 2n; - const intToBigInt = (key, val, { source }) => - typeof val === 'number' && val % 1 === 0 ? BigInt(source) : val; - const roundTripped = JSON.parse(String(tooBigForNumber), intToBigInt); - assert.sameValue(tooBigForNumber, roundTripped); - - const bigIntToRawJSON = (key, val) => - typeof val === 'bigint' ? JSON.rawJSON(val) : val; - const embedded = JSON.stringify({ tooBigForNumber }, bigIntToRawJSON); - assert.sameValue('{"tooBigForNumber":9007199254740993}', embedded); -})(); - -function GenerateParseReviverFunction(texts) { - let i = 0; - return function (key, value, context) { - assert(typeof context === 'object'); - assert.sameValue(Object.prototype, Object.getPrototypeOf(context)); - // The json value is a primitive value, it's context only has a source property. - if (texts[i] !== undefined) { - const descriptor = Object.getOwnPropertyDescriptor(context, 'source'); - assert(descriptor.configurable); - assert(descriptor.enumerable); - assert(descriptor.writable); - assert.sameValue(undefined, descriptor.get); - assert.sameValue(undefined, descriptor.set); - assert.sameValue(texts[i++], descriptor.value); - - assert.deepEqual(['source'], Object.getOwnPropertyNames(context)); - assert.deepEqual([], Object.getOwnPropertySymbols(context)); - } else { - // The json value is JSArray or JSObject, it's context has no property. - assert(!Object.hasOwn(context, 'source')); - assert.deepEqual([], Object.getOwnPropertyNames(context)); - assert.deepEqual([], Object.getOwnPropertySymbols(context)); - i++; - } - return value; - }; -} - -(function TestNumber() { - assert.sameValue(1, JSON.parse('1', GenerateParseReviverFunction(['1']))); - assert.sameValue(1.1, JSON.parse('1.1', GenerateParseReviverFunction(['1.1']))); - assert.sameValue(-1, JSON.parse('-1', GenerateParseReviverFunction(['-1']))); - assert.sameValue( - -1.1, - JSON.parse('-1.1', GenerateParseReviverFunction(['-1.1'])) - ); - assert.sameValue( - 11, - JSON.parse('1.1e1', GenerateParseReviverFunction(['1.1e1'])) - ); - assert.sameValue( - 11, - JSON.parse('1.1e+1', GenerateParseReviverFunction(['1.1e+1'])) - ); - assert.sameValue( - 0.11, - JSON.parse('1.1e-1', GenerateParseReviverFunction(['1.1e-1'])) - ); - assert.sameValue( - 11, - JSON.parse('1.1E1', GenerateParseReviverFunction(['1.1E1'])) - ); - assert.sameValue( - 11, - JSON.parse('1.1E+1', GenerateParseReviverFunction(['1.1E+1'])) - ); - assert.sameValue( - 0.11, - JSON.parse('1.1E-1', GenerateParseReviverFunction(['1.1E-1'])) - ); - - assert.sameValue('1', JSON.stringify(JSON.rawJSON(1))); - assert.sameValue('1.1', JSON.stringify(JSON.rawJSON(1.1))); - assert.sameValue('-1', JSON.stringify(JSON.rawJSON(-1))); - assert.sameValue('-1.1', JSON.stringify(JSON.rawJSON(-1.1))); - assert.sameValue('11', JSON.stringify(JSON.rawJSON(1.1e1))); - assert.sameValue('0.11', JSON.stringify(JSON.rawJSON(1.1e-1))); -})(); - -(function TestBasic() { - assert.sameValue( - null, - JSON.parse('null', GenerateParseReviverFunction(['null'])) - ); - assert.sameValue( - true, - JSON.parse('true', GenerateParseReviverFunction(['true'])) - ); - assert.sameValue( - false, - JSON.parse('false', GenerateParseReviverFunction(['false'])) - ); - assert.sameValue( - 'foo', - JSON.parse('"foo"', GenerateParseReviverFunction(['"foo"'])) - ); - - assert.sameValue('null', JSON.stringify(JSON.rawJSON(null))); - assert.sameValue('true', JSON.stringify(JSON.rawJSON(true))); - assert.sameValue('false', JSON.stringify(JSON.rawJSON(false))); - assert.sameValue('"foo"', JSON.stringify(JSON.rawJSON('"foo"'))); -})(); - -(function TestObject() { - assert.deepEqual( - {}, - JSON.parse('{}', GenerateParseReviverFunction([])) - ); - assert.deepEqual( - { 42: 37 }, - JSON.parse('{"42":37}', GenerateParseReviverFunction(['37'])) - ); - assert.deepEqual( - { x: 1, y: 2 }, - JSON.parse('{"x": 1, "y": 2}', GenerateParseReviverFunction(['1', '2'])) - ); - // undefined means the json value is JSObject or JSArray and the passed - // context to the reviver function has no source property. - assert.deepEqual( - { x: [1, 2], y: [2, 3] }, - JSON.parse( - '{"x": [1,2], "y": [2,3]}', - GenerateParseReviverFunction(['1', '2', undefined, '2', '3', undefined]) - ) - ); - assert.deepEqual( - { x: { x: 1, y: 2 } }, - JSON.parse( - '{"x": {"x": 1, "y": 2}}', - GenerateParseReviverFunction(['1', '2', undefined, undefined]) - ) - ); - - assert.sameValue('{"42":37}', JSON.stringify({ 42: JSON.rawJSON(37) })); - assert.sameValue( - '{"x":1,"y":2}', - JSON.stringify({ x: JSON.rawJSON(1), y: JSON.rawJSON(2) }) - ); - assert.sameValue( - '{"x":{"x":1,"y":2}}', - JSON.stringify({ x: { x: JSON.rawJSON(1), y: JSON.rawJSON(2) } }) - ); -})(); - -(function TestArray() { - assert.deepEqual([1], JSON.parse('[1.0]', GenerateParseReviverFunction(['1.0']))); - assert.deepEqual( - [1.1], - JSON.parse('[1.1]', GenerateParseReviverFunction(['1.1'])) - ); - assert.deepEqual([], JSON.parse('[]', GenerateParseReviverFunction([]))); - assert.deepEqual( - [1, '2', true, null, { x: 1, y: 1 }], - JSON.parse( - '[1, "2", true, null, {"x": 1, "y": 1}]', - GenerateParseReviverFunction(['1', '"2"', 'true', 'null', '1', '1']) - ) - ); - - assert.sameValue('[1,1.1]', JSON.stringify([JSON.rawJSON(1), JSON.rawJSON(1.1)])); - assert.sameValue( - '["1",true,null,false]', - JSON.stringify([ - JSON.rawJSON('"1"'), - JSON.rawJSON(true), - JSON.rawJSON(null), - JSON.rawJSON(false), - ]) - ); - assert.sameValue( - '[{"x":1,"y":1}]', - JSON.stringify([{ x: JSON.rawJSON(1), y: JSON.rawJSON(1) }]) - ); -})(); - -function assertIsRawJson(rawJson, expectedRawJsonValue) { - assert.sameValue(null, Object.getPrototypeOf(rawJson)); - assert(Object.hasOwn(rawJson, 'rawJSON')); - assert.deepEqual(['rawJSON'], Object.getOwnPropertyNames(rawJson)); - assert.deepEqual([], Object.getOwnPropertySymbols(rawJson)); - assert.sameValue(expectedRawJsonValue, rawJson.rawJSON); -} - -(function TestRawJson() { - assertIsRawJson(JSON.rawJSON(1), '1'); - assertIsRawJson(JSON.rawJSON(null), 'null'); - assertIsRawJson(JSON.rawJSON(true), 'true'); - assertIsRawJson(JSON.rawJSON(false), 'false'); - assertIsRawJson(JSON.rawJSON('"foo"'), '"foo"'); - - assert.throws(TypeError, () => { - JSON.rawJSON(Symbol('123')); - }); - - assert.throws(SyntaxError, () => { - JSON.rawJSON(undefined); - }); - - assert.throws(SyntaxError, () => { - JSON.rawJSON({}); - }); - - assert.throws(SyntaxError, () => { - JSON.rawJSON([]); - }); - - const ILLEGAL_END_CHARS = ['\n', '\t', '\r', ' ']; - for (const char of ILLEGAL_END_CHARS) { - assert.throws(SyntaxError, () => { - JSON.rawJSON(`${char}123`); - }); - assert.throws(SyntaxError, () => { - JSON.rawJSON(`123${char}`); - }); - } - - assert.throws(SyntaxError, () => { - JSON.rawJSON(''); - }); - - const values = [1, 1.1, null, false, true, '123']; - for (const value of values) { - assert(!JSON.isRawJSON(value)); - assert(JSON.isRawJSON(JSON.rawJSON(value))); - } - assert(!JSON.isRawJSON(undefined)); - assert(!JSON.isRawJSON(Symbol('123'))); - assert(!JSON.isRawJSON([])); - assert(!JSON.isRawJSON({ rawJSON: '123' })); -})(); - -(function TestReviverModifyJsonValue() { - { - let reviverCallIndex = 0; - const expectedKeys = ['a', 'b', 'c', '']; - const reviver = function(key, value, {source}) { - assert.sameValue(expectedKeys[reviverCallIndex++], key); - if (key == 'a') { - this.b = 2; - assert.sameValue('0', source); - } else if (key == 'b') { - this.c = 3; - assert.sameValue(2, value); - assert.sameValue(undefined, source); - } else if (key == 'c') { - assert.sameValue(3, value); - assert.sameValue(undefined, source); - } - return value; - } - assert.deepEqual({a: 0, b: 2, c: 3}, JSON.parse('{"a": 0, "b": 1, "c": [1, 2]}', reviver)); - } - { - let reviverCallIndex = 0; - const expectedKeys = ['0', '1', '2', '3', '']; - const reviver = function(key, value, {source}) { - assert.sameValue(expectedKeys[reviverCallIndex++], key); - if (key == '0') { - this[1] = 3; - assert.sameValue(1, value); - assert.sameValue('1', source); - } else if (key == '1') { - this[2] = 4; - assert.sameValue(3, value); - assert.sameValue(undefined, source); - } else if(key == '2') { - this[3] = 5; - assert.sameValue(4, value); - assert.sameValue(undefined, source); - } else if(key == '5'){ - assert.sameValue(5, value); - assert.sameValue(undefined, source); - } - return value; - } - assert.deepEqual([1, 3, 4, 5], JSON.parse('[1, 2, 3, {"a": 1}]', reviver)); - } -})();