Skip to content

Commit

Permalink
[JSC] Implement JSON.parse source text access proposal
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=248031
rdar://102646189

Reviewed by NOBODY (OOPS!).

This patch implements JSON.parse source text access proposal[1], which is now stage-3.
However, we found that the proposal has a critical bug, which causes crash[2]. So, we
do not enable this by default until it is fixed. Right now, JSC is adding non-specified
guards to avoid crashes due to [2], but this breaks the compatibility of the behavior of
JSON.parse, so we are not enabling this feature. We will expect that some semantics changes
will happen in the proposal.

This patch implements two major things in the proposal.

1. JSON.rawJSON mechaism. Now new object type is integrated, and JSON.rawJSON can wrap JSON text
   with this object, which is a tool to inject raw JSON text into JSON.stringify. JSON.stringify
   uses held string from rawJSON-created object.
2. JSON.parse collects source information during parsing, and offer substring of text as a third parameter
   of JSON.parse reviver function. (but right now, this has a spec bug).

[1]: https://github.com/tc39/proposal-json-parse-with-source
[2]: tc39/proposal-json-parse-with-source#39

* JSTests/stress/json-parse-source-text-access.js: Added.
(shouldBe):
* JSTests/stress/json-raw-json-stringify.js: Added.
(shouldBe):
(shouldBe.JSON.stringify):
* JSTests/stress/json-raw-json.js: Added.
(shouldBe):
(shouldThrow):
(SyntaxError.JSON.Parse.error.Single.quotes.shouldThrow):
* Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj:
* Source/JavaScriptCore/Sources.txt:
* Source/JavaScriptCore/heap/Heap.cpp:
* Source/JavaScriptCore/heap/Heap.h:
* Source/JavaScriptCore/runtime/CommonIdentifiers.h:
* Source/JavaScriptCore/runtime/JSGlobalObject.cpp:
(JSC::createJSONProperty):
(JSC::JSGlobalObject::init):
* Source/JavaScriptCore/runtime/JSGlobalObject.h:
(JSC::JSGlobalObject::rawJSONObjectStructure const):
* Source/JavaScriptCore/runtime/JSONObject.cpp:
(JSC::JSONObject::finishCreation):
(JSC::Stringifier::appendStringifiedValue):
(JSC::Walker::Walker):
(JSC::Walker::callReviver):
(JSC::Walker::walk):
(JSC::JSC_DEFINE_HOST_FUNCTION):
* Source/JavaScriptCore/runtime/JSONObject.h:
* Source/JavaScriptCore/runtime/JSRawJSONObject.cpp: Added.
(JSC::JSRawJSONObject::JSRawJSONObject):
(JSC::JSRawJSONObject::finishCreation):
(JSC::JSRawJSONObject::createStructure):
(JSC::JSRawJSONObject::rawJSON):
* Source/JavaScriptCore/runtime/JSRawJSONObject.h: Copied from Source/JavaScriptCore/runtime/JSONObject.h.
* Source/JavaScriptCore/runtime/LiteralParser.cpp:
(JSC::LiteralParser<CharType>::tryJSONPParse):
(JSC::LiteralParser<CharType>::parse):
* Source/JavaScriptCore/runtime/LiteralParser.h:
(JSC::JSONRanges::JSONRanges):
(JSC::JSONRanges::root const):
(JSC::LiteralParser::tryLiteralParse):
(JSC::LiteralParser::tryLiteralParsePrimitiveValue):
(JSC::LiteralParser::Lexer::Lexer):
(JSC::LiteralParser::Lexer::start const):
* Source/JavaScriptCore/runtime/OptionsList.h:
  • Loading branch information
Constellation committed Dec 2, 2022
1 parent 3244603 commit 8d3885f
Show file tree
Hide file tree
Showing 17 changed files with 630 additions and 48 deletions.
19 changes: 19 additions & 0 deletions JSTests/stress/json-parse-source-text-access.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//@ requireOptions("--useJSONSourceTextAccess=1")

function shouldBe(actual, expected) {
if (actual !== expected)
throw new Error('bad value: ' + actual);
}

const digitsToBigInt = (key, val, {source}) => /^[0-9]+$/.test(source) ? BigInt(source) : val;

const bigIntToRawJSON = (key, val) => typeof val === "bigint" ? JSON.rawJSON(String(val)) : val;

const tooBigForNumber = BigInt(Number.MAX_SAFE_INTEGER) + 2n;
shouldBe(JSON.parse(String(tooBigForNumber), digitsToBigInt), tooBigForNumber);

const wayTooBig = BigInt("1" + "0".repeat(1000));
shouldBe(JSON.parse(String(wayTooBig), digitsToBigInt), wayTooBig);

const embedded = JSON.stringify({ tooBigForNumber }, bigIntToRawJSON);
shouldBe(embedded, '{"tooBigForNumber":9007199254740993}');
12 changes: 12 additions & 0 deletions JSTests/stress/json-raw-json-stringify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//@ requireOptions("--useJSONSourceTextAccess=1")

function shouldBe(actual, expected) {
if (actual !== expected)
throw new Error('bad value: ' + actual);
}

var object = {
"Hello": JSON.rawJSON(`"World"`)
};

shouldBe(JSON.stringify(object), `{"Hello":"World"}`);
59 changes: 59 additions & 0 deletions JSTests/stress/json-raw-json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//@ requireOptions("--useJSONSourceTextAccess=1")

function shouldBe(actual, expected) {
if (actual !== expected)
throw new Error('bad value: ' + actual);
}

function shouldThrow(func, errorMessage) {
var errorThrown = false;
var error = null;
try {
func();
} catch (e) {
errorThrown = true;
error = e;
}
if (!errorThrown)
throw new Error('not thrown');
if (String(error) !== errorMessage)
throw new Error(`bad error: ${String(error)}`);
}

shouldThrow(() => {
JSON.rawJSON("42: 42");
}, `SyntaxError: JSON Parse error: Unexpected content at end of JSON literal`);
shouldThrow(() => {
JSON.rawJSON("42n");
}, `SyntaxError: JSON Parse error: Unexpected content at end of JSON literal`);
shouldThrow(() => {
JSON.rawJSON("[]");
}, `SyntaxError: JSON Parse error: Could not parse value expression`);
shouldThrow(() => {
JSON.rawJSON("{}");
}, `SyntaxError: JSON Parse error: Could not parse value expression`);
shouldThrow(() => {
JSON.rawJSON("'string'");
}, `SyntaxError: JSON Parse error: Single quotes (') are not allowed in JSON`);
shouldThrow(() => {
JSON.rawJSON("Hello");
}, `SyntaxError: JSON Parse error: Unexpected identifier "Hello"`);

var texts = [
'42',
'null',
'false',
'true',
'"Hello"',
];

for (let text of texts) {
let object = JSON.rawJSON(text);
shouldBe(typeof object, "object");
shouldBe(JSON.isRawJSON(object), true);
shouldBe(Object.isFrozen(object), true);
shouldBe(Object.getPrototypeOf(object), null);
shouldBe(object.rawJSON, text);
}
shouldBe(JSON.isRawJSON({}), false);
shouldBe(JSON.isRawJSON({rawJSON:"hello"}), false);
Original file line number Diff line number Diff line change
Expand Up @@ -2018,6 +2018,7 @@
E39DA4A71B7E8B7C0084F33A /* JSModuleRecord.h in Headers */ = {isa = PBXBuildFile; fileRef = E39DA4A51B7E8B7C0084F33A /* JSModuleRecord.h */; settings = {ATTRIBUTES = (Private, ); }; };
E39E44912748E3F800EDD2A5 /* Repatch.h in Headers */ = {isa = PBXBuildFile; fileRef = E39E448E2748E3F800EDD2A5 /* Repatch.h */; };
E39E44932748E3F800EDD2A5 /* RepatchInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = E39E44902748E3F800EDD2A5 /* RepatchInlines.h */; };
E39E578329380FC6000D0D3B /* JSRawJSONObject.h in Headers */ = {isa = PBXBuildFile; fileRef = E39E578129380FC1000D0D3B /* JSRawJSONObject.h */; };
E39EEAF322812450008474F4 /* CachedSpecialPropertyAdaptiveStructureWatchpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = E39EEAF22281244C008474F4 /* CachedSpecialPropertyAdaptiveStructureWatchpoint.h */; };
E39FEBE32339C5D900B40AB0 /* JSAsyncGenerator.h in Headers */ = {isa = PBXBuildFile; fileRef = E39FEBE22339C5D400B40AB0 /* JSAsyncGenerator.h */; };
E3A0531A21342B680022EC14 /* WasmStreamingParser.h in Headers */ = {isa = PBXBuildFile; fileRef = E3A0531621342B660022EC14 /* WasmStreamingParser.h */; settings = {ATTRIBUTES = (Private, ); }; };
Expand Down Expand Up @@ -5565,6 +5566,8 @@
E39E448E2748E3F800EDD2A5 /* Repatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Repatch.h; sourceTree = "<group>"; };
E39E448F2748E3F800EDD2A5 /* Repatch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Repatch.cpp; sourceTree = "<group>"; };
E39E44902748E3F800EDD2A5 /* RepatchInlines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RepatchInlines.h; sourceTree = "<group>"; };
E39E578129380FC1000D0D3B /* JSRawJSONObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSRawJSONObject.h; sourceTree = "<group>"; };
E39E578229380FC1000D0D3B /* JSRawJSONObject.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = JSRawJSONObject.cpp; sourceTree = "<group>"; };
E39EEAF12281244C008474F4 /* CachedSpecialPropertyAdaptiveStructureWatchpoint.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CachedSpecialPropertyAdaptiveStructureWatchpoint.cpp; sourceTree = "<group>"; };
E39EEAF22281244C008474F4 /* CachedSpecialPropertyAdaptiveStructureWatchpoint.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CachedSpecialPropertyAdaptiveStructureWatchpoint.h; sourceTree = "<group>"; };
E39FEBE12339C5D400B40AB0 /* JSAsyncGenerator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = JSAsyncGenerator.cpp; sourceTree = "<group>"; };
Expand Down Expand Up @@ -8125,6 +8128,8 @@
2A05ABD41961DF2400341750 /* JSPropertyNameEnumerator.h */,
862553CE16136AA5009F17D0 /* JSProxy.cpp */,
862553CF16136AA5009F17D0 /* JSProxy.h */,
E39E578229380FC1000D0D3B /* JSRawJSONObject.cpp */,
E39E578129380FC1000D0D3B /* JSRawJSONObject.h */,
5B40327F2798D1FD00F37939 /* JSRemoteFunction.cpp */,
5B40327E2798D1FD00F37939 /* JSRemoteFunction.h */,
534638721E70D01500F12AC1 /* JSRunLoopTimer.cpp */,
Expand Down Expand Up @@ -10879,6 +10884,7 @@
996B731F1BDA08EF00331B84 /* JSPromisePrototype.lut.h in Headers */,
2A05ABD61961DF2400341750 /* JSPropertyNameEnumerator.h in Headers */,
862553D216136E1A009F17D0 /* JSProxy.h in Headers */,
E39E578329380FC6000D0D3B /* JSRawJSONObject.h in Headers */,
5B4032802798D20600F37939 /* JSRemoteFunction.h in Headers */,
A552C3801ADDB8FE00139726 /* JSRemoteInspector.h in Headers */,
BC18C4260E16F5CD00B34460 /* JSRetainPtr.h in Headers */,
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/Sources.txt
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,7 @@ runtime/JSPromiseConstructor.cpp
runtime/JSPromisePrototype.cpp
runtime/JSPropertyNameEnumerator.cpp
runtime/JSProxy.cpp
runtime/JSRawJSONObject.cpp
runtime/JSRemoteFunction.cpp
runtime/JSRunLoopTimer.cpp
runtime/JSScope.cpp
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/heap/Heap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
#include "JITStubRoutineSet.h"
#include "JITWorklistInlines.h"
#include "JSFinalizationRegistry.h"
#include "JSRawJSONObject.h"
#include "JSRemoteFunction.h"
#include "JSVirtualMachineInternal.h"
#include "JSWeakMap.h"
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/heap/Heap.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ class Heap;
v(nativeStdFunctionSpace, nativeStdFunctionHeapCellType, JSNativeStdFunction) \
v(proxyObjectSpace, cellHeapCellType, ProxyObject) \
v(proxyRevokeSpace, cellHeapCellType, ProxyRevoke) \
v(rawJSONObjectSpace, cellHeapCellType, JSRawJSONObject) \
v(remoteFunctionSpace, cellHeapCellType, JSRemoteFunction) \
v(scopedArgumentsTableSpace, destructibleCellHeapCellType, ScopedArgumentsTable) \
v(scriptFetchParametersSpace, destructibleCellHeapCellType, JSScriptFetchParameters) \
Expand Down
2 changes: 2 additions & 0 deletions Source/JavaScriptCore/runtime/CommonIdentifiers.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@
macro(isArray) \
macro(isEnabled) \
macro(isPrototypeOf) \
macro(isRawJSON) \
macro(isView) \
macro(isWatchpoint) \
macro(isWellFormed) \
Expand Down Expand Up @@ -228,6 +229,7 @@
macro(propertyIsEnumerable) \
macro(prototype) \
macro(raw) \
macro(rawJSON) \
macro(region) \
macro(replace) \
macro(resizable) \
Expand Down
7 changes: 6 additions & 1 deletion Source/JavaScriptCore/runtime/JSGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
#include "JSPromise.h"
#include "JSPromiseConstructor.h"
#include "JSPromisePrototype.h"
#include "JSRawJSONObject.h"
#include "JSRemoteFunction.h"
#include "JSSet.h"
#include "JSSetIterator.h"
Expand Down Expand Up @@ -318,7 +319,7 @@ static JSValue createProxyProperty(VM& vm, JSObject* object)
static JSValue createJSONProperty(VM& vm, JSObject* object)
{
JSGlobalObject* global = jsCast<JSGlobalObject*>(object);
return JSONObject::create(vm, JSONObject::createStructure(vm, global, global->objectPrototype()));
return JSONObject::create(vm, global, JSONObject::createStructure(vm, global, global->objectPrototype()));
}

static JSValue createMathProperty(VM& vm, JSObject* object)
Expand Down Expand Up @@ -986,6 +987,10 @@ void JSGlobalObject::init(VM& vm)
[] (const Initializer<Structure>& init) {
init.set(JSCallbackObject<JSNonFinalObject>::createStructure(init.vm, init.owner, init.owner->m_objectPrototype.get()));
});
m_rawJSONObjectStructure.initLater(
[] (const Initializer<Structure>& init) {
init.set(JSRawJSONObject::createStructure(init.vm, init.owner, jsNull()));
});

#if JSC_OBJC_API_ENABLED
m_objcCallbackFunctionStructure.initLater(
Expand Down
2 changes: 2 additions & 0 deletions Source/JavaScriptCore/runtime/JSGlobalObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ class JSGlobalObject : public JSSegmentedVariableObject {
LazyProperty<JSGlobalObject, Structure> m_callbackConstructorStructure;
LazyProperty<JSGlobalObject, Structure> m_callbackFunctionStructure;
LazyProperty<JSGlobalObject, Structure> m_callbackObjectStructure;
LazyProperty<JSGlobalObject, Structure> m_rawJSONObjectStructure;
#if JSC_OBJC_API_ENABLED
LazyProperty<JSGlobalObject, Structure> m_objcCallbackFunctionStructure;
LazyProperty<JSGlobalObject, Structure> m_objcWrapperObjectStructure;
Expand Down Expand Up @@ -934,6 +935,7 @@ class JSGlobalObject : public JSSegmentedVariableObject {
Structure* callbackConstructorStructure() const { return m_callbackConstructorStructure.get(this); }
Structure* callbackFunctionStructure() const { return m_callbackFunctionStructure.get(this); }
Structure* callbackObjectStructure() const { return m_callbackObjectStructure.get(this); }
Structure* rawJSONObjectStructure() const { return m_rawJSONObjectStructure.get(this); }
#if JSC_OBJC_API_ENABLED
Structure* objcCallbackFunctionStructure() const { return m_objcCallbackFunctionStructure.get(this); }
Structure* objcWrapperObjectStructure() const { return m_objcWrapperObjectStructure.get(this); }
Expand Down
Loading

0 comments on commit 8d3885f

Please sign in to comment.