Diff
Modified: trunk/JSTests/ChangeLog (282467 => 282468)
--- trunk/JSTests/ChangeLog 2021-09-15 20:06:42 UTC (rev 282467)
+++ trunk/JSTests/ChangeLog 2021-09-15 20:17:05 UTC (rev 282468)
@@ -1,3 +1,12 @@
+2021-09-15 Yusuke Suzuki <[email protected]>
+
+ [JSC] Optimize leaf object creation in JSON.parse
+ https://bugs.webkit.org/show_bug.cgi?id=230298
+
+ Reviewed by Keith Miller.
+
+ * microbenchmarks/json-parse-leaf-object.js: Added.
+
2021-09-13 Yusuke Suzuki <[email protected]>
[JSC] Add JSON.parse microbenchmarks from Speedometer2
Added: trunk/JSTests/microbenchmarks/json-parse-leaf-object.js (0 => 282468)
--- trunk/JSTests/microbenchmarks/json-parse-leaf-object.js (rev 0)
+++ trunk/JSTests/microbenchmarks/json-parse-leaf-object.js 2021-09-15 20:17:05 UTC (rev 282468)
@@ -0,0 +1,187 @@
+var text = `
+[
+{
+"number": 0,
+"string": "test",
+"null": null,
+"boolean": false
+},
+{
+"number": 0,
+"string": "test",
+"null": null,
+"boolean": false
+},
+{
+"number": 0,
+"string": "test",
+"null": null,
+"boolean": false
+},
+{
+"number": 0,
+"string": "test",
+"null": null,
+"boolean": false
+},
+{
+"number": 0,
+"string": "test",
+"null": null,
+"boolean": false
+},
+{
+"number": 0,
+"string": "test",
+"null": null,
+"boolean": false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+}
+]
+`;
+
+for (var i = 0; i < 1e4; ++i)
+ JSON.parse(text);
Added: trunk/JSTests/stress/json-parse-syntax.js (0 => 282468)
--- trunk/JSTests/stress/json-parse-syntax.js (rev 0)
+++ trunk/JSTests/stress/json-parse-syntax.js 2021-09-15 20:17:05 UTC (rev 282468)
@@ -0,0 +1,26 @@
+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)}`);
+}
+
+function shouldBe(actual, expected) {
+ if (actual !== expected)
+ throw new Error('bad value: ' + actual);
+}
+
+shouldThrow(() => JSON.parse(`{"object": 42`), `SyntaxError: JSON Parse error: Expected '}'`);
+shouldBe(JSON.stringify(JSON.parse(`{}`)), `{}`);
+shouldBe(JSON.stringify(JSON.parse(`{"object": {}}`)), `{"object":{}}`);
+shouldThrow(() => JSON.parse(`{"object": 42, "test":{}`), `SyntaxError: JSON Parse error: Expected '}'`);
+shouldBe(JSON.stringify(JSON.parse(`{"hello":42, "object": {}}`)), `{"hello":42,"object":{}}`);
+shouldBe(JSON.stringify(JSON.parse(`{"hello":42, "object": 42, "test": 43}`)), `{"hello":42,"object":42,"test":43}`);
Modified: trunk/LayoutTests/ChangeLog (282467 => 282468)
--- trunk/LayoutTests/ChangeLog 2021-09-15 20:06:42 UTC (rev 282467)
+++ trunk/LayoutTests/ChangeLog 2021-09-15 20:17:05 UTC (rev 282468)
@@ -1,3 +1,12 @@
+2021-09-15 Yusuke Suzuki <[email protected]>
+
+ [JSC] Optimize leaf object creation in JSON.parse
+ https://bugs.webkit.org/show_bug.cgi?id=230298
+
+ Reviewed by Keith Miller.
+
+ * js/dom/JSON-parse-expected.txt:
+
2021-09-15 Chris Dumez <[email protected]>
Unreviewed, skip a layout worklet test as it is not supported.
Modified: trunk/LayoutTests/js/dom/JSON-parse-expected.txt (282467 => 282468)
--- trunk/LayoutTests/js/dom/JSON-parse-expected.txt 2021-09-15 20:06:42 UTC (rev 282467)
+++ trunk/LayoutTests/js/dom/JSON-parse-expected.txt 2021-09-15 20:17:05 UTC (rev 282468)
@@ -85,7 +85,7 @@
function (jsonObject){
return jsonObject.parse('{"a":5,"a",}');
}
-PASS tests[i](nativeJSON) threw exception SyntaxError: JSON Parse error: Expected ':'.
+PASS tests[i](nativeJSON) threw exception SyntaxError: JSON Parse error: Expected ':' before value in object property definition.
function (jsonObject){
return jsonObject.parse('{"a":(5,"a"),}');
}
Modified: trunk/Source/_javascript_Core/ChangeLog (282467 => 282468)
--- trunk/Source/_javascript_Core/ChangeLog 2021-09-15 20:06:42 UTC (rev 282467)
+++ trunk/Source/_javascript_Core/ChangeLog 2021-09-15 20:17:05 UTC (rev 282468)
@@ -1,3 +1,64 @@
+2021-09-15 Yusuke Suzuki <[email protected]>
+
+ [JSC] Optimize leaf object creation in JSON.parse
+ https://bugs.webkit.org/show_bug.cgi?id=230298
+
+ Reviewed by Keith Miller.
+
+ This patch optimizes JSON.parse.
+
+ 1. Use table in isJSONWhiteSpace.
+ 2. Extract primitive value creation as parsePrimitiveValue function to use it in different place.
+ 3. Add leaf-object creation fast path. Previously, when creating a leaf-object from JSON.parse we
+ are too generic and jumping around the code. Instead we add a fast path that does not perform
+ unnecessary operations and code gets tight.
+
+ It offers 3-4% improvement in microbenchmarks.
+ ToT Patched
+
+ vanilla-es2015-babel-webpack-todomvc-json-parse
+ 104.7169+-0.1113 ^ 101.4836+-0.2168 ^ definitely 1.0319x faster
+ flight-todomvc-json-parse 53.9074+-0.0957 ^ 52.1347+-0.0802 ^ definitely 1.0340x faster
+ vanilla-es2015-todomvc-json-parse 104.9373+-0.1631 ^ 101.4978+-0.1073 ^ definitely 1.0339x faster
+ vanilla-todomvc-json-parse 79.1330+-0.0963 ^ 76.7568+-0.1606 ^ definitely 1.0310x faster
+
+ This offers 0.2% improvement in Speedometer2.
+
+ ----------------------------------------------------------------------------------------------------------------------------------
+ | subtest | ms | ms | b / a | pValue (significance using False Discovery Rate) |
+ ----------------------------------------------------------------------------------------------------------------------------------
+ | Elm-TodoMVC |116.860000 |116.825000 |0.999700 | 0.901070 |
+ | VueJS-TodoMVC |24.658333 |24.763333 |1.004258 | 0.571728 |
+ | EmberJS-TodoMVC |126.666667 |126.335000 |0.997382 | 0.289517 |
+ | BackboneJS-TodoMVC |48.435000 |48.523333 |1.001824 | 0.455638 |
+ | Preact-TodoMVC |17.585000 |17.368333 |0.987679 | 0.247658 |
+ | AngularJS-TodoMVC |129.576667 |129.398333 |0.998624 | 0.625634 |
+ | Vanilla-ES2015-TodoMVC |62.746667 |62.241667 |0.991952 | 0.000019 (significant) |
+ | Inferno-TodoMVC |63.741667 |63.495000 |0.996130 | 0.448861 |
+ | Flight-TodoMVC |78.021667 |77.306667 |0.990836 | 0.087137 |
+ | Angular2-TypeScript-TodoMVC |39.823333 |39.923333 |1.002511 | 0.736279 |
+ | VanillaJS-TodoMVC |50.073333 |49.791667 |0.994375 | 0.136495 |
+ | jQuery-TodoMVC |221.300000 |221.586667 |1.001295 | 0.418008 |
+ | EmberJS-Debug-TodoMVC |340.145000 |339.965000 |0.999471 | 0.691490 |
+ | React-TodoMVC |85.698333 |85.650000 |0.999436 | 0.761586 |
+ | React-Redux-TodoMVC |140.510000 |140.785000 |1.001957 | 0.285922 |
+ | Vanilla-ES2015-Babel-Webpack-TodoMVC |60.928333 |60.500000 |0.992970 | 0.000069 (significant) |
+ ----------------------------------------------------------------------------------------------------------------------------------
+ a mean = 262.15844
+ b mean = 262.72261
+ pValue = 0.0428052487
+ (Bigger means are better.)
+ 1.002 times better
+ Results ARE significant
+
+ * runtime/LiteralParser.cpp:
+ (JSC::LiteralParser<CharType>::makeIdentifier):
+ (JSC::isJSONWhiteSpace):
+ (JSC::LiteralParser<CharType>::Lexer::lex):
+ (JSC::LiteralParser<CharType>::parsePrimitiveValue):
+ (JSC::LiteralParser<CharType>::parse):
+ * runtime/LiteralParser.h:
+
2021-09-14 Don Olmstead <[email protected]>
Non-unified build fixes, mid September 2021 edition
Modified: trunk/Source/_javascript_Core/runtime/LiteralParser.cpp (282467 => 282468)
--- trunk/Source/_javascript_Core/runtime/LiteralParser.cpp 2021-09-15 20:06:42 UTC (rev 282467)
+++ trunk/Source/_javascript_Core/runtime/LiteralParser.cpp 2021-09-15 20:17:05 UTC (rev 282468)
@@ -59,14 +59,6 @@
}
template <typename CharType>
-static ALWAYS_INLINE bool isJSONWhiteSpace(const CharType& c)
-{
- // The JSON RFC 4627 defines a list of allowed characters to be considered
- // insignificant white space: http://www.ietf.org/rfc/rfc4627.txt (2. JSON Grammar).
- return c == ' ' || c == 0x9 || c == 0xA || c == 0xD;
-}
-
-template <typename CharType>
bool LiteralParser<CharType>::tryJSONPParse(Vector<JSONPData>& results, bool needsFullSourceInfo)
{
VM& vm = m_globalObject->vm();
@@ -149,6 +141,15 @@
} while (m_lexer.currentToken()->type == TokIdentifier);
return m_lexer.currentToken()->type == TokEnd;
}
+
+template <typename CharType>
+ALWAYS_INLINE Identifier LiteralParser<CharType>::makeIdentifier(typename Lexer::LiteralParserTokenPtr token)
+{
+ if (token->stringIs8Bit)
+ return makeIdentifier(token->stringToken8, token->stringLength);
+ return makeIdentifier(token->stringToken16, token->stringLength);
+}
+
template <typename CharType>
template <typename LiteralCharType>
@@ -200,6 +201,8 @@
}
// 256 Latin-1 codes
+// The JSON RFC 4627 defines a list of allowed characters to be considered
+// insignificant white space: http://www.ietf.org/rfc/rfc4627.txt (2. JSON Grammar).
static constexpr const TokenType tokenTypesOfLatin1Characters[256] = {
/* 0 - Null */ TokError,
/* 1 - Start of Heading */ TokError,
@@ -210,11 +213,11 @@
/* 6 - Acknowledgment */ TokError,
/* 7 - Bell */ TokError,
/* 8 - Back Space */ TokError,
-/* 9 - Horizontal Tab */ TokError,
-/* 10 - Line Feed */ TokError,
+/* 9 - Horizontal Tab */ TokErrorSpace,
+/* 10 - Line Feed */ TokErrorSpace,
/* 11 - Vertical Tab */ TokError,
/* 12 - Form Feed */ TokError,
-/* 13 - Carriage Return */ TokError,
+/* 13 - Carriage Return */ TokErrorSpace,
/* 14 - Shift Out */ TokError,
/* 15 - Shift In */ TokError,
/* 16 - Data Line Escape */ TokError,
@@ -233,7 +236,7 @@
/* 29 - Group Separator */ TokError,
/* 30 - Record Separator */ TokError,
/* 31 - Unit Separator */ TokError,
-/* 32 - Space */ TokError,
+/* 32 - Space */ TokErrorSpace,
/* 33 - ! */ TokError,
/* 34 - " */ TokString,
/* 35 - # */ TokError,
@@ -720,6 +723,12 @@
};
template <typename CharType>
+static ALWAYS_INLINE bool isJSONWhiteSpace(const CharType& c)
+{
+ return isLatin1(c) && tokenTypesOfLatin1Characters[c] == TokErrorSpace;
+}
+
+template <typename CharType>
ALWAYS_INLINE TokenType LiteralParser<CharType>::Lexer::lex(LiteralParserToken<CharType>& token)
{
#if ASSERT_ENABLED
@@ -783,6 +792,7 @@
return lexNumber(token);
case TokError:
+ case TokErrorSpace:
break;
default:
@@ -1111,6 +1121,110 @@
}
template <typename CharType>
+void LiteralParser<CharType>::setErrorMessageForToken(TokenType tokenType)
+{
+ switch (tokenType) {
+ case TokRBrace:
+ m_parseErrorMessage = "Expected '}'"_s;
+ break;
+ case TokRBracket:
+ m_parseErrorMessage = "Expected ']'"_s;
+ break;
+ case TokColon:
+ m_parseErrorMessage = "Expected ':' before value in object property definition"_s;
+ break;
+ default: {
+ RELEASE_ASSERT_NOT_REACHED();
+ }
+ }
+}
+
+template <typename CharType>
+ALWAYS_INLINE JSValue LiteralParser<CharType>::parsePrimitiveValue(VM& vm)
+{
+ switch (m_lexer.currentToken()->type) {
+ case TokString: {
+ JSValue result = jsString(vm, makeIdentifier(m_lexer.currentToken()).string());
+ m_lexer.next();
+ return result;
+ }
+ case TokNumber: {
+ JSValue result = jsNumber(m_lexer.currentToken()->numberToken);
+ m_lexer.next();
+ return result;
+ }
+ case TokNull:
+ m_lexer.next();
+ return jsNull();
+ case TokTrue:
+ m_lexer.next();
+ return jsBoolean(true);
+ case TokFalse:
+ m_lexer.next();
+ return jsBoolean(false);
+ case TokRBracket:
+ m_parseErrorMessage = "Unexpected token ']'"_s;
+ return { };
+ case TokRBrace:
+ m_parseErrorMessage = "Unexpected token '}'"_s;
+ return { };
+ case TokIdentifier: {
+ auto token = m_lexer.currentToken();
+
+ auto tryMakeErrorString = [&] (unsigned length) -> String {
+ bool addEllipsis = length != token->stringLength;
+ if (token->stringIs8Bit)
+ return tryMakeString("Unexpected identifier \"", StringView { token->stringToken8, length }, addEllipsis ? "..." : "", '"');
+ return tryMakeString("Unexpected identifier \"", StringView { token->stringToken16, length }, addEllipsis ? "..." : "", '"');
+ };
+
+ constexpr unsigned maxLength = 200;
+
+ String errorString = tryMakeErrorString(std::min(token->stringLength, maxLength));
+ if (!errorString) {
+ constexpr unsigned shortLength = 10;
+ if (token->stringLength > shortLength)
+ errorString = tryMakeErrorString(shortLength);
+ if (!errorString)
+ errorString = "Unexpected identifier";
+ }
+
+ m_parseErrorMessage = errorString;
+ return { };
+ }
+ case TokColon:
+ m_parseErrorMessage = "Unexpected token ':'"_s;
+ return { };
+ case TokLParen:
+ m_parseErrorMessage = "Unexpected token '('"_s;
+ return { };
+ case TokRParen:
+ m_parseErrorMessage = "Unexpected token ')'"_s;
+ return { };
+ case TokComma:
+ m_parseErrorMessage = "Unexpected token ','"_s;
+ return { };
+ case TokDot:
+ m_parseErrorMessage = "Unexpected token '.'"_s;
+ return { };
+ case TokAssign:
+ m_parseErrorMessage = "Unexpected token '='"_s;
+ return { };
+ case TokSemi:
+ m_parseErrorMessage = "Unexpected token ';'"_s;
+ return { };
+ case TokEnd:
+ m_parseErrorMessage = "Unexpected EOF"_s;
+ return { };
+ case TokError:
+ default:
+ // Error
+ m_parseErrorMessage = "Could not parse value _expression_"_s;
+ return { };
+ }
+}
+
+template <typename CharType>
JSValue LiteralParser<CharType>::parse(ParserState initialState)
{
VM& vm = m_globalObject->vm();
@@ -1123,298 +1237,257 @@
HashSet<JSObject*> visitedUnderscoreProto;
while (1) {
switch(state) {
- startParseArray:
- case StartParseArray: {
- JSArray* array = constructEmptyArray(m_globalObject, nullptr);
- RETURN_IF_EXCEPTION(scope, JSValue());
- objectStack.appendWithCrashOnOverflow(array);
- }
- doParseArrayStartExpression:
- FALLTHROUGH;
- case DoParseArrayStartExpression: {
- TokenType lastToken = m_lexer.currentToken()->type;
- if (m_lexer.next() == TokRBracket) {
- if (lastToken == TokComma) {
- m_parseErrorMessage = "Unexpected comma at the end of array _expression_"_s;
- return JSValue();
- }
- m_lexer.next();
- lastValue = objectStack.takeLast();
- break;
+ startParseArray:
+ case StartParseArray: {
+ JSArray* array = constructEmptyArray(m_globalObject, nullptr);
+ RETURN_IF_EXCEPTION(scope, { });
+ objectStack.appendWithCrashOnOverflow(array);
+ }
+ doParseArrayStartExpression:
+ FALLTHROUGH;
+ case DoParseArrayStartExpression: {
+ TokenType lastToken = m_lexer.currentToken()->type;
+ if (m_lexer.next() == TokRBracket) {
+ if (UNLIKELY(lastToken == TokComma)) {
+ m_parseErrorMessage = "Unexpected comma at the end of array _expression_"_s;
+ return { };
}
-
- stateStack.append(DoParseArrayEndExpression);
- goto startParseExpression;
- }
- case DoParseArrayEndExpression: {
- JSArray* array = asArray(objectStack.last());
- array->putDirectIndex(m_globalObject, array->length(), lastValue);
- RETURN_IF_EXCEPTION(scope, JSValue());
-
- if (m_lexer.currentToken()->type == TokComma)
- goto doParseArrayStartExpression;
-
- if (m_lexer.currentToken()->type != TokRBracket) {
- m_parseErrorMessage = "Expected ']'"_s;
- return JSValue();
- }
-
m_lexer.next();
lastValue = objectStack.takeLast();
break;
}
- startParseObject:
- case StartParseObject: {
- JSObject* object = constructEmptyObject(m_globalObject);
- objectStack.appendWithCrashOnOverflow(object);
- TokenType type = m_lexer.next();
- if (type == TokString || (m_mode != StrictJSON && type == TokIdentifier)) {
- typename Lexer::LiteralParserTokenPtr identifierToken = m_lexer.currentToken();
- if (identifierToken->stringIs8Bit)
- identifierStack.append(makeIdentifier(identifierToken->stringToken8, identifierToken->stringLength));
- else
- identifierStack.append(makeIdentifier(identifierToken->stringToken16, identifierToken->stringLength));
+ stateStack.append(DoParseArrayEndExpression);
+ goto startParseExpression;
+ }
+ case DoParseArrayEndExpression: {
+ JSArray* array = asArray(objectStack.last());
+ array->putDirectIndex(m_globalObject, array->length(), lastValue);
+ RETURN_IF_EXCEPTION(scope, { });
- // Check for colon
- if (m_lexer.next() != TokColon) {
- m_parseErrorMessage = "Expected ':' before value in object property definition"_s;
- return JSValue();
- }
-
- m_lexer.next();
- stateStack.append(DoParseObjectEndExpression);
- goto startParseExpression;
- }
- if (type != TokRBrace) {
- m_parseErrorMessage = "Expected '}'"_s;
- return JSValue();
- }
- m_lexer.next();
- lastValue = objectStack.takeLast();
- break;
+ if (m_lexer.currentToken()->type == TokComma)
+ goto doParseArrayStartExpression;
+
+ if (UNLIKELY(m_lexer.currentToken()->type != TokRBracket)) {
+ setErrorMessageForToken(TokRBracket);
+ return { };
}
- doParseObjectStartExpression:
- case DoParseObjectStartExpression: {
- TokenType type = m_lexer.next();
- if (type != TokString && (m_mode == StrictJSON || type != TokIdentifier)) {
- m_parseErrorMessage = "Property name must be a string literal"_s;
- return JSValue();
- }
- typename Lexer::LiteralParserTokenPtr identifierToken = m_lexer.currentToken();
- if (identifierToken->stringIs8Bit)
- identifierStack.append(makeIdentifier(identifierToken->stringToken8, identifierToken->stringLength));
- else
- identifierStack.append(makeIdentifier(identifierToken->stringToken16, identifierToken->stringLength));
+
+ m_lexer.next();
+ lastValue = objectStack.takeLast();
+ break;
+ }
+ startParseObject:
+ case StartParseObject: {
+ JSObject* object = constructEmptyObject(m_globalObject);
- // Check for colon
- if (m_lexer.next() != TokColon) {
- m_parseErrorMessage = "Expected ':'"_s;
- return JSValue();
- }
+ TokenType type = m_lexer.next();
+ if (type == TokString || (m_mode != StrictJSON && type == TokIdentifier)) {
+ while (true) {
+ Identifier ident = makeIdentifier(m_lexer.currentToken());
- m_lexer.next();
- stateStack.append(DoParseObjectEndExpression);
- goto startParseExpression;
- }
- case DoParseObjectEndExpression:
- {
- JSObject* object = asObject(objectStack.last());
- Identifier ident = identifierStack.takeLast();
- if (m_mode != StrictJSON && ident == vm.propertyNames->underscoreProto) {
- if (!visitedUnderscoreProto.add(object).isNewEntry) {
- m_parseErrorMessage = "Attempted to redefine __proto__ property"_s;
- return JSValue();
+ if (UNLIKELY(m_lexer.next() != TokColon)) {
+ setErrorMessageForToken(TokColon);
+ return { };
}
- PutPropertySlot slot(object, m_nullOrCodeBlock ? m_nullOrCodeBlock->ownerExecutable()->isInStrictContext() : false);
- objectStack.last().put(m_globalObject, ident, lastValue, slot);
- } else {
- if (std::optional<uint32_t> index = parseIndex(ident))
- object->putDirectIndex(m_globalObject, index.value(), lastValue);
- else
- object->putDirect(vm, ident, lastValue);
- }
- RETURN_IF_EXCEPTION(scope, JSValue());
- if (m_lexer.currentToken()->type == TokComma)
- goto doParseObjectStartExpression;
- if (m_lexer.currentToken()->type != TokRBrace) {
- m_parseErrorMessage = "Expected '}'"_s;
- return JSValue();
- }
- m_lexer.next();
- lastValue = objectStack.takeLast();
- break;
- }
- startParseExpression:
- case StartParseExpression: {
- switch (m_lexer.currentToken()->type) {
- case TokLBracket:
+
+ TokenType nextType = m_lexer.next();
+ if (nextType == TokLBrace || nextType == TokLBracket) {
+ objectStack.appendWithCrashOnOverflow(object);
+ identifierStack.append(WTFMove(ident));
+ stateStack.append(DoParseObjectEndExpression);
+ if (nextType == TokLBrace)
+ goto startParseObject;
+ ASSERT(nextType == TokLBracket);
goto startParseArray;
- case TokLBrace:
- goto startParseObject;
- case TokString: {
- typename Lexer::LiteralParserTokenPtr stringToken = m_lexer.currentToken();
- if (stringToken->stringIs8Bit)
- lastValue = jsString(vm, makeIdentifier(stringToken->stringToken8, stringToken->stringLength).string());
- else
- lastValue = jsString(vm, makeIdentifier(stringToken->stringToken16, stringToken->stringLength).string());
- m_lexer.next();
- break;
}
- case TokNumber: {
- typename Lexer::LiteralParserTokenPtr numberToken = m_lexer.currentToken();
- lastValue = jsNumber(numberToken->numberToken);
- m_lexer.next();
- break;
+
+ // Leaf object construction fast path.
+ JSValue primitive = parsePrimitiveValue(vm);
+ if (UNLIKELY(!primitive))
+ return { };
+
+ if (m_mode != StrictJSON && ident == vm.propertyNames->underscoreProto) {
+ if (UNLIKELY(!visitedUnderscoreProto.add(object).isNewEntry)) {
+ m_parseErrorMessage = "Attempted to redefine __proto__ property"_s;
+ return { };
+ }
+ PutPropertySlot slot(object, m_nullOrCodeBlock ? m_nullOrCodeBlock->ownerExecutable()->isInStrictContext() : false);
+ JSValue(object).put(m_globalObject, ident, primitive, slot);
+ RETURN_IF_EXCEPTION(scope, { });
+ } else {
+ if (std::optional<uint32_t> index = parseIndex(ident)) {
+ object->putDirectIndex(m_globalObject, index.value(), primitive);
+ RETURN_IF_EXCEPTION(scope, { });
+ } else
+ object->putDirect(vm, ident, primitive);
}
- case TokNull:
- m_lexer.next();
- lastValue = jsNull();
- break;
- case TokTrue:
- m_lexer.next();
- lastValue = jsBoolean(true);
+ if (m_lexer.currentToken()->type != TokComma)
break;
- case TokFalse:
- m_lexer.next();
- lastValue = jsBoolean(false);
- break;
- case TokRBracket:
- m_parseErrorMessage = "Unexpected token ']'"_s;
- return JSValue();
- case TokRBrace:
- m_parseErrorMessage = "Unexpected token '}'"_s;
- return JSValue();
- case TokIdentifier: {
- auto token = m_lexer.currentToken();
+ nextType = m_lexer.next();
+ if (UNLIKELY(nextType != TokString && (m_mode == StrictJSON || nextType != TokIdentifier))) {
+ m_parseErrorMessage = "Property name must be a string literal"_s;
+ return { };
+ }
+ }
- auto tryMakeErrorString = [&] (unsigned length) -> String {
- bool addEllipsis = length != token->stringLength;
- if (token->stringIs8Bit)
- return tryMakeString("Unexpected identifier \"", StringView { token->stringToken8, length }, addEllipsis ? "..." : "", '"');
- return tryMakeString("Unexpected identifier \"", StringView { token->stringToken16, length }, addEllipsis ? "..." : "", '"');
- };
+ if (UNLIKELY(m_lexer.currentToken()->type != TokRBrace)) {
+ setErrorMessageForToken(TokRBrace);
+ return { };
+ }
+ m_lexer.next();
+ lastValue = object;
+ break;
+ }
- constexpr unsigned maxLength = 200;
+ if (UNLIKELY(type != TokRBrace)) {
+ setErrorMessageForToken(TokRBrace);
+ return { };
+ }
- String errorString = tryMakeErrorString(std::min(token->stringLength, maxLength));
- if (!errorString) {
- constexpr unsigned shortLength = 10;
- if (token->stringLength > shortLength)
- errorString = tryMakeErrorString(shortLength);
- if (!errorString)
- errorString = "Unexpected identifier";
- }
+ m_lexer.next();
+ lastValue = object;
+ break;
+ }
+ doParseObjectStartExpression:
+ case DoParseObjectStartExpression: {
+ TokenType type = m_lexer.next();
+ if (UNLIKELY(type != TokString && (m_mode == StrictJSON || type != TokIdentifier))) {
+ m_parseErrorMessage = "Property name must be a string literal"_s;
+ return { };
+ }
+ identifierStack.append(makeIdentifier(m_lexer.currentToken()));
- m_parseErrorMessage = errorString;
- return JSValue();
- }
- case TokColon:
- m_parseErrorMessage = "Unexpected token ':'"_s;
- return JSValue();
- case TokLParen:
- m_parseErrorMessage = "Unexpected token '('"_s;
- return JSValue();
- case TokRParen:
- m_parseErrorMessage = "Unexpected token ')'"_s;
- return JSValue();
- case TokComma:
- m_parseErrorMessage = "Unexpected token ','"_s;
- return JSValue();
- case TokDot:
- m_parseErrorMessage = "Unexpected token '.'"_s;
- return JSValue();
- case TokAssign:
- m_parseErrorMessage = "Unexpected token '='"_s;
- return JSValue();
- case TokSemi:
- m_parseErrorMessage = "Unexpected token ';'"_s;
- return JSValue();
- case TokEnd:
- m_parseErrorMessage = "Unexpected EOF"_s;
- return JSValue();
- case TokError:
- default:
- // Error
- m_parseErrorMessage = "Could not parse value _expression_"_s;
- return JSValue();
+ // Check for colon
+ if (UNLIKELY(m_lexer.next() != TokColon)) {
+ setErrorMessageForToken(TokColon);
+ return { };
+ }
+
+ m_lexer.next();
+ stateStack.append(DoParseObjectEndExpression);
+ goto startParseExpression;
+ }
+ case DoParseObjectEndExpression:
+ {
+ JSObject* object = asObject(objectStack.last());
+ Identifier ident = identifierStack.takeLast();
+ if (m_mode != StrictJSON && ident == vm.propertyNames->underscoreProto) {
+ if (UNLIKELY(!visitedUnderscoreProto.add(object).isNewEntry)) {
+ m_parseErrorMessage = "Attempted to redefine __proto__ property"_s;
+ return { };
}
+ PutPropertySlot slot(object, m_nullOrCodeBlock ? m_nullOrCodeBlock->ownerExecutable()->isInStrictContext() : false);
+ JSValue(object).put(m_globalObject, ident, lastValue, slot);
+ RETURN_IF_EXCEPTION(scope, { });
+ } else {
+ if (std::optional<uint32_t> index = parseIndex(ident)) {
+ object->putDirectIndex(m_globalObject, index.value(), lastValue);
+ RETURN_IF_EXCEPTION(scope, { });
+ } else
+ object->putDirect(vm, ident, lastValue);
+ }
+ if (m_lexer.currentToken()->type == TokComma)
+ goto doParseObjectStartExpression;
+ if (UNLIKELY(m_lexer.currentToken()->type != TokRBrace)) {
+ setErrorMessageForToken(TokRBrace);
+ return { };
+ }
+ m_lexer.next();
+ lastValue = objectStack.takeLast();
+ break;
+ }
+ startParseExpression:
+ case StartParseExpression: {
+ TokenType type = m_lexer.currentToken()->type;
+ if (type == TokLBracket)
+ goto startParseArray;
+ if (type == TokLBrace)
+ goto startParseObject;
+ lastValue = parsePrimitiveValue(vm);
+ if (UNLIKELY(!lastValue))
+ return { };
+ break;
+ }
+ case StartParseStatement: {
+ switch (m_lexer.currentToken()->type) {
+ case TokLBracket:
+ case TokNumber:
+ case TokString: {
+ lastValue = parsePrimitiveValue(vm);
+ if (UNLIKELY(!lastValue))
+ return { };
break;
}
- case StartParseStatement: {
- switch (m_lexer.currentToken()->type) {
- case TokLBracket:
- case TokNumber:
- case TokString:
- goto startParseExpression;
- case TokLParen: {
- m_lexer.next();
- stateStack.append(StartParseStatementEndStatement);
- goto startParseExpression;
- }
- case TokRBracket:
- m_parseErrorMessage = "Unexpected token ']'"_s;
- return JSValue();
- case TokLBrace:
- m_parseErrorMessage = "Unexpected token '{'"_s;
- return JSValue();
- case TokRBrace:
- m_parseErrorMessage = "Unexpected token '}'"_s;
- return JSValue();
- case TokIdentifier:
- m_parseErrorMessage = "Unexpected identifier"_s;
- return JSValue();
- case TokColon:
- m_parseErrorMessage = "Unexpected token ':'"_s;
- return JSValue();
- case TokRParen:
- m_parseErrorMessage = "Unexpected token ')'"_s;
- return JSValue();
- case TokComma:
- m_parseErrorMessage = "Unexpected token ','"_s;
- return JSValue();
- case TokTrue:
- m_parseErrorMessage = "Unexpected token 'true'"_s;
- return JSValue();
- case TokFalse:
- m_parseErrorMessage = "Unexpected token 'false'"_s;
- return JSValue();
- case TokNull:
- m_parseErrorMessage = "Unexpected token 'null'"_s;
- return JSValue();
- case TokEnd:
- m_parseErrorMessage = "Unexpected EOF"_s;
- return JSValue();
- case TokDot:
- m_parseErrorMessage = "Unexpected token '.'"_s;
- return JSValue();
- case TokAssign:
- m_parseErrorMessage = "Unexpected token '='"_s;
- return JSValue();
- case TokSemi:
- m_parseErrorMessage = "Unexpected token ';'"_s;
- return JSValue();
- case TokError:
- default:
- m_parseErrorMessage = "Could not parse statement"_s;
- return JSValue();
- }
+ case TokLParen: {
+ m_lexer.next();
+ stateStack.append(StartParseStatementEndStatement);
+ goto startParseExpression;
}
- case StartParseStatementEndStatement: {
- ASSERT(stateStack.isEmpty());
- if (m_lexer.currentToken()->type != TokRParen)
- return JSValue();
- if (m_lexer.next() == TokEnd)
- return lastValue;
- m_parseErrorMessage = "Unexpected content at end of JSON literal"_s;
- return JSValue();
+ case TokRBracket:
+ m_parseErrorMessage = "Unexpected token ']'"_s;
+ return { };
+ case TokLBrace:
+ m_parseErrorMessage = "Unexpected token '{'"_s;
+ return { };
+ case TokRBrace:
+ m_parseErrorMessage = "Unexpected token '}'"_s;
+ return { };
+ case TokIdentifier:
+ m_parseErrorMessage = "Unexpected identifier"_s;
+ return { };
+ case TokColon:
+ m_parseErrorMessage = "Unexpected token ':'"_s;
+ return { };
+ case TokRParen:
+ m_parseErrorMessage = "Unexpected token ')'"_s;
+ return { };
+ case TokComma:
+ m_parseErrorMessage = "Unexpected token ','"_s;
+ return { };
+ case TokTrue:
+ m_parseErrorMessage = "Unexpected token 'true'"_s;
+ return { };
+ case TokFalse:
+ m_parseErrorMessage = "Unexpected token 'false'"_s;
+ return { };
+ case TokNull:
+ m_parseErrorMessage = "Unexpected token 'null'"_s;
+ return { };
+ case TokEnd:
+ m_parseErrorMessage = "Unexpected EOF"_s;
+ return { };
+ case TokDot:
+ m_parseErrorMessage = "Unexpected token '.'"_s;
+ return { };
+ case TokAssign:
+ m_parseErrorMessage = "Unexpected token '='"_s;
+ return { };
+ case TokSemi:
+ m_parseErrorMessage = "Unexpected token ';'"_s;
+ return { };
+ case TokError:
+ default:
+ m_parseErrorMessage = "Could not parse statement"_s;
+ return { };
}
- default:
- RELEASE_ASSERT_NOT_REACHED();
+ break;
}
+ case StartParseStatementEndStatement: {
+ ASSERT(stateStack.isEmpty());
+ if (m_lexer.currentToken()->type != TokRParen)
+ return { };
+ if (m_lexer.next() == TokEnd)
+ return lastValue;
+ m_parseErrorMessage = "Unexpected content at end of JSON literal"_s;
+ return { };
+ }
+ default:
+ RELEASE_ASSERT_NOT_REACHED();
+ }
if (stateStack.isEmpty())
return lastValue;
state = stateStack.takeLast();
Modified: trunk/Source/_javascript_Core/runtime/LiteralParser.h (282467 => 282468)
--- trunk/Source/_javascript_Core/runtime/LiteralParser.h 2021-09-15 20:06:42 UTC (rev 282467)
+++ trunk/Source/_javascript_Core/runtime/LiteralParser.h 2021-09-15 20:17:05 UTC (rev 282468)
@@ -52,7 +52,7 @@
TokLBracket, TokRBracket, TokLBrace, TokRBrace,
TokString, TokIdentifier, TokNumber, TokColon,
TokLParen, TokRParen, TokComma, TokTrue, TokFalse,
- TokNull, TokEnd, TokDot, TokAssign, TokSemi, TokError };
+ TokNull, TokEnd, TokDot, TokAssign, TokSemi, TokError, TokErrorSpace };
struct JSONPPathEntry {
Identifier m_pathEntryName;
@@ -138,7 +138,7 @@
TokenType next();
#if !ASSERT_ENABLED
- typedef const LiteralParserToken<CharType>* LiteralParserTokenPtr;
+ using LiteralParserTokenPtr = const LiteralParserToken<CharType>*;
LiteralParserTokenPtr currentToken()
{
@@ -195,9 +195,14 @@
class StackGuard;
JSValue parse(ParserState);
+ JSValue parsePrimitiveValue(VM&);
+
+ ALWAYS_INLINE Identifier makeIdentifier(typename Lexer::LiteralParserTokenPtr);
template<typename LiteralCharType>
ALWAYS_INLINE Identifier makeIdentifier(const LiteralCharType* characters, size_t length);
+ void setErrorMessageForToken(TokenType);
+
JSGlobalObject* m_globalObject;
CodeBlock* m_nullOrCodeBlock;
typename LiteralParser<CharType>::Lexer m_lexer;