Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reviver can modify holder object, like inserting new object / array, which breaks invariant of parsing information #39

Closed
Constellation opened this issue Dec 1, 2022 · 6 comments · Fixed by #41

Comments

@Constellation
Copy link
Member

Constellation commented Dec 1, 2022

Let's have this example,

print(JSON.stringify(JSON.parse("[1, 2]", function (name, value, context) {
    if (name === "0")
        this[1] = [ "Hello" ];
    return this[name];
})));

JSON.stringify's result is [1,["Hello",["Hello"]]] before this proposal because reviver modified array[1] at runtime and we visit this newly inserted ["Hello"] array.
But for this array, we do not have correct corresponding parse information since this is not coming from text. source information for this property is still 2's information.
Thus,

i. Assert: typedValNode is an ArrayLiteral.

This will crash since this newly inserted array does not have right source information. Source information for this property says 2 (it is coming from the original source).

@Constellation Constellation changed the title Reviver can insert new object / array, which breaks invariant of parsing information Reviver can modify holder object, like inserting new object / array, which breaks invariant of parsing information Dec 1, 2022
@Constellation
Copy link
Member Author

We observed invariant failures in JSC's prototype implementation, and we found that V8 crashes with the above example with --harmony-json-parse-with-source

Constellation added a commit to Constellation/WebKit that referenced this issue Dec 2, 2022
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:
@syg
Copy link

syg commented Dec 8, 2022

This looks like ultimately the same root cause as #35, which was also noticed during V8's implementation.

@gibson042 @mathiasbynens Needless to say this and #35 is a hard blocker for shipping.

@gibson042
Copy link
Collaborator

Nice find, @Constellation! This is a counterexample to our claim that bottom-up invocation renders moot the concern about divergence between live values and the source text from which they are derived, and will require a normative change to fix. I'll make a PR to take a shallow snapshot of arrays and other objects and suppress source in the context passed to the reviver for entries that have diverged and raise it for approval at the next TC39 plenary. We'll also want to cover this with test cases:

  • [array and plain object] overwrite of not-yet-processed entry should result in absence of source for that entry
    • primitive to non-primitive
    • non-primitive to primitive
    • non-primitive to a clone (same date but different identity)
    • primitive to a different primitive
  • [array and plain object] overwrite and restoration of not-yet-processed entry should preserve source for that entry
    • primitive
    • non-primitive
  • [array] splicing out an item should result in absence of source from the affected index forward
    • ...but restoring items at their original index should preserve source

@mhofman
Copy link
Member

mhofman commented Dec 8, 2022

What about deletion and recreation of an already processed object property? Values would be the same but order wouldn't.

@mhofman
Copy link
Member

mhofman commented Dec 8, 2022

Would it be web compatible to use the snapshot of objects and arrays for the entries passed to the reviver, and ignore mutations past snapshot time?

I mean which legitimate code mutates the object through the reviver?

@gibson042
Copy link
Collaborator

What about deletion and recreation of an already processed object property? Values would be the same but order wouldn't.

My plan is to preserve source in that case:

const reorderedSource = JSON.parse(
  '{ "foo": 0, "bar": 1, "baz": 2 }',
  function reviver(key, value, {source}) {
    if (key === "foo") {
      // Reposition the "bar" property.
      const barVal = this.bar;
      delete this.bar;
      this.bar = barVal;
    }
    return key ? source : value;
  },
);
assert.strictEqual(reorderedSource.bar, "1");

Would it be web compatible to use the snapshot of objects and arrays for the entries passed to the reviver, and ignore mutations past snapshot time?

I mean which legitimate code mutates the object through the reviver?

I don't think it would be (as evidenced in part by these two issues), and at any rate such a change is out of scope for this proposal.

gibson042 added a commit to gibson042/proposal-json-parse-with-source that referenced this issue Jan 20, 2023
…ccess

Snapshot the initial data structure before exposing it to ECMAScript code.
Fixes tc39#35
Fixes tc39#39
gibson042 added a commit to gibson042/proposal-json-parse-with-source that referenced this issue Jan 27, 2023
…ccess

Snapshot the initial data structure before exposing it to ECMAScript code.
Fixes tc39#35
Fixes tc39#39
gibson042 added a commit to gibson042/proposal-json-parse-with-source that referenced this issue Jan 27, 2023
…ccess

Snapshot the initial data structure before exposing it to ECMAScript code.
Fixes tc39#35
Fixes tc39#39
gibson042 added a commit to gibson042/proposal-json-parse-with-source that referenced this issue Jan 27, 2023
…ccess

Snapshot the initial data structure before exposing it to ECMAScript code.
Fixes tc39#35
Fixes tc39#39
gibson042 added a commit that referenced this issue Sep 18, 2023
…ccess

Snapshot the initial data structure before exposing it to ECMAScript code.
Fixes #35
Fixes #39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants