summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/JSON.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin/JSON.cpp')
-rw-r--r--js/src/builtin/JSON.cpp268
1 files changed, 226 insertions, 42 deletions
diff --git a/js/src/builtin/JSON.cpp b/js/src/builtin/JSON.cpp
index dbfab8b43a..1423595937 100644
--- a/js/src/builtin/JSON.cpp
+++ b/js/src/builtin/JSON.cpp
@@ -20,15 +20,18 @@
#include "builtin/Array.h"
#include "builtin/BigInt.h"
#include "builtin/ParseRecordObject.h"
+#include "builtin/RawJSONObject.h"
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
#include "js/Object.h" // JS::GetBuiltinClass
+#include "js/Prefs.h" // JS::Prefs
#include "js/PropertySpec.h"
#include "js/StableStringChars.h"
#include "js/TypeDecls.h"
#include "js/Value.h"
#include "util/StringBuffer.h"
-#include "vm/BooleanObject.h" // js::BooleanObject
+#include "vm/BooleanObject.h" // js::BooleanObject
+#include "vm/EqualityOperations.h" // js::SameValue
#include "vm/Interpreter.h"
#include "vm/Iteration.h"
#include "vm/JSAtomUtils.h" // ToAtom
@@ -436,6 +439,16 @@ class CycleDetector {
bool appended_;
};
+static inline JSString* MaybeGetRawJSON(JSContext* cx, JSObject* obj) {
+ if (!obj->is<RawJSONObject>()) {
+ return nullptr;
+ }
+
+ JSString* rawJSON = obj->as<js::RawJSONObject>().rawJSON(cx);
+ MOZ_ASSERT(rawJSON);
+ return rawJSON;
+}
+
#ifdef ENABLE_RECORD_TUPLE
enum class JOType { Record, Object };
template <JOType type = JOType::Object>
@@ -783,6 +796,12 @@ static bool SerializeJSONProperty(JSContext* cx, const Value& v,
MOZ_ASSERT(v.hasObjectPayload());
RootedObject obj(cx, &v.getObjectPayload());
+ /* https://tc39.es/proposal-json-parse-with-source/#sec-serializejsonproperty
+ * Step 4a.*/
+ if (JSString* rawJSON = MaybeGetRawJSON(cx, obj)) {
+ return scx->sb.append(rawJSON);
+ }
+
MOZ_ASSERT(
!scx->maybeSafely || obj->is<PlainObject>() || obj->is<ArrayObject>(),
"input to JS::ToJSONMaybeSafely must not include reachable "
@@ -1233,6 +1252,10 @@ static bool FastSerializeJSONProperty(JSContext* cx, Handle<Value> v,
MOZ_ASSERT(*whySlow == BailReason::NO_REASON);
MOZ_ASSERT(v.isObject());
+ if (JSString* rawJSON = MaybeGetRawJSON(cx, &v.toObject())) {
+ return scx->sb.append(rawJSON);
+ }
+
/*
* FastSerializeJSONProperty is an optimistic fast path for the
* SerializeJSONProperty algorithm that applies in limited situations. It
@@ -1356,19 +1379,24 @@ static bool FastSerializeJSONProperty(JSContext* cx, Handle<Value> v,
}
if (val.isObject()) {
- if (stack.length() >= MAX_STACK_DEPTH - 1) {
- *whySlow = BailReason::DEEP_RECURSION;
- return true;
+ if (JSString* rawJSON = MaybeGetRawJSON(cx, &val.toObject())) {
+ if (!scx->sb.append(rawJSON)) {
+ return false;
+ }
+ } else {
+ if (stack.length() >= MAX_STACK_DEPTH - 1) {
+ *whySlow = BailReason::DEEP_RECURSION;
+ return true;
+ }
+ // Save the current iterator position on the stack and
+ // switch to processing the nested value.
+ stack.infallibleAppend(std::move(top));
+ top = FastStackEntry(&val.toObject().as<NativeObject>());
+ wroteMember = false;
+ nestedObject = true; // Break out to the outer loop.
+ break;
}
- // Save the current iterator position on the stack and
- // switch to processing the nested value.
- stack.infallibleAppend(std::move(top));
- top = FastStackEntry(&val.toObject().as<NativeObject>());
- wroteMember = false;
- nestedObject = true; // Break out to the outer loop.
- break;
- }
- if (!EmitSimpleValue(cx, scx->sb, val)) {
+ } else if (!EmitSimpleValue(cx, scx->sb, val)) {
return false;
}
}
@@ -1433,19 +1461,24 @@ static bool FastSerializeJSONProperty(JSContext* cx, Handle<Value> v,
return false;
}
if (val.isObject()) {
- if (stack.length() >= MAX_STACK_DEPTH - 1) {
- *whySlow = BailReason::DEEP_RECURSION;
- return true;
+ if (JSString* rawJSON = MaybeGetRawJSON(cx, &val.toObject())) {
+ if (!scx->sb.append(rawJSON)) {
+ return false;
+ }
+ } else {
+ if (stack.length() >= MAX_STACK_DEPTH - 1) {
+ *whySlow = BailReason::DEEP_RECURSION;
+ return true;
+ }
+ // Save the current iterator position on the stack and
+ // switch to processing the nested value.
+ stack.infallibleAppend(std::move(top));
+ top = FastStackEntry(&val.toObject().as<NativeObject>());
+ wroteMember = false;
+ nesting = true; // Break out to the outer loop.
+ break;
}
- // Save the current iterator position on the stack and
- // switch to processing the nested value.
- stack.infallibleAppend(std::move(top));
- top = FastStackEntry(&val.toObject().as<NativeObject>());
- wroteMember = false;
- nesting = true; // Break out to the outer loop.
- break;
- }
- if (!EmitSimpleValue(cx, scx->sb, val)) {
+ } else if (!EmitSimpleValue(cx, scx->sb, val)) {
return false;
}
}
@@ -1733,6 +1766,41 @@ static bool InternalizeJSONProperty(
return false;
}
+#ifdef ENABLE_JSON_PARSE_WITH_SOURCE
+ RootedObject context(cx);
+ Rooted<UniquePtr<ParseRecordObject::EntryMap>> entries(cx);
+ if (JS::Prefs::experimental_json_parse_with_source()) {
+ // https://tc39.es/proposal-json-parse-with-source/#sec-internalizejsonproperty
+ bool sameVal = false;
+ Rooted<Value> parsedValue(cx, parseRecord.get().value);
+ if (!SameValue(cx, parsedValue, val, &sameVal)) {
+ return false;
+ }
+ if (!parseRecord.get().isEmpty() && sameVal) {
+ if (parseRecord.get().parseNode) {
+ MOZ_ASSERT(!val.isObject());
+ Rooted<IdValueVector> props(cx, cx);
+ if (!props.emplaceBack(
+ IdValuePair(NameToId(cx->names().source),
+ StringValue(parseRecord.get().parseNode)))) {
+ return false;
+ }
+ context = NewPlainObjectWithUniqueNames(cx, props);
+ if (!context) {
+ return false;
+ }
+ }
+ entries = std::move(parseRecord.get().entries);
+ }
+ if (!context) {
+ context = NewPlainObject(cx);
+ if (!context) {
+ return false;
+ }
+ }
+ }
+#endif
+
/* Step 2. */
if (val.isObject()) {
RootedObject obj(cx, &val.toObject());
@@ -1763,6 +1831,13 @@ static bool InternalizeJSONProperty(
/* Step 2a(iii)(1). */
Rooted<ParseRecordObject> elementRecord(cx);
+#ifdef ENABLE_JSON_PARSE_WITH_SOURCE
+ if (entries) {
+ if (auto entry = entries->lookup(id)) {
+ elementRecord = std::move(entry->value());
+ }
+ }
+#endif
if (!InternalizeJSONProperty(cx, obj, id, reviver, &elementRecord,
&newElement)) {
return false;
@@ -1804,6 +1879,13 @@ static bool InternalizeJSONProperty(
/* Step 2c(ii)(1). */
id = keys[i];
Rooted<ParseRecordObject> entryRecord(cx);
+#ifdef ENABLE_JSON_PARSE_WITH_SOURCE
+ if (entries) {
+ if (auto entry = entries->lookup(id)) {
+ entryRecord = std::move(entry->value());
+ }
+ }
+#endif
if (!InternalizeJSONProperty(cx, obj, id, reviver, &entryRecord,
&newElement)) {
return false;
@@ -1838,15 +1920,7 @@ static bool InternalizeJSONProperty(
RootedValue keyVal(cx, StringValue(key));
#ifdef ENABLE_JSON_PARSE_WITH_SOURCE
- if (cx->realm()->creationOptions().getJSONParseWithSource()) {
- RootedObject context(cx, NewPlainObject(cx));
- if (!context) {
- return false;
- }
- Rooted<Value> parseNode(cx, StringValue(parseRecord.get().parseNode));
- if (!DefineDataProperty(cx, context, cx->names().source, parseNode)) {
- return false;
- }
+ if (JS::Prefs::experimental_json_parse_with_source()) {
RootedValue contextVal(cx, ObjectValue(*context));
return js::Call(cx, reviver, holder, keyVal, val, contextVal, vp);
}
@@ -1867,7 +1941,7 @@ static bool Revive(JSContext* cx, HandleValue reviver,
}
#ifdef ENABLE_JSON_PARSE_WITH_SOURCE
- MOZ_ASSERT_IF(cx->realm()->creationOptions().getJSONParseWithSource(),
+ MOZ_ASSERT_IF(JS::Prefs::experimental_json_parse_with_source(),
pro.get().value == vp.get());
#endif
Rooted<jsid> id(cx, NameToId(cx->names().empty_));
@@ -1876,10 +1950,10 @@ static bool Revive(JSContext* cx, HandleValue reviver,
template <typename CharT>
bool ParseJSON(JSContext* cx, const mozilla::Range<const CharT> chars,
- MutableHandleValue vp, MutableHandle<ParseRecordObject> pro) {
+ MutableHandleValue vp) {
Rooted<JSONParser<CharT>> parser(cx, cx, chars,
JSONParser<CharT>::ParseType::JSONParse);
- return parser.parse(vp, pro);
+ return parser.parse(vp);
}
template <typename CharT>
@@ -1888,7 +1962,15 @@ bool js::ParseJSONWithReviver(JSContext* cx,
HandleValue reviver, MutableHandleValue vp) {
/* https://262.ecma-international.org/14.0/#sec-json.parse steps 2-10. */
Rooted<ParseRecordObject> pro(cx);
- if (!ParseJSON(cx, chars, vp, &pro)) {
+#ifdef ENABLE_JSON_PARSE_WITH_SOURCE
+ if (JS::Prefs::experimental_json_parse_with_source() && IsCallable(reviver)) {
+ Rooted<JSONReviveParser<CharT>> parser(cx, cx, chars);
+ if (!parser.get().parse(vp, &pro)) {
+ return false;
+ }
+ } else
+#endif
+ if (!ParseJSON(cx, chars, vp)) {
return false;
}
@@ -2086,14 +2168,13 @@ static bool json_parseImmutable(JSContext* cx, unsigned argc, Value* vp) {
HandleValue reviver = args.get(1);
RootedValue unfiltered(cx);
- Rooted<ParseRecordObject> pro(cx);
if (linearChars.isLatin1()) {
- if (!ParseJSON(cx, linearChars.latin1Range(), &unfiltered, &pro)) {
+ if (!ParseJSON(cx, linearChars.latin1Range(), &unfiltered)) {
return false;
}
} else {
- if (!ParseJSON(cx, linearChars.twoByteRange(), &unfiltered, &pro)) {
+ if (!ParseJSON(cx, linearChars.twoByteRange(), &unfiltered)) {
return false;
}
}
@@ -2103,6 +2184,104 @@ static bool json_parseImmutable(JSContext* cx, unsigned argc, Value* vp) {
}
#endif
+#ifdef ENABLE_JSON_PARSE_WITH_SOURCE
+/* https://tc39.es/proposal-json-parse-with-source/#sec-json.israwjson */
+static bool json_isRawJSON(JSContext* cx, unsigned argc, Value* vp) {
+ AutoJSMethodProfilerEntry pseudoFrame(cx, "JSON", "isRawJSON");
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ /* Step 1. */
+ if (args.get(0).isObject()) {
+ Rooted<JSObject*> obj(cx, &args[0].toObject());
+# ifdef DEBUG
+ if (obj->is<RawJSONObject>()) {
+ bool objIsFrozen = false;
+ MOZ_ASSERT(js::TestIntegrityLevel(cx, obj, IntegrityLevel::Frozen,
+ &objIsFrozen));
+ MOZ_ASSERT(objIsFrozen);
+ }
+# endif // DEBUG
+ args.rval().setBoolean(obj->is<RawJSONObject>());
+ return true;
+ }
+
+ /* Step 2. */
+ args.rval().setBoolean(false);
+ return true;
+}
+
+static inline bool IsJSONWhitespace(char16_t ch) {
+ return ch == '\t' || ch == '\n' || ch == '\r' || ch == ' ';
+}
+
+/* https://tc39.es/proposal-json-parse-with-source/#sec-json.rawjson */
+static bool json_rawJSON(JSContext* cx, unsigned argc, Value* vp) {
+ AutoJSMethodProfilerEntry pseudoFrame(cx, "JSON", "rawJSON");
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ /* Step 1. */
+ JSString* jsonString = ToString<CanGC>(cx, args.get(0));
+ if (!jsonString) {
+ return false;
+ }
+
+ Rooted<JSLinearString*> linear(cx, jsonString->ensureLinear(cx));
+ if (!linear) {
+ return false;
+ }
+
+ AutoStableStringChars linearChars(cx);
+ if (!linearChars.init(cx, linear)) {
+ return false;
+ }
+
+ /* Step 2. */
+ if (linear->empty()) {
+ JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
+ JSMSG_JSON_RAW_EMPTY);
+ return false;
+ }
+ if (IsJSONWhitespace(linear->latin1OrTwoByteChar(0)) ||
+ IsJSONWhitespace(linear->latin1OrTwoByteChar(linear->length() - 1))) {
+ JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
+ JSMSG_JSON_RAW_WHITESPACE);
+ return false;
+ }
+
+ /* Step 3. */
+ RootedValue parsedValue(cx);
+ if (linearChars.isLatin1()) {
+ if (!ParseJSON(cx, linearChars.latin1Range(), &parsedValue)) {
+ return false;
+ }
+ } else {
+ if (!ParseJSON(cx, linearChars.twoByteRange(), &parsedValue)) {
+ return false;
+ }
+ }
+
+ if (parsedValue.isObject()) {
+ JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
+ JSMSG_JSON_RAW_ARRAY_OR_OBJECT);
+ return false;
+ }
+
+ /* Steps 4-6. */
+ Rooted<RawJSONObject*> obj(cx, RawJSONObject::create(cx, linear));
+ if (!obj) {
+ return false;
+ }
+
+ /* Step 7. */
+ if (!js::FreezeObject(cx, obj)) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+#endif // ENABLE_JSON_PARSE_WITH_SOURCE
+
/* https://262.ecma-international.org/14.0/#sec-json.stringify */
bool json_stringify(JSContext* cx, unsigned argc, Value* vp) {
AutoJSMethodProfilerEntry pseudoFrame(cx, "JSON", "stringify");
@@ -2141,11 +2320,16 @@ bool json_stringify(JSContext* cx, unsigned argc, Value* vp) {
}
static const JSFunctionSpec json_static_methods[] = {
- JS_FN("toSource", json_toSource, 0, 0), JS_FN("parse", json_parse, 2, 0),
+ JS_FN("toSource", json_toSource, 0, 0),
+ JS_FN("parse", json_parse, 2, 0),
JS_FN("stringify", json_stringify, 3, 0),
#ifdef ENABLE_RECORD_TUPLE
JS_FN("parseImmutable", json_parseImmutable, 2, 0),
#endif
+#ifdef ENABLE_JSON_PARSE_WITH_SOURCE
+ JS_FN("isRawJSON", json_isRawJSON, 1, 0),
+ JS_FN("rawJSON", json_rawJSON, 1, 0),
+#endif
JS_FS_END};
static const JSPropertySpec json_static_properties[] = {