8015346: JSON parsing issues with escaped strings, octal, decimal numbers
authorsundar
Thu, 06 Jun 2013 21:41:20 +0530
changeset 18317 2f5434c9c9fd
parent 17985 dad5bfcd7f83
child 18318 5e4244619d79
8015346: JSON parsing issues with escaped strings, octal, decimal numbers Reviewed-by: hannesw, jlaskey
nashorn/src/jdk/nashorn/internal/ir/BlockLexicalContext.java
nashorn/src/jdk/nashorn/internal/parser/JSONParser.java
nashorn/src/jdk/nashorn/internal/parser/Lexer.java
nashorn/src/jdk/nashorn/internal/runtime/JSONFunctions.java
nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties
nashorn/test/script/basic/JDK-8015346.js
--- a/nashorn/src/jdk/nashorn/internal/ir/BlockLexicalContext.java	Wed Jun 05 12:41:09 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/ir/BlockLexicalContext.java	Thu Jun 06 21:41:20 2013 +0530
@@ -63,6 +63,7 @@
         return sstack.pop();
     }
 
+    @SuppressWarnings("unchecked")
     @Override
     public <T extends LexicalContextNode> T pop(final T node) {
         T expected = node;
--- a/nashorn/src/jdk/nashorn/internal/parser/JSONParser.java	Wed Jun 05 12:41:09 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/parser/JSONParser.java	Thu Jun 06 21:41:20 2013 +0530
@@ -54,10 +54,9 @@
      * Constructor
      * @param source  the source
      * @param errors  the error manager
-     * @param strict  are we in strict mode
      */
-    public JSONParser(final Source source, final ErrorManager errors, final boolean strict) {
-        super(source, errors, strict);
+    public JSONParser(final Source source, final ErrorManager errors) {
+        super(source, errors, false);
     }
 
     /**
@@ -135,6 +134,7 @@
                 return ch == '\"';
             }
 
+            // ECMA 15.12.1.1 The JSON Lexical Grammar - JSONWhiteSpace
             @Override
             protected boolean isWhitespace(final char ch) {
                 return Lexer.isJsonWhitespace(ch);
@@ -144,6 +144,99 @@
             protected boolean isEOL(final char ch) {
                 return Lexer.isJsonEOL(ch);
             }
+
+            // ECMA 15.12.1.1 The JSON Lexical Grammar - JSONNumber
+            @Override
+            protected void scanNumber() {
+                // Record beginning of number.
+                final int start = position;
+                // Assume value is a decimal.
+                TokenType type = TokenType.DECIMAL;
+
+                // floating point can't start with a "." with no leading digit before
+                if (ch0 == '.') {
+                    error(Lexer.message("json.invalid.number"), STRING, position, limit);
+                }
+
+                // First digit of number.
+                int digit = convertDigit(ch0, 10);
+
+                // skip first digit
+                skip(1);
+
+                if (digit != 0) {
+                    // Skip over remaining digits.
+                    while (convertDigit(ch0, 10) != -1) {
+                        skip(1);
+                    }
+                }
+
+                if (ch0 == '.' || ch0 == 'E' || ch0 == 'e') {
+                    // Must be a double.
+                    if (ch0 == '.') {
+                        // Skip period.
+                        skip(1);
+
+                        boolean mantissa = false;
+                        // Skip mantissa.
+                        while (convertDigit(ch0, 10) != -1) {
+                            mantissa = true;
+                            skip(1);
+                        }
+
+                        if (! mantissa) {
+                            // no digit after "."
+                            error(Lexer.message("json.invalid.number"), STRING, position, limit);
+                        }
+                    }
+
+                    // Detect exponent.
+                    if (ch0 == 'E' || ch0 == 'e') {
+                        // Skip E.
+                        skip(1);
+                        // Detect and skip exponent sign.
+                        if (ch0 == '+' || ch0 == '-') {
+                            skip(1);
+                        }
+                        boolean exponent = false;
+                        // Skip exponent.
+                        while (convertDigit(ch0, 10) != -1) {
+                            exponent = true;
+                            skip(1);
+                        }
+
+                        if (! exponent) {
+                            // no digit after "E"
+                            error(Lexer.message("json.invalid.number"), STRING, position, limit);
+                        }
+                    }
+
+                    type = TokenType.FLOATING;
+                }
+
+                // Add number token.
+                add(type, start);
+            }
+
+            // ECMA 15.12.1.1 The JSON Lexical Grammar - JSONEscapeCharacter
+            @Override
+            protected boolean isEscapeCharacter(final char ch) {
+                switch (ch) {
+                    case '"':
+                    case '/':
+                    case '\\':
+                    case 'b':
+                    case 'f':
+                    case 'n':
+                    case 'r':
+                    case 't':
+                    // could be unicode escape
+                    case 'u':
+                        return true;
+                    default:
+                        return false;
+                }
+            }
         };
 
         k = -1;
--- a/nashorn/src/jdk/nashorn/internal/parser/Lexer.java	Wed Jun 05 12:41:09 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/parser/Lexer.java	Thu Jun 06 21:41:20 2013 +0530
@@ -648,7 +648,7 @@
      *
      * @return The converted digit or -1 if invalid.
      */
-    private static int convertDigit(final char ch, final int base) {
+    protected static int convertDigit(final char ch, final int base) {
         int digit;
 
         if ('0' <= ch && ch <= '9') {
@@ -908,7 +908,7 @@
     /**
      * Scan over a string literal.
      */
-    private void scanString(final boolean add) {
+    protected void scanString(final boolean add) {
         // Type of string.
         TokenType type = STRING;
         // Record starting quote.
@@ -925,6 +925,9 @@
             if (ch0 == '\\') {
                 type = ESCSTRING;
                 skip(1);
+                if (! isEscapeCharacter(ch0)) {
+                    error(Lexer.message("invalid.escape.char"), STRING, position, limit);
+                }
                 if (isEOL(ch0)) {
                     // Multiline string literal
                     skipEOL(false);
@@ -979,6 +982,16 @@
     }
 
     /**
+     * Is the given character a valid escape char after "\" ?
+     *
+     * @param ch character to be checked
+     * @return if the given character is valid after "\"
+     */
+    protected boolean isEscapeCharacter(final char ch) {
+        return true;
+    }
+
+    /**
      * Convert string to number.
      *
      * @param valueString  String to convert.
@@ -1024,7 +1037,7 @@
     /**
      * Scan a number.
      */
-    private void scanNumber() {
+    protected void scanNumber() {
         // Record beginning of number.
         final int start = position;
         // Assume value is a decimal.
@@ -1583,7 +1596,7 @@
         return null;
     }
 
-    private static String message(final String msgId, final String... args) {
+    protected static String message(final String msgId, final String... args) {
         return ECMAErrors.getMessage("lexer.error." + msgId, args);
     }
 
--- a/nashorn/src/jdk/nashorn/internal/runtime/JSONFunctions.java	Wed Jun 05 12:41:09 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/runtime/JSONFunctions.java	Thu Jun 06 21:41:20 2013 +0530
@@ -66,13 +66,9 @@
      */
     public static Object parse(final Object text, final Object reviver) {
         final String     str     = JSType.toString(text);
-        final Context    context = Context.getContextTrusted();
         final JSONParser parser  = new JSONParser(
                 new Source("<json>", str),
-                new Context.ThrowErrorManager(),
-                (context != null) ?
-                    context.getEnv()._strict :
-                    false);
+                new Context.ThrowErrorManager());
 
         Node node;
 
--- a/nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties	Wed Jun 05 12:41:09 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties	Thu Jun 06 21:41:20 2013 +0530
@@ -28,6 +28,8 @@
 lexer.error.invalid.hex=Invalid hex digit
 lexer.error.invalid.octal=Invalid octal digit
 lexer.error.strict.no.octal=cannot use octal escapes in strict mode
+lexer.error.json.invalid.number=Invalid JSON number format
+lexer.error.invalid.escape.char=Invalid escape character
 lexer.error.illegal.identifier.character=Illegal character in identifier
 
 parser.error.illegal.continue.stmt=Illegal continue statement
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8015346.js	Thu Jun 06 21:41:20 2013 +0530
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2010, 2013, 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.
+ * 
+ * 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.
+ */
+
+/**
+ * JDK-8015346: JSON parsing issues with escaped strings, octal, decimal numbers *
+ * @test
+ * @run
+ */
+
+function checkJSON(str) {
+    try {
+        JSON.parse(str);
+        fail("should have thrown SyntaxError for JSON.parse on " + str);
+    } catch (e) {
+        if (! (e instanceof SyntaxError)) {
+            fail("Expected SyntaxError, but got " + e);
+        }
+    }
+}
+
+// invalid escape in a string
+checkJSON('"\\a"')
+
+// invalid floating point number patterns
+checkJSON("1.")
+checkJSON(".8")
+checkJSON("2.3e+")
+checkJSON("0.3E+")
+
+// octal, hexadecimal not allowed
+checkJSON("08")
+checkJSON("06")
+checkJSON('0x3')