--- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/JSONParser.java 2020-04-15 18:49:19.000000000 +0530 +++ /dev/null 2020-04-15 18:49:19.000000000 +0530 @@ -1,548 +0,0 @@ -/* - * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.nashorn.internal.parser; - -import java.util.ArrayList; -import java.util.List; -import jdk.nashorn.internal.codegen.ObjectClassGenerator; -import jdk.nashorn.internal.objects.Global; -import jdk.nashorn.internal.runtime.ECMAErrors; -import jdk.nashorn.internal.runtime.ErrorManager; -import jdk.nashorn.internal.runtime.JSErrorType; -import jdk.nashorn.internal.runtime.JSType; -import jdk.nashorn.internal.runtime.ParserException; -import jdk.nashorn.internal.runtime.Property; -import jdk.nashorn.internal.runtime.PropertyMap; -import jdk.nashorn.internal.runtime.ScriptObject; -import jdk.nashorn.internal.runtime.Source; -import jdk.nashorn.internal.runtime.SpillProperty; -import jdk.nashorn.internal.runtime.arrays.ArrayData; -import jdk.nashorn.internal.runtime.arrays.ArrayIndex; -import jdk.nashorn.internal.scripts.JD; -import jdk.nashorn.internal.scripts.JO; - -import static jdk.nashorn.internal.parser.TokenType.STRING; - -/** - * Parses JSON text and returns the corresponding IR node. This is derived from - * the objectLiteral production of the main parser. - * - * See: 15.12.1.2 The JSON Syntactic Grammar - */ -public class JSONParser { - - final private String source; - final private Global global; - final private boolean dualFields; - final int length; - int pos = 0; - - private static final int EOF = -1; - - private static final String TRUE = "true"; - private static final String FALSE = "false"; - private static final String NULL = "null"; - - private static final int STATE_EMPTY = 0; - private static final int STATE_ELEMENT_PARSED = 1; - private static final int STATE_COMMA_PARSED = 2; - - /** - * Constructor. - * - * @param source the source - * @param global the global object - * @param dualFields whether the parser should regard dual field representation - */ - public JSONParser(final String source, final Global global, final boolean dualFields) { - this.source = source; - this.global = global; - this.length = source.length(); - this.dualFields = dualFields; - } - - /** - * Implementation of the Quote(value) operation as defined in the ECMAscript - * spec. It wraps a String value in double quotes and escapes characters - * within. - * - * @param value string to quote - * - * @return quoted and escaped string - */ - public static String quote(final String value) { - - final StringBuilder product = new StringBuilder(); - - product.append("\""); - - for (final char ch : value.toCharArray()) { - // TODO: should use a table? - switch (ch) { - case '\\': - product.append("\\\\"); - break; - case '"': - product.append("\\\""); - break; - case '\b': - product.append("\\b"); - break; - case '\f': - product.append("\\f"); - break; - case '\n': - product.append("\\n"); - break; - case '\r': - product.append("\\r"); - break; - case '\t': - product.append("\\t"); - break; - default: - if (ch < ' ') { - product.append(Lexer.unicodeEscape(ch)); - break; - } - - product.append(ch); - break; - } - } - - product.append("\""); - - return product.toString(); - } - - /** - * Public parse method. Parse a string into a JSON object. - * - * @return the parsed JSON Object - */ - public Object parse() { - final Object value = parseLiteral(); - skipWhiteSpace(); - if (pos < length) { - throw expectedError(pos, "eof", toString(peek())); - } - return value; - } - - private Object parseLiteral() { - skipWhiteSpace(); - - final int c = peek(); - if (c == EOF) { - throw expectedError(pos, "json literal", "eof"); - } - switch (c) { - case '{': - return parseObject(); - case '[': - return parseArray(); - case '"': - return parseString(); - case 'f': - return parseKeyword(FALSE, Boolean.FALSE); - case 't': - return parseKeyword(TRUE, Boolean.TRUE); - case 'n': - return parseKeyword(NULL, null); - default: - if (isDigit(c) || c == '-') { - return parseNumber(); - } else if (c == '.') { - throw numberError(pos); - } else { - throw expectedError(pos, "json literal", toString(c)); - } - } - } - - private Object parseObject() { - PropertyMap propertyMap = dualFields ? JD.getInitialMap() : JO.getInitialMap(); - ArrayData arrayData = ArrayData.EMPTY_ARRAY; - final ArrayList values = new ArrayList<>(); - int state = STATE_EMPTY; - - assert peek() == '{'; - pos++; - - while (pos < length) { - skipWhiteSpace(); - final int c = peek(); - - switch (c) { - case '"': - if (state == STATE_ELEMENT_PARSED) { - throw expectedError(pos - 1, ", or }", toString(c)); - } - final String id = parseString(); - expectColon(); - final Object value = parseLiteral(); - final int index = ArrayIndex.getArrayIndex(id); - if (ArrayIndex.isValidArrayIndex(index)) { - arrayData = addArrayElement(arrayData, index, value); - } else { - propertyMap = addObjectProperty(propertyMap, values, id, value); - } - state = STATE_ELEMENT_PARSED; - break; - case ',': - if (state != STATE_ELEMENT_PARSED) { - throw error(AbstractParser.message("trailing.comma.in.json"), pos); - } - state = STATE_COMMA_PARSED; - pos++; - break; - case '}': - if (state == STATE_COMMA_PARSED) { - throw error(AbstractParser.message("trailing.comma.in.json"), pos); - } - pos++; - return createObject(propertyMap, values, arrayData); - default: - throw expectedError(pos, ", or }", toString(c)); - } - } - throw expectedError(pos, ", or }", "eof"); - } - - private static ArrayData addArrayElement(final ArrayData arrayData, final int index, final Object value) { - final long oldLength = arrayData.length(); - final long longIndex = ArrayIndex.toLongIndex(index); - ArrayData newArrayData = arrayData; - if (longIndex >= oldLength) { - newArrayData = newArrayData.ensure(longIndex); - if (longIndex > oldLength) { - newArrayData = newArrayData.delete(oldLength, longIndex - 1); - } - } - return newArrayData.set(index, value, false); - } - - private PropertyMap addObjectProperty(final PropertyMap propertyMap, final List values, - final String id, final Object value) { - final Property oldProperty = propertyMap.findProperty(id); - final PropertyMap newMap; - final Class type; - final int flags; - if (dualFields) { - type = getType(value); - flags = Property.DUAL_FIELDS; - } else { - type = Object.class; - flags = 0; - } - - if (oldProperty != null) { - values.set(oldProperty.getSlot(), value); - newMap = propertyMap.replaceProperty(oldProperty, new SpillProperty(id, flags, oldProperty.getSlot(), type));; - } else { - values.add(value); - newMap = propertyMap.addProperty(new SpillProperty(id, flags, propertyMap.size(), type)); - } - - return newMap; - } - - private Object createObject(final PropertyMap propertyMap, final List values, final ArrayData arrayData) { - final long[] primitiveSpill = dualFields ? new long[values.size()] : null; - final Object[] objectSpill = new Object[values.size()]; - - for (final Property property : propertyMap.getProperties()) { - if (!dualFields || property.getType() == Object.class) { - objectSpill[property.getSlot()] = values.get(property.getSlot()); - } else { - primitiveSpill[property.getSlot()] = ObjectClassGenerator.pack((Number) values.get(property.getSlot())); - } - } - - final ScriptObject object = dualFields ? - new JD(propertyMap, primitiveSpill, objectSpill) : new JO(propertyMap, null, objectSpill); - object.setInitialProto(global.getObjectPrototype()); - object.setArray(arrayData); - return object; - } - - private static Class getType(final Object value) { - if (value instanceof Integer) { - return int.class; - } else if (value instanceof Double) { - return double.class; - } else { - return Object.class; - } - } - - private void expectColon() { - skipWhiteSpace(); - final int n = next(); - if (n != ':') { - throw expectedError(pos - 1, ":", toString(n)); - } - } - - private Object parseArray() { - ArrayData arrayData = ArrayData.EMPTY_ARRAY; - int state = STATE_EMPTY; - - assert peek() == '['; - pos++; - - while (pos < length) { - skipWhiteSpace(); - final int c = peek(); - - switch (c) { - case ',': - if (state != STATE_ELEMENT_PARSED) { - throw error(AbstractParser.message("trailing.comma.in.json"), pos); - } - state = STATE_COMMA_PARSED; - pos++; - break; - case ']': - if (state == STATE_COMMA_PARSED) { - throw error(AbstractParser.message("trailing.comma.in.json"), pos); - } - pos++; - return global.wrapAsObject(arrayData); - default: - if (state == STATE_ELEMENT_PARSED) { - throw expectedError(pos, ", or ]", toString(c)); - } - final long index = arrayData.length(); - arrayData = arrayData.ensure(index).set((int) index, parseLiteral(), true); - state = STATE_ELEMENT_PARSED; - break; - } - } - - throw expectedError(pos, ", or ]", "eof"); - } - - private String parseString() { - // String buffer is only instantiated if string contains escape sequences. - int start = ++pos; - StringBuilder sb = null; - - while (pos < length) { - final int c = next(); - if (c <= 0x1f) { - // Characters < 0x1f are not allowed in JSON strings. - throw syntaxError(pos, "String contains control character"); - - } else if (c == '\\') { - if (sb == null) { - sb = new StringBuilder(pos - start + 16); - } - sb.append(source, start, pos - 1); - sb.append(parseEscapeSequence()); - start = pos; - - } else if (c == '"') { - if (sb != null) { - sb.append(source, start, pos - 1); - return sb.toString(); - } - return source.substring(start, pos - 1); - } - } - - throw error(Lexer.message("missing.close.quote"), pos, length); - } - - private char parseEscapeSequence() { - final int c = next(); - switch (c) { - case '"': - return '"'; - case '\\': - return '\\'; - case '/': - return '/'; - case 'b': - return '\b'; - case 'f': - return '\f'; - case 'n': - return '\n'; - case 'r': - return '\r'; - case 't': - return '\t'; - case 'u': - return parseUnicodeEscape(); - default: - throw error(Lexer.message("invalid.escape.char"), pos - 1, length); - } - } - - private char parseUnicodeEscape() { - return (char) (parseHexDigit() << 12 | parseHexDigit() << 8 | parseHexDigit() << 4 | parseHexDigit()); - } - - private int parseHexDigit() { - final int c = next(); - if (c >= '0' && c <= '9') { - return c - '0'; - } else if (c >= 'A' && c <= 'F') { - return c + 10 - 'A'; - } else if (c >= 'a' && c <= 'f') { - return c + 10 - 'a'; - } - throw error(Lexer.message("invalid.hex"), pos - 1, length); - } - - private boolean isDigit(final int c) { - return c >= '0' && c <= '9'; - } - - private void skipDigits() { - while (pos < length) { - final int c = peek(); - if (!isDigit(c)) { - break; - } - pos++; - } - } - - private Number parseNumber() { - final int start = pos; - int c = next(); - - if (c == '-') { - c = next(); - } - if (!isDigit(c)) { - throw numberError(start); - } - // no more digits allowed after 0 - if (c != '0') { - skipDigits(); - } - - // fraction - if (peek() == '.') { - pos++; - if (!isDigit(next())) { - throw numberError(pos - 1); - } - skipDigits(); - } - - // exponent - c = peek(); - if (c == 'e' || c == 'E') { - pos++; - c = next(); - if (c == '-' || c == '+') { - c = next(); - } - if (!isDigit(c)) { - throw numberError(pos - 1); - } - skipDigits(); - } - - final double d = Double.parseDouble(source.substring(start, pos)); - if (JSType.isRepresentableAsInt(d)) { - return (int) d; - } - return d; - } - - private Object parseKeyword(final String keyword, final Object value) { - if (!source.regionMatches(pos, keyword, 0, keyword.length())) { - throw expectedError(pos, "json literal", "ident"); - } - pos += keyword.length(); - return value; - } - - private int peek() { - if (pos >= length) { - return -1; - } - return source.charAt(pos); - } - - private int next() { - final int next = peek(); - pos++; - return next; - } - - private void skipWhiteSpace() { - while (pos < length) { - switch (peek()) { - case '\t': - case '\r': - case '\n': - case ' ': - pos++; - break; - default: - return; - } - } - } - - private static String toString(final int c) { - return c == EOF ? "eof" : String.valueOf((char) c); - } - - ParserException error(final String message, final int start, final int length) throws ParserException { - final long token = Token.toDesc(STRING, start, length); - final int pos = Token.descPosition(token); - final Source src = Source.sourceFor("", source); - final int lineNum = src.getLine(pos); - final int columnNum = src.getColumn(pos); - final String formatted = ErrorManager.format(message, src, lineNum, columnNum, token); - return new ParserException(JSErrorType.SYNTAX_ERROR, formatted, src, lineNum, columnNum, token); - } - - private ParserException error(final String message, final int start) { - return error(message, start, length); - } - - private ParserException numberError(final int start) { - return error(Lexer.message("json.invalid.number"), start); - } - - private ParserException expectedError(final int start, final String expected, final String found) { - return error(AbstractParser.message("expected", expected, found), start); - } - - private ParserException syntaxError(final int start, final String reason) { - final String message = ECMAErrors.getMessage("syntax.error.invalid.json", reason); - return error(message, start); - } -}