Changeset 282468 in webkit


Ignore:
Timestamp:
Sep 15, 2021 1:17:05 PM (10 months ago)
Author:
ysuzuki@apple.com
Message:

[JSC] Optimize leaf object creation in JSON.parse
https://bugs.webkit.org/show_bug.cgi?id=230298

Reviewed by Keith Miller.

JSTests:

  • microbenchmarks/json-parse-leaf-object.js: Added.

Source/JavaScriptCore:

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:

LayoutTests:

  • js/dom/JSON-parse-expected.txt:
Location:
trunk
Files:
2 added
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/JSTests/ChangeLog

    r282373 r282468  
     12021-09-15  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [JSC] Optimize leaf object creation in JSON.parse
     4        https://bugs.webkit.org/show_bug.cgi?id=230298
     5
     6        Reviewed by Keith Miller.
     7
     8        * microbenchmarks/json-parse-leaf-object.js: Added.
     9
    1102021-09-13  Yusuke Suzuki  <ysuzuki@apple.com>
    211
  • trunk/LayoutTests/ChangeLog

    r282467 r282468  
     12021-09-15  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [JSC] Optimize leaf object creation in JSON.parse
     4        https://bugs.webkit.org/show_bug.cgi?id=230298
     5
     6        Reviewed by Keith Miller.
     7
     8        * js/dom/JSON-parse-expected.txt:
     9
    1102021-09-15  Chris Dumez  <cdumez@apple.com>
    211
  • trunk/LayoutTests/js/dom/JSON-parse-expected.txt

    r246162 r282468  
    8686        return jsonObject.parse('{"a":5,"a",}');
    8787    }
    88 PASS tests[i](nativeJSON) threw exception SyntaxError: JSON Parse error: Expected ':'.
     88PASS tests[i](nativeJSON) threw exception SyntaxError: JSON Parse error: Expected ':' before value in object property definition.
    8989function (jsonObject){
    9090        return jsonObject.parse('{"a":(5,"a"),}');
  • trunk/Source/JavaScriptCore/ChangeLog

    r282430 r282468  
     12021-09-15  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [JSC] Optimize leaf object creation in JSON.parse
     4        https://bugs.webkit.org/show_bug.cgi?id=230298
     5
     6        Reviewed by Keith Miller.
     7
     8        This patch optimizes JSON.parse.
     9
     10        1. Use table in isJSONWhiteSpace.
     11        2. Extract primitive value creation as parsePrimitiveValue function to use it in different place.
     12        3. Add leaf-object creation fast path. Previously, when creating a leaf-object from JSON.parse we
     13           are too generic and jumping around the code. Instead we add a fast path that does not perform
     14           unnecessary operations and code gets tight.
     15
     16        It offers 3-4% improvement in microbenchmarks.
     17                                                             ToT                     Patched
     18
     19        vanilla-es2015-babel-webpack-todomvc-json-parse
     20                                                      104.7169+-0.1113     ^    101.4836+-0.2168        ^ definitely 1.0319x faster
     21        flight-todomvc-json-parse                      53.9074+-0.0957     ^     52.1347+-0.0802        ^ definitely 1.0340x faster
     22        vanilla-es2015-todomvc-json-parse             104.9373+-0.1631     ^    101.4978+-0.1073        ^ definitely 1.0339x faster
     23        vanilla-todomvc-json-parse                     79.1330+-0.0963     ^     76.7568+-0.1606        ^ definitely 1.0310x faster
     24
     25        This offers 0.2% improvement in Speedometer2.
     26
     27        ----------------------------------------------------------------------------------------------------------------------------------
     28        |               subtest                |     ms      |     ms      |  b / a   | pValue (significance using False Discovery Rate) |
     29        ----------------------------------------------------------------------------------------------------------------------------------
     30        | Elm-TodoMVC                          |116.860000   |116.825000   |0.999700  | 0.901070                                         |
     31        | VueJS-TodoMVC                        |24.658333    |24.763333    |1.004258  | 0.571728                                         |
     32        | EmberJS-TodoMVC                      |126.666667   |126.335000   |0.997382  | 0.289517                                         |
     33        | BackboneJS-TodoMVC                   |48.435000    |48.523333    |1.001824  | 0.455638                                         |
     34        | Preact-TodoMVC                       |17.585000    |17.368333    |0.987679  | 0.247658                                         |
     35        | AngularJS-TodoMVC                    |129.576667   |129.398333   |0.998624  | 0.625634                                         |
     36        | Vanilla-ES2015-TodoMVC               |62.746667    |62.241667    |0.991952  | 0.000019 (significant)                           |
     37        | Inferno-TodoMVC                      |63.741667    |63.495000    |0.996130  | 0.448861                                         |
     38        | Flight-TodoMVC                       |78.021667    |77.306667    |0.990836  | 0.087137                                         |
     39        | Angular2-TypeScript-TodoMVC          |39.823333    |39.923333    |1.002511  | 0.736279                                         |
     40        | VanillaJS-TodoMVC                    |50.073333    |49.791667    |0.994375  | 0.136495                                         |
     41        | jQuery-TodoMVC                       |221.300000   |221.586667   |1.001295  | 0.418008                                         |
     42        | EmberJS-Debug-TodoMVC                |340.145000   |339.965000   |0.999471  | 0.691490                                         |
     43        | React-TodoMVC                        |85.698333    |85.650000    |0.999436  | 0.761586                                         |
     44        | React-Redux-TodoMVC                  |140.510000   |140.785000   |1.001957  | 0.285922                                         |
     45        | Vanilla-ES2015-Babel-Webpack-TodoMVC |60.928333    |60.500000    |0.992970  | 0.000069 (significant)                           |
     46        ----------------------------------------------------------------------------------------------------------------------------------
     47        a mean = 262.15844
     48        b mean = 262.72261
     49        pValue = 0.0428052487
     50        (Bigger means are better.)
     51        1.002 times better
     52        Results ARE significant
     53
     54        * runtime/LiteralParser.cpp:
     55        (JSC::LiteralParser<CharType>::makeIdentifier):
     56        (JSC::isJSONWhiteSpace):
     57        (JSC::LiteralParser<CharType>::Lexer::lex):
     58        (JSC::LiteralParser<CharType>::parsePrimitiveValue):
     59        (JSC::LiteralParser<CharType>::parse):
     60        * runtime/LiteralParser.h:
     61
    1622021-09-14  Don Olmstead  <don.olmstead@sony.com>
    263
  • trunk/Source/JavaScriptCore/runtime/LiteralParser.cpp

    r279393 r282468  
    6060
    6161template <typename CharType>
    62 static ALWAYS_INLINE bool isJSONWhiteSpace(const CharType& c)
    63 {
    64     // The JSON RFC 4627 defines a list of allowed characters to be considered
    65     // insignificant white space: http://www.ietf.org/rfc/rfc4627.txt (2. JSON Grammar).
    66     return c == ' ' || c == 0x9 || c == 0xA || c == 0xD;
    67 }
    68 
    69 template <typename CharType>
    7062bool LiteralParser<CharType>::tryJSONPParse(Vector<JSONPData>& results, bool needsFullSourceInfo)
    7163{
     
    150142    return m_lexer.currentToken()->type == TokEnd;
    151143}
     144
     145template <typename CharType>
     146ALWAYS_INLINE Identifier LiteralParser<CharType>::makeIdentifier(typename Lexer::LiteralParserTokenPtr token)
     147{
     148    if (token->stringIs8Bit)
     149        return makeIdentifier(token->stringToken8, token->stringLength);
     150    return makeIdentifier(token->stringToken16, token->stringLength);
     151}
     152
    152153   
    153154template <typename CharType>
     
    201202
    202203// 256 Latin-1 codes
     204// The JSON RFC 4627 defines a list of allowed characters to be considered
     205// insignificant white space: http://www.ietf.org/rfc/rfc4627.txt (2. JSON Grammar).
    203206static constexpr const TokenType tokenTypesOfLatin1Characters[256] = {
    204207/*   0 - Null               */ TokError,
     
    211214/*   7 - Bell               */ TokError,
    212215/*   8 - Back Space         */ TokError,
    213 /*   9 - Horizontal Tab     */ TokError,
    214 /*  10 - Line Feed          */ TokError,
     216/*   9 - Horizontal Tab     */ TokErrorSpace,
     217/*  10 - Line Feed          */ TokErrorSpace,
    215218/*  11 - Vertical Tab       */ TokError,
    216219/*  12 - Form Feed          */ TokError,
    217 /*  13 - Carriage Return    */ TokError,
     220/*  13 - Carriage Return    */ TokErrorSpace,
    218221/*  14 - Shift Out          */ TokError,
    219222/*  15 - Shift In           */ TokError,
     
    234237/*  30 - Record Separator   */ TokError,
    235238/*  31 - Unit Separator     */ TokError,
    236 /*  32 - Space              */ TokError,
     239/*  32 - Space              */ TokErrorSpace,
    237240/*  33 - !                  */ TokError,
    238241/*  34 - "                  */ TokString,
     
    721724
    722725template <typename CharType>
     726static ALWAYS_INLINE bool isJSONWhiteSpace(const CharType& c)
     727{
     728    return isLatin1(c) && tokenTypesOfLatin1Characters[c] == TokErrorSpace;
     729}
     730
     731template <typename CharType>
    723732ALWAYS_INLINE TokenType LiteralParser<CharType>::Lexer::lex(LiteralParserToken<CharType>& token)
    724733{
     
    784793
    785794        case TokError:
     795        case TokErrorSpace:
    786796            break;
    787797
     
    11121122
    11131123template <typename CharType>
     1124void LiteralParser<CharType>::setErrorMessageForToken(TokenType tokenType)
     1125{
     1126    switch (tokenType) {
     1127    case TokRBrace:
     1128        m_parseErrorMessage = "Expected '}'"_s;
     1129        break;
     1130    case TokRBracket:
     1131        m_parseErrorMessage = "Expected ']'"_s;
     1132        break;
     1133    case TokColon:
     1134        m_parseErrorMessage = "Expected ':' before value in object property definition"_s;
     1135        break;
     1136    default: {
     1137        RELEASE_ASSERT_NOT_REACHED();
     1138    }
     1139    }
     1140}
     1141
     1142template <typename CharType>
     1143ALWAYS_INLINE JSValue LiteralParser<CharType>::parsePrimitiveValue(VM& vm)
     1144{
     1145    switch (m_lexer.currentToken()->type) {
     1146    case TokString: {
     1147        JSValue result = jsString(vm, makeIdentifier(m_lexer.currentToken()).string());
     1148        m_lexer.next();
     1149        return result;
     1150    }
     1151    case TokNumber: {
     1152        JSValue result = jsNumber(m_lexer.currentToken()->numberToken);
     1153        m_lexer.next();
     1154        return result;
     1155    }
     1156    case TokNull:
     1157        m_lexer.next();
     1158        return jsNull();
     1159    case TokTrue:
     1160        m_lexer.next();
     1161        return jsBoolean(true);
     1162    case TokFalse:
     1163        m_lexer.next();
     1164        return jsBoolean(false);
     1165    case TokRBracket:
     1166        m_parseErrorMessage = "Unexpected token ']'"_s;
     1167        return { };
     1168    case TokRBrace:
     1169        m_parseErrorMessage = "Unexpected token '}'"_s;
     1170        return { };
     1171    case TokIdentifier: {
     1172        auto token = m_lexer.currentToken();
     1173
     1174        auto tryMakeErrorString = [&] (unsigned length) -> String {
     1175            bool addEllipsis = length != token->stringLength;
     1176            if (token->stringIs8Bit)
     1177                return tryMakeString("Unexpected identifier \"", StringView { token->stringToken8, length }, addEllipsis ? "..." : "", '"');
     1178            return tryMakeString("Unexpected identifier \"", StringView { token->stringToken16, length }, addEllipsis ? "..." : "", '"');
     1179        };
     1180
     1181        constexpr unsigned maxLength = 200;
     1182
     1183        String errorString = tryMakeErrorString(std::min(token->stringLength, maxLength));
     1184        if (!errorString) {
     1185            constexpr unsigned shortLength = 10;
     1186            if (token->stringLength > shortLength)
     1187                errorString = tryMakeErrorString(shortLength);
     1188            if (!errorString)
     1189                errorString = "Unexpected identifier";
     1190        }
     1191
     1192        m_parseErrorMessage = errorString;
     1193        return { };
     1194    }
     1195    case TokColon:
     1196        m_parseErrorMessage = "Unexpected token ':'"_s;
     1197        return { };
     1198    case TokLParen:
     1199        m_parseErrorMessage = "Unexpected token '('"_s;
     1200        return { };
     1201    case TokRParen:
     1202        m_parseErrorMessage = "Unexpected token ')'"_s;
     1203        return { };
     1204    case TokComma:
     1205        m_parseErrorMessage = "Unexpected token ','"_s;
     1206        return { };
     1207    case TokDot:
     1208        m_parseErrorMessage = "Unexpected token '.'"_s;
     1209        return { };
     1210    case TokAssign:
     1211        m_parseErrorMessage = "Unexpected token '='"_s;
     1212        return { };
     1213    case TokSemi:
     1214        m_parseErrorMessage = "Unexpected token ';'"_s;
     1215        return { };
     1216    case TokEnd:
     1217        m_parseErrorMessage = "Unexpected EOF"_s;
     1218        return { };
     1219    case TokError:
     1220    default:
     1221        // Error
     1222        m_parseErrorMessage = "Could not parse value expression"_s;
     1223        return { };
     1224    }
     1225}
     1226
     1227template <typename CharType>
    11141228JSValue LiteralParser<CharType>::parse(ParserState initialState)
    11151229{
     
    11241238    while (1) {
    11251239        switch(state) {
    1126             startParseArray:
    1127             case StartParseArray: {
    1128                 JSArray* array = constructEmptyArray(m_globalObject, nullptr);
    1129                 RETURN_IF_EXCEPTION(scope, JSValue());
    1130                 objectStack.appendWithCrashOnOverflow(array);
    1131             }
    1132             doParseArrayStartExpression:
    1133             FALLTHROUGH;
    1134             case DoParseArrayStartExpression: {
    1135                 TokenType lastToken = m_lexer.currentToken()->type;
    1136                 if (m_lexer.next() == TokRBracket) {
    1137                     if (lastToken == TokComma) {
    1138                         m_parseErrorMessage = "Unexpected comma at the end of array expression"_s;
    1139                         return JSValue();
    1140                     }
    1141                     m_lexer.next();
    1142                     lastValue = objectStack.takeLast();
    1143                     break;
    1144                 }
    1145 
    1146                 stateStack.append(DoParseArrayEndExpression);
    1147                 goto startParseExpression;
    1148             }
    1149             case DoParseArrayEndExpression: {
    1150                 JSArray* array = asArray(objectStack.last());
    1151                 array->putDirectIndex(m_globalObject, array->length(), lastValue);
    1152                 RETURN_IF_EXCEPTION(scope, JSValue());
    1153 
    1154                 if (m_lexer.currentToken()->type == TokComma)
    1155                     goto doParseArrayStartExpression;
    1156 
    1157                 if (m_lexer.currentToken()->type != TokRBracket) {
    1158                     m_parseErrorMessage = "Expected ']'"_s;
    1159                     return JSValue();
    1160                 }
    1161                
    1162                 m_lexer.next();
    1163                 lastValue = objectStack.takeLast();
    1164                 break;
    1165             }
    1166             startParseObject:
    1167             case StartParseObject: {
    1168                 JSObject* object = constructEmptyObject(m_globalObject);
    1169                 objectStack.appendWithCrashOnOverflow(object);
    1170 
    1171                 TokenType type = m_lexer.next();
    1172                 if (type == TokString || (m_mode != StrictJSON && type == TokIdentifier)) {
    1173                     typename Lexer::LiteralParserTokenPtr identifierToken = m_lexer.currentToken();
    1174                     if (identifierToken->stringIs8Bit)
    1175                         identifierStack.append(makeIdentifier(identifierToken->stringToken8, identifierToken->stringLength));
    1176                     else
    1177                         identifierStack.append(makeIdentifier(identifierToken->stringToken16, identifierToken->stringLength));
    1178 
    1179                     // Check for colon
    1180                     if (m_lexer.next() != TokColon) {
    1181                         m_parseErrorMessage = "Expected ':' before value in object property definition"_s;
    1182                         return JSValue();
    1183                     }
    1184                    
    1185                     m_lexer.next();
    1186                     stateStack.append(DoParseObjectEndExpression);
    1187                     goto startParseExpression;
    1188                 }
    1189                 if (type != TokRBrace)  {
    1190                     m_parseErrorMessage = "Expected '}'"_s;
    1191                     return JSValue();
     1240        startParseArray:
     1241        case StartParseArray: {
     1242            JSArray* array = constructEmptyArray(m_globalObject, nullptr);
     1243            RETURN_IF_EXCEPTION(scope, { });
     1244            objectStack.appendWithCrashOnOverflow(array);
     1245        }
     1246        doParseArrayStartExpression:
     1247        FALLTHROUGH;
     1248        case DoParseArrayStartExpression: {
     1249            TokenType lastToken = m_lexer.currentToken()->type;
     1250            if (m_lexer.next() == TokRBracket) {
     1251                if (UNLIKELY(lastToken == TokComma)) {
     1252                    m_parseErrorMessage = "Unexpected comma at the end of array expression"_s;
     1253                    return { };
    11921254                }
    11931255                m_lexer.next();
     
    11951257                break;
    11961258            }
    1197             doParseObjectStartExpression:
    1198             case DoParseObjectStartExpression: {
    1199                 TokenType type = m_lexer.next();
    1200                 if (type != TokString && (m_mode == StrictJSON || type != TokIdentifier)) {
    1201                     m_parseErrorMessage = "Property name must be a string literal"_s;
    1202                     return JSValue();
     1259
     1260            stateStack.append(DoParseArrayEndExpression);
     1261            goto startParseExpression;
     1262        }
     1263        case DoParseArrayEndExpression: {
     1264            JSArray* array = asArray(objectStack.last());
     1265            array->putDirectIndex(m_globalObject, array->length(), lastValue);
     1266            RETURN_IF_EXCEPTION(scope, { });
     1267
     1268            if (m_lexer.currentToken()->type == TokComma)
     1269                goto doParseArrayStartExpression;
     1270
     1271            if (UNLIKELY(m_lexer.currentToken()->type != TokRBracket)) {
     1272                setErrorMessageForToken(TokRBracket);
     1273                return { };
     1274            }
     1275           
     1276            m_lexer.next();
     1277            lastValue = objectStack.takeLast();
     1278            break;
     1279        }
     1280        startParseObject:
     1281        case StartParseObject: {
     1282            JSObject* object = constructEmptyObject(m_globalObject);
     1283
     1284            TokenType type = m_lexer.next();
     1285            if (type == TokString || (m_mode != StrictJSON && type == TokIdentifier)) {
     1286                while (true) {
     1287                    Identifier ident = makeIdentifier(m_lexer.currentToken());
     1288
     1289                    if (UNLIKELY(m_lexer.next() != TokColon)) {
     1290                        setErrorMessageForToken(TokColon);
     1291                        return { };
     1292                    }
     1293
     1294                    TokenType nextType = m_lexer.next();
     1295                    if (nextType == TokLBrace || nextType == TokLBracket) {
     1296                        objectStack.appendWithCrashOnOverflow(object);
     1297                        identifierStack.append(WTFMove(ident));
     1298                        stateStack.append(DoParseObjectEndExpression);
     1299                        if (nextType == TokLBrace)
     1300                            goto startParseObject;
     1301                        ASSERT(nextType == TokLBracket);
     1302                        goto startParseArray;
     1303                    }
     1304
     1305                    // Leaf object construction fast path.
     1306                    JSValue primitive = parsePrimitiveValue(vm);
     1307                    if (UNLIKELY(!primitive))
     1308                        return { };
     1309
     1310                    if (m_mode != StrictJSON && ident == vm.propertyNames->underscoreProto) {
     1311                        if (UNLIKELY(!visitedUnderscoreProto.add(object).isNewEntry)) {
     1312                            m_parseErrorMessage = "Attempted to redefine __proto__ property"_s;
     1313                            return { };
     1314                        }
     1315                        PutPropertySlot slot(object, m_nullOrCodeBlock ? m_nullOrCodeBlock->ownerExecutable()->isInStrictContext() : false);
     1316                        JSValue(object).put(m_globalObject, ident, primitive, slot);
     1317                        RETURN_IF_EXCEPTION(scope, { });
     1318                    } else {
     1319                        if (std::optional<uint32_t> index = parseIndex(ident)) {
     1320                            object->putDirectIndex(m_globalObject, index.value(), primitive);
     1321                            RETURN_IF_EXCEPTION(scope, { });
     1322                        } else
     1323                            object->putDirect(vm, ident, primitive);
     1324                    }
     1325
     1326                    if (m_lexer.currentToken()->type != TokComma)
     1327                        break;
     1328
     1329                    nextType = m_lexer.next();
     1330                    if (UNLIKELY(nextType != TokString && (m_mode == StrictJSON || nextType != TokIdentifier))) {
     1331                        m_parseErrorMessage = "Property name must be a string literal"_s;
     1332                        return { };
     1333                    }
    12031334                }
    1204                 typename Lexer::LiteralParserTokenPtr identifierToken = m_lexer.currentToken();
    1205                 if (identifierToken->stringIs8Bit)
    1206                     identifierStack.append(makeIdentifier(identifierToken->stringToken8, identifierToken->stringLength));
    1207                 else
    1208                     identifierStack.append(makeIdentifier(identifierToken->stringToken16, identifierToken->stringLength));
    1209 
    1210                 // Check for colon
    1211                 if (m_lexer.next() != TokColon) {
    1212                     m_parseErrorMessage = "Expected ':'"_s;
    1213                     return JSValue();
    1214                 }
    1215 
    1216                 m_lexer.next();
    1217                 stateStack.append(DoParseObjectEndExpression);
    1218                 goto startParseExpression;
    1219             }
    1220             case DoParseObjectEndExpression:
    1221             {
    1222                 JSObject* object = asObject(objectStack.last());
    1223                 Identifier ident = identifierStack.takeLast();
    1224                 if (m_mode != StrictJSON && ident == vm.propertyNames->underscoreProto) {
    1225                     if (!visitedUnderscoreProto.add(object).isNewEntry) {
    1226                         m_parseErrorMessage = "Attempted to redefine __proto__ property"_s;
    1227                         return JSValue();
    1228                     }
    1229                     PutPropertySlot slot(object, m_nullOrCodeBlock ? m_nullOrCodeBlock->ownerExecutable()->isInStrictContext() : false);
    1230                     objectStack.last().put(m_globalObject, ident, lastValue, slot);
    1231                 } else {
    1232                     if (std::optional<uint32_t> index = parseIndex(ident))
    1233                         object->putDirectIndex(m_globalObject, index.value(), lastValue);
    1234                     else
    1235                         object->putDirect(vm, ident, lastValue);
    1236                 }
    1237                 RETURN_IF_EXCEPTION(scope, JSValue());
    1238                 if (m_lexer.currentToken()->type == TokComma)
    1239                     goto doParseObjectStartExpression;
    1240                 if (m_lexer.currentToken()->type != TokRBrace) {
    1241                     m_parseErrorMessage = "Expected '}'"_s;
    1242                     return JSValue();
     1335
     1336                if (UNLIKELY(m_lexer.currentToken()->type != TokRBrace)) {
     1337                    setErrorMessageForToken(TokRBrace);
     1338                    return { };
    12431339                }
    12441340                m_lexer.next();
    1245                 lastValue = objectStack.takeLast();
     1341                lastValue = object;
    12461342                break;
    12471343            }
    1248             startParseExpression:
    1249             case StartParseExpression: {
    1250                 switch (m_lexer.currentToken()->type) {
    1251                     case TokLBracket:
    1252                         goto startParseArray;
    1253                     case TokLBrace:
    1254                         goto startParseObject;
    1255                     case TokString: {
    1256                         typename Lexer::LiteralParserTokenPtr stringToken = m_lexer.currentToken();
    1257                         if (stringToken->stringIs8Bit)
    1258                             lastValue = jsString(vm, makeIdentifier(stringToken->stringToken8, stringToken->stringLength).string());
    1259                         else
    1260                             lastValue = jsString(vm, makeIdentifier(stringToken->stringToken16, stringToken->stringLength).string());
    1261                         m_lexer.next();
    1262                         break;
    1263                     }
    1264                     case TokNumber: {
    1265                         typename Lexer::LiteralParserTokenPtr numberToken = m_lexer.currentToken();
    1266                         lastValue = jsNumber(numberToken->numberToken);
    1267                         m_lexer.next();
    1268                         break;
    1269                     }
    1270                     case TokNull:
    1271                         m_lexer.next();
    1272                         lastValue = jsNull();
    1273                         break;
    1274 
    1275                     case TokTrue:
    1276                         m_lexer.next();
    1277                         lastValue = jsBoolean(true);
    1278                         break;
    1279 
    1280                     case TokFalse:
    1281                         m_lexer.next();
    1282                         lastValue = jsBoolean(false);
    1283                         break;
    1284                     case TokRBracket:
    1285                         m_parseErrorMessage = "Unexpected token ']'"_s;
    1286                         return JSValue();
    1287                     case TokRBrace:
    1288                         m_parseErrorMessage = "Unexpected token '}'"_s;
    1289                         return JSValue();
    1290                     case TokIdentifier: {
    1291                         auto token = m_lexer.currentToken();
    1292 
    1293                         auto tryMakeErrorString = [&] (unsigned length) -> String {
    1294                             bool addEllipsis = length != token->stringLength;
    1295                             if (token->stringIs8Bit)
    1296                                 return tryMakeString("Unexpected identifier \"", StringView { token->stringToken8, length }, addEllipsis ? "..." : "", '"');
    1297                             return tryMakeString("Unexpected identifier \"", StringView { token->stringToken16, length }, addEllipsis ? "..." : "", '"');
    1298                         };
    1299 
    1300                         constexpr unsigned maxLength = 200;
    1301 
    1302                         String errorString = tryMakeErrorString(std::min(token->stringLength, maxLength));
    1303                         if (!errorString) {
    1304                             constexpr unsigned shortLength = 10;
    1305                             if (token->stringLength > shortLength)
    1306                                 errorString = tryMakeErrorString(shortLength);
    1307                             if (!errorString)
    1308                                 errorString = "Unexpected identifier";
    1309                         }
    1310 
    1311                         m_parseErrorMessage = errorString;
    1312                         return JSValue();
    1313                     }
    1314                     case TokColon:
    1315                         m_parseErrorMessage = "Unexpected token ':'"_s;
    1316                         return JSValue();
    1317                     case TokLParen:
    1318                         m_parseErrorMessage = "Unexpected token '('"_s;
    1319                         return JSValue();
    1320                     case TokRParen:
    1321                         m_parseErrorMessage = "Unexpected token ')'"_s;
    1322                         return JSValue();
    1323                     case TokComma:
    1324                         m_parseErrorMessage = "Unexpected token ','"_s;
    1325                         return JSValue();
    1326                     case TokDot:
    1327                         m_parseErrorMessage = "Unexpected token '.'"_s;
    1328                         return JSValue();
    1329                     case TokAssign:
    1330                         m_parseErrorMessage = "Unexpected token '='"_s;
    1331                         return JSValue();
    1332                     case TokSemi:
    1333                         m_parseErrorMessage = "Unexpected token ';'"_s;
    1334                         return JSValue();
    1335                     case TokEnd:
    1336                         m_parseErrorMessage = "Unexpected EOF"_s;
    1337                         return JSValue();
    1338                     case TokError:
    1339                     default:
    1340                         // Error
    1341                         m_parseErrorMessage = "Could not parse value expression"_s;
    1342                         return JSValue();
     1344
     1345            if (UNLIKELY(type != TokRBrace)) {
     1346                setErrorMessageForToken(TokRBrace);
     1347                return { };
     1348            }
     1349
     1350            m_lexer.next();
     1351            lastValue = object;
     1352            break;
     1353        }
     1354        doParseObjectStartExpression:
     1355        case DoParseObjectStartExpression: {
     1356            TokenType type = m_lexer.next();
     1357            if (UNLIKELY(type != TokString && (m_mode == StrictJSON || type != TokIdentifier))) {
     1358                m_parseErrorMessage = "Property name must be a string literal"_s;
     1359                return { };
     1360            }
     1361            identifierStack.append(makeIdentifier(m_lexer.currentToken()));
     1362
     1363            // Check for colon
     1364            if (UNLIKELY(m_lexer.next() != TokColon)) {
     1365                setErrorMessageForToken(TokColon);
     1366                return { };
     1367            }
     1368
     1369            m_lexer.next();
     1370            stateStack.append(DoParseObjectEndExpression);
     1371            goto startParseExpression;
     1372        }
     1373        case DoParseObjectEndExpression:
     1374        {
     1375            JSObject* object = asObject(objectStack.last());
     1376            Identifier ident = identifierStack.takeLast();
     1377            if (m_mode != StrictJSON && ident == vm.propertyNames->underscoreProto) {
     1378                if (UNLIKELY(!visitedUnderscoreProto.add(object).isNewEntry)) {
     1379                    m_parseErrorMessage = "Attempted to redefine __proto__ property"_s;
     1380                    return { };
    13431381                }
     1382                PutPropertySlot slot(object, m_nullOrCodeBlock ? m_nullOrCodeBlock->ownerExecutable()->isInStrictContext() : false);
     1383                JSValue(object).put(m_globalObject, ident, lastValue, slot);
     1384                RETURN_IF_EXCEPTION(scope, { });
     1385            } else {
     1386                if (std::optional<uint32_t> index = parseIndex(ident)) {
     1387                    object->putDirectIndex(m_globalObject, index.value(), lastValue);
     1388                    RETURN_IF_EXCEPTION(scope, { });
     1389                } else
     1390                    object->putDirect(vm, ident, lastValue);
     1391            }
     1392            if (m_lexer.currentToken()->type == TokComma)
     1393                goto doParseObjectStartExpression;
     1394            if (UNLIKELY(m_lexer.currentToken()->type != TokRBrace)) {
     1395                setErrorMessageForToken(TokRBrace);
     1396                return { };
     1397            }
     1398            m_lexer.next();
     1399            lastValue = objectStack.takeLast();
     1400            break;
     1401        }
     1402        startParseExpression:
     1403        case StartParseExpression: {
     1404            TokenType type = m_lexer.currentToken()->type;
     1405            if (type == TokLBracket)
     1406                goto startParseArray;
     1407            if (type == TokLBrace)
     1408                goto startParseObject;
     1409            lastValue = parsePrimitiveValue(vm);
     1410            if (UNLIKELY(!lastValue))
     1411                return { };
     1412            break;
     1413        }
     1414        case StartParseStatement: {
     1415            switch (m_lexer.currentToken()->type) {
     1416            case TokLBracket:
     1417            case TokNumber:
     1418            case TokString: {
     1419                lastValue = parsePrimitiveValue(vm);
     1420                if (UNLIKELY(!lastValue))
     1421                    return { };
    13441422                break;
    13451423            }
    1346             case StartParseStatement: {
    1347                 switch (m_lexer.currentToken()->type) {
    1348                     case TokLBracket:
    1349                     case TokNumber:
    1350                     case TokString:
    1351                         goto startParseExpression;
    1352 
    1353                     case TokLParen: {
    1354                         m_lexer.next();
    1355                         stateStack.append(StartParseStatementEndStatement);
    1356                         goto startParseExpression;
    1357                     }
    1358                     case TokRBracket:
    1359                         m_parseErrorMessage = "Unexpected token ']'"_s;
    1360                         return JSValue();
    1361                     case TokLBrace:
    1362                         m_parseErrorMessage = "Unexpected token '{'"_s;
    1363                         return JSValue();
    1364                     case TokRBrace:
    1365                         m_parseErrorMessage = "Unexpected token '}'"_s;
    1366                         return JSValue();
    1367                     case TokIdentifier:
    1368                         m_parseErrorMessage = "Unexpected identifier"_s;
    1369                         return JSValue();
    1370                     case TokColon:
    1371                         m_parseErrorMessage = "Unexpected token ':'"_s;
    1372                         return JSValue();
    1373                     case TokRParen:
    1374                         m_parseErrorMessage = "Unexpected token ')'"_s;
    1375                         return JSValue();
    1376                     case TokComma:
    1377                         m_parseErrorMessage = "Unexpected token ','"_s;
    1378                         return JSValue();
    1379                     case TokTrue:
    1380                         m_parseErrorMessage = "Unexpected token 'true'"_s;
    1381                         return JSValue();
    1382                     case TokFalse:
    1383                         m_parseErrorMessage = "Unexpected token 'false'"_s;
    1384                         return JSValue();
    1385                     case TokNull:
    1386                         m_parseErrorMessage = "Unexpected token 'null'"_s;
    1387                         return JSValue();
    1388                     case TokEnd:
    1389                         m_parseErrorMessage = "Unexpected EOF"_s;
    1390                         return JSValue();
    1391                     case TokDot:
    1392                         m_parseErrorMessage = "Unexpected token '.'"_s;
    1393                         return JSValue();
    1394                     case TokAssign:
    1395                         m_parseErrorMessage = "Unexpected token '='"_s;
    1396                         return JSValue();
    1397                     case TokSemi:
    1398                         m_parseErrorMessage = "Unexpected token ';'"_s;
    1399                         return JSValue();
    1400                     case TokError:
    1401                     default:
    1402                         m_parseErrorMessage = "Could not parse statement"_s;
    1403                         return JSValue();
    1404                 }
    1405             }
    1406             case StartParseStatementEndStatement: {
    1407                 ASSERT(stateStack.isEmpty());
    1408                 if (m_lexer.currentToken()->type != TokRParen)
    1409                     return JSValue();
    1410                 if (m_lexer.next() == TokEnd)
    1411                     return lastValue;
    1412                 m_parseErrorMessage = "Unexpected content at end of JSON literal"_s;
    1413                 return JSValue();
    1414             }
     1424
     1425            case TokLParen: {
     1426                m_lexer.next();
     1427                stateStack.append(StartParseStatementEndStatement);
     1428                goto startParseExpression;
     1429            }
     1430            case TokRBracket:
     1431                m_parseErrorMessage = "Unexpected token ']'"_s;
     1432                return { };
     1433            case TokLBrace:
     1434                m_parseErrorMessage = "Unexpected token '{'"_s;
     1435                return { };
     1436            case TokRBrace:
     1437                m_parseErrorMessage = "Unexpected token '}'"_s;
     1438                return { };
     1439            case TokIdentifier:
     1440                m_parseErrorMessage = "Unexpected identifier"_s;
     1441                return { };
     1442            case TokColon:
     1443                m_parseErrorMessage = "Unexpected token ':'"_s;
     1444                return { };
     1445            case TokRParen:
     1446                m_parseErrorMessage = "Unexpected token ')'"_s;
     1447                return { };
     1448            case TokComma:
     1449                m_parseErrorMessage = "Unexpected token ','"_s;
     1450                return { };
     1451            case TokTrue:
     1452                m_parseErrorMessage = "Unexpected token 'true'"_s;
     1453                return { };
     1454            case TokFalse:
     1455                m_parseErrorMessage = "Unexpected token 'false'"_s;
     1456                return { };
     1457            case TokNull:
     1458                m_parseErrorMessage = "Unexpected token 'null'"_s;
     1459                return { };
     1460            case TokEnd:
     1461                m_parseErrorMessage = "Unexpected EOF"_s;
     1462                return { };
     1463            case TokDot:
     1464                m_parseErrorMessage = "Unexpected token '.'"_s;
     1465                return { };
     1466            case TokAssign:
     1467                m_parseErrorMessage = "Unexpected token '='"_s;
     1468                return { };
     1469            case TokSemi:
     1470                m_parseErrorMessage = "Unexpected token ';'"_s;
     1471                return { };
     1472            case TokError:
    14151473            default:
    1416                 RELEASE_ASSERT_NOT_REACHED();
     1474                m_parseErrorMessage = "Could not parse statement"_s;
     1475                return { };
     1476            }
     1477            break;
     1478        }
     1479        case StartParseStatementEndStatement: {
     1480            ASSERT(stateStack.isEmpty());
     1481            if (m_lexer.currentToken()->type != TokRParen)
     1482                return { };
     1483            if (m_lexer.next() == TokEnd)
     1484                return lastValue;
     1485            m_parseErrorMessage = "Unexpected content at end of JSON literal"_s;
     1486            return { };
     1487        }
     1488        default:
     1489            RELEASE_ASSERT_NOT_REACHED();
    14171490        }
    14181491        if (stateStack.isEmpty())
  • trunk/Source/JavaScriptCore/runtime/LiteralParser.h

    r278971 r282468  
    5353    TokString, TokIdentifier, TokNumber, TokColon,
    5454    TokLParen, TokRParen, TokComma, TokTrue, TokFalse,
    55     TokNull, TokEnd, TokDot, TokAssign, TokSemi, TokError };
     55    TokNull, TokEnd, TokDot, TokAssign, TokSemi, TokError, TokErrorSpace };
    5656
    5757struct JSONPPathEntry {
     
    139139       
    140140#if !ASSERT_ENABLED
    141         typedef const LiteralParserToken<CharType>* LiteralParserTokenPtr;
     141        using LiteralParserTokenPtr = const LiteralParserToken<CharType>*;
    142142
    143143        LiteralParserTokenPtr currentToken()
     
    196196    JSValue parse(ParserState);
    197197
     198    JSValue parsePrimitiveValue(VM&);
     199
     200    ALWAYS_INLINE Identifier makeIdentifier(typename Lexer::LiteralParserTokenPtr);
    198201    template<typename LiteralCharType>
    199202    ALWAYS_INLINE Identifier makeIdentifier(const LiteralCharType* characters, size_t length);
     203
     204    void setErrorMessageForToken(TokenType);
    200205
    201206    JSGlobalObject* m_globalObject;
Note: See TracChangeset for help on using the changeset viewer.