diff --git a/js/src/builtin/Eval.cpp b/js/src/builtin/Eval.cpp index cb6197995bb1..482cb4dd22f2 100644 --- a/js/src/builtin/Eval.cpp +++ b/js/src/builtin/Eval.cpp @@ -175,7 +175,8 @@ static EvalJSONResult ParseEvalStringAsJSON( chars.begin().get() + 1U, len - 2); Rooted> parser( - cx, JSONParser(cx, jsonChars, JSONParserBase::NoError)); + cx, JSONParser(cx, jsonChars, + JSONParserBase::ParseType::AttemptForEval)); if (!parser.parse(rval)) { return EvalJSON_Failure; } diff --git a/js/src/builtin/JSON.cpp b/js/src/builtin/JSON.cpp index 34f308f252bb..d24654b92f88 100644 --- a/js/src/builtin/JSON.cpp +++ b/js/src/builtin/JSON.cpp @@ -1008,7 +1008,8 @@ bool js::ParseJSONWithReviver(JSContext* cx, const mozilla::Range chars, HandleValue reviver, MutableHandleValue vp) { /* 15.12.2 steps 2-3. */ - Rooted> parser(cx, JSONParser(cx, chars)); + Rooted> parser( + cx, JSONParser(cx, chars, JSONParserBase::ParseType::JSONParse)); if (!parser.parse(vp)) { return false; } diff --git a/js/src/jit-test/tests/basic/eval-json-differences.js b/js/src/jit-test/tests/basic/eval-json-differences.js new file mode 100644 index 000000000000..eb7ff3389b41 --- /dev/null +++ b/js/src/jit-test/tests/basic/eval-json-differences.js @@ -0,0 +1,25 @@ +load(libdir + "asserts.js"); + +// eval({__proto__}) changes [[Prototype]] +var obj = eval('({"__proto__": null})'); +assertEq(Object.getPrototypeOf(obj), null); +assertEq(Object.prototype.hasOwnProperty.call(obj, "__proto__"), false); + +// JSON.parse({__proto__}) creates new property __proto__ +obj = JSON.parse('{"__proto__": null}'); +assertEq(Object.getPrototypeOf(obj), Object.prototype); +assertEq(Object.prototype.hasOwnProperty.call(obj, "__proto__"), true); + +// If __proto__ appears more than once as quoted or unquoted property name in an +// object initializer expression, that's an error. +//(No other property name has this restriction.)" +assertThrowsInstanceOf(() => + eval('({ "__proto__" : null, "__proto__" : null })'), SyntaxError); + +assertThrowsInstanceOf(() => + eval(' ({ "__proto__" : null, "__proto__" : null })'), SyntaxError); + +// JSON.parse doesn't care about duplication, the last definition counts. +obj = JSON.parse('{"__proto__": null, "__proto__": 5}'); +assertEq(Object.getPrototypeOf(obj), Object.prototype); +assertEq(obj["__proto__"], 5); diff --git a/js/src/vm/JSONParser.cpp b/js/src/vm/JSONParser.cpp index 3641e94a10a6..79210f352ac9 100644 --- a/js/src/vm/JSONParser.cpp +++ b/js/src/vm/JSONParser.cpp @@ -77,7 +77,7 @@ void JSONParser::getTextPosition(uint32_t* column, uint32_t* line) { template void JSONParser::error(const char* msg) { - if (errorHandling == RaiseError) { + if (parseType == ParseType::JSONParse) { uint32_t column = 1, line = 1; getTextPosition(&column, &line); @@ -93,7 +93,9 @@ void JSONParser::error(const char* msg) { } } -bool JSONParserBase::errorReturn() { return errorHandling == NoError; } +bool JSONParserBase::errorReturn() { + return parseType == ParseType::AttemptForEval; +} template template @@ -666,6 +668,17 @@ bool JSONParser::parse(MutableHandleValue vp) { JSONMember: if (token == String) { jsid id = AtomToId(atomValue()); + if (parseType == ParseType::AttemptForEval) { + // In |JSON.parse|, "__proto__" is a property like any other and may + // appear multiple times. In object literal syntax, "__proto__" is + // prototype mutation and can appear at most once. |JSONParser| only + // supports the former semantics, so if this parse attempt is for + // |eval|, return true (without reporting an error) to indicate the + // JSON parse attempt was unsuccessful. + if (id == NameToId(cx->names().proto)) { + return true; + } + } PropertyVector& properties = stack.back().properties(); if (!properties.append(IdValuePair(id))) { return false; diff --git a/js/src/vm/JSONParser.h b/js/src/vm/JSONParser.h index 2f638fc96341..eb527f8ff886 100644 --- a/js/src/vm/JSONParser.h +++ b/js/src/vm/JSONParser.h @@ -22,7 +22,14 @@ namespace js { // can be shared between the two encodings. class MOZ_STACK_CLASS JSONParserBase { public: - enum ErrorHandling { RaiseError, NoError }; + enum class ParseType { + // Parsing a string as if by JSON.parse. + JSONParse, + // Parsing what may or may not be JSON in a string of eval code. + // In this case, a failure to parse indicates either syntax that isn't JSON, + // or syntax that has different semantics in eval code than in JSON. + AttemptForEval, + }; private: /* Data members */ @@ -31,7 +38,7 @@ class MOZ_STACK_CLASS JSONParserBase { protected: JSContext* const cx; - const ErrorHandling errorHandling; + const ParseType parseType; enum Token { String, @@ -114,9 +121,9 @@ class MOZ_STACK_CLASS JSONParserBase { Token lastToken; #endif - JSONParserBase(JSContext* cx, ErrorHandling errorHandling) + JSONParserBase(JSContext* cx, ParseType parseType) : cx(cx), - errorHandling(errorHandling), + parseType(parseType), stack(cx), freeElements(cx), freeProperties(cx) @@ -132,7 +139,7 @@ class MOZ_STACK_CLASS JSONParserBase { JSONParserBase(JSONParserBase&& other) : v(other.v), cx(other.cx), - errorHandling(other.errorHandling), + parseType(other.parseType), stack(std::move(other.stack)), freeElements(std::move(other.freeElements)), freeProperties(std::move(other.freeProperties)) @@ -212,8 +219,8 @@ class MOZ_STACK_CLASS JSONParser : public JSONParserBase { /* Create a parser for the provided JSON data. */ JSONParser(JSContext* cx, mozilla::Range data, - ErrorHandling errorHandling = RaiseError) - : JSONParserBase(cx, errorHandling), + ParseType parseType) + : JSONParserBase(cx, parseType), current(data.begin()), begin(current), end(data.end()) {