< prev index next >

src/java.base/share/classes/java/util/Properties.java

Print this page
rev 54989 : 8224240: Properties.load fails to throw IAE on malformed unicode in certain circumstances
Reviewed-by: smarks
rev 54990 : 8224202: Speed up Properties.load
Reviewed-by: rriggs

@@ -46,10 +46,11 @@
 import java.util.function.BiFunction;
 import java.util.function.Function;
 
 import jdk.internal.access.SharedSecrets;
 import jdk.internal.misc.Unsafe;
+import jdk.internal.util.ArraysSupport;
 import jdk.internal.util.xml.PropertiesDefaultHandler;
 
 /**
  * The {@code Properties} class represents a persistent set of
  * properties. The {@code Properties} can be saved to a stream

@@ -402,29 +403,27 @@
     public synchronized void load(InputStream inStream) throws IOException {
         Objects.requireNonNull(inStream, "inStream parameter is null");
         load0(new LineReader(inStream));
     }
 
-    private void load0 (LineReader lr) throws IOException {
-        char[] convtBuf = new char[1024];
+    private void load0(LineReader lr) throws IOException {
+        StringBuilder outBuffer = new StringBuilder(128);
         int limit;
         int keyLen;
         int valueStart;
-        char c;
         boolean hasSep;
         boolean precedingBackslash;
 
         while ((limit = lr.readLine()) >= 0) {
-            c = 0;
             keyLen = 0;
             valueStart = limit;
             hasSep = false;
 
             //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">");
             precedingBackslash = false;
             while (keyLen < limit) {
-                c = lr.lineBuf[keyLen];
+                char c = lr.lineBuf[keyLen];
                 //need check if escaped.
                 if ((c == '=' ||  c == ':') && !precedingBackslash) {
                     valueStart = keyLen + 1;
                     hasSep = true;
                     break;

@@ -438,174 +437,187 @@
                     precedingBackslash = false;
                 }
                 keyLen++;
             }
             while (valueStart < limit) {
-                c = lr.lineBuf[valueStart];
+                char c = lr.lineBuf[valueStart];
                 if (c != ' ' && c != '\t' &&  c != '\f') {
                     if (!hasSep && (c == '=' ||  c == ':')) {
                         hasSep = true;
                     } else {
                         break;
                     }
                 }
                 valueStart++;
             }
-            String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
-            String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
+            String key = loadConvert(lr.lineBuf, 0, keyLen, outBuffer);
+            String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, outBuffer);
             put(key, value);
         }
     }
 
     /* Read in a "logical line" from an InputStream/Reader, skip all comment
      * and blank lines and filter out those leading whitespace characters
      * (\u0020, \u0009 and \u000c) from the beginning of a "natural line".
      * Method returns the char length of the "logical line" and stores
      * the line in "lineBuf".
      */
-    class LineReader {
-        public LineReader(InputStream inStream) {
+    private static class LineReader {
+        LineReader(InputStream inStream) {
             this.inStream = inStream;
             inByteBuf = new byte[8192];
         }
 
-        public LineReader(Reader reader) {
+        LineReader(Reader reader) {
             this.reader = reader;
             inCharBuf = new char[8192];
         }
 
-        byte[] inByteBuf;
-        char[] inCharBuf;
         char[] lineBuf = new char[1024];
-        int inLimit = 0;
-        int inOff = 0;
-        InputStream inStream;
-        Reader reader;
+        private byte[] inByteBuf;
+        private char[] inCharBuf;
+        private int inLimit = 0;
+        private int inOff = 0;
+        private InputStream inStream;
+        private Reader reader;
 
         int readLine() throws IOException {
+            // use locals to optimize for interpreted performance
             int len = 0;
-            char c = 0;
+            int off = inOff;
+            int limit = inLimit;
 
             boolean skipWhiteSpace = true;
-            boolean isCommentLine = false;
-            boolean isNewLine = true;
             boolean appendedLineBegin = false;
             boolean precedingBackslash = false;
-            boolean skipLF = false;
+            boolean fromStream = inStream != null;
+            byte[] byteBuf = inByteBuf;
+            char[] charBuf = inCharBuf;
+            char c;
 
             while (true) {
-                if (inOff >= inLimit) {
-                    inLimit = (inStream==null)?reader.read(inCharBuf)
-                                              :inStream.read(inByteBuf);
-                    inOff = 0;
-                    if (inLimit <= 0) {
-                        if (len == 0 || isCommentLine) {
+                if (off >= limit) {
+                    inLimit = limit = fromStream ? inStream.read(byteBuf)
+                                                 : reader.read(charBuf);
+                    if (limit <= 0) {
+                        if (len == 0) {
                             return -1;
                         }
-                        if (precedingBackslash) {
-                            len--;
-                        }
-                        return len;
+                        return precedingBackslash ? len - 1 : len;
                     }
+                    off = 0;
                 }
-                if (inStream != null) {
-                    //The line below is equivalent to calling a
-                    //ISO8859-1 decoder.
-                    c = (char)(inByteBuf[inOff++] & 0xFF);
+                if (fromStream) {
+                    // The line below is equivalent to calling a
+                    // ISO8859-1 decoder.
+                    c = (char) (byteBuf[off++] & 0xFF);
                 } else {
-                    c = inCharBuf[inOff++];
-                }
-                if (skipLF) {
-                    skipLF = false;
-                    if (c == '\n') {
-                        continue;
-                    }
+                    c = charBuf[off++];
                 }
                 if (skipWhiteSpace) {
                     if (c == ' ' || c == '\t' || c == '\f') {
                         continue;
                     }
                     if (!appendedLineBegin && (c == '\r' || c == '\n')) {
                         continue;
                     }
                     skipWhiteSpace = false;
                     appendedLineBegin = false;
+
                 }
-                if (isNewLine) {
-                    isNewLine = false;
+                if (len == 0) { // still on a new logical line
                     if (c == '#' || c == '!') {
-                        // Comment, quickly consume the rest of the line,
-                        // resume on line-break and backslash.
-                        if (inStream != null) {
-                            while (inOff < inLimit) {
-                                byte b = inByteBuf[inOff++];
-                                if (b == '\n' || b == '\r' || b == '\\') {
-                                    c = (char)(b & 0xFF);
-                                    break;
+                        // Comment, quickly consume the rest of the line
+
+                        // When checking for new line characters a range check,
+                        // starting with the higher bound ('\r') means one less
+                        // branch in the common case.
+                        commentLoop: while (true) {
+                            if (fromStream) {
+                                byte b;
+                                while (off < limit) {
+                                    b = byteBuf[off++];
+                                    if (b <= '\r' && (b == '\r' || b == '\n'))
+                                        break commentLoop;
+                                }
+                                if (off == limit) {
+                                    inLimit = limit = inStream.read(byteBuf);
+                                    if (limit <= 0) { // EOF
+                                        return -1;
                                 }
+                                    off = 0;
                             }
                         } else {
-                            while (inOff < inLimit) {
-                                c = inCharBuf[inOff++];
-                                if (c == '\n' || c == '\r' || c == '\\') {
-                                    break;
+                                while (off < limit) {
+                                    c = charBuf[off++];
+                                    if (c <= '\r' && (c == '\r' || c == '\n'))
+                                        break commentLoop;
+                                }
+                                if (off == limit) {
+                                    inLimit = limit = reader.read(charBuf);
+                                    if (limit <= 0) { // EOF
+                                        return -1;
                                 }
+                                    off = 0;
                             }
                         }
-                        isCommentLine = true;
+                        }
+                        skipWhiteSpace = true;
+                        continue;
                     }
                 }
 
                 if (c != '\n' && c != '\r') {
                     lineBuf[len++] = c;
                     if (len == lineBuf.length) {
-                        int newLength = lineBuf.length * 2;
-                        if (newLength < 0) {
-                            newLength = Integer.MAX_VALUE;
-                        }
+                        int newLength = ArraysSupport.newLength(lineBuf.length, 1, lineBuf.length);
                         char[] buf = new char[newLength];
                         System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length);
                         lineBuf = buf;
                     }
-                    //flip the preceding backslash flag
+                    // flip the preceding backslash flag
                     if (c == '\\') {
                         precedingBackslash = !precedingBackslash;
                     } else {
                         precedingBackslash = false;
                     }
-                }
-                else {
+                } else {
                     // reached EOL
-                    if (isCommentLine || len == 0) {
-                        isCommentLine = false;
-                        isNewLine = true;
+                    if (len == 0) {
                         skipWhiteSpace = true;
                         len = 0;
                         continue;
                     }
-                    if (inOff >= inLimit) {
-                        inLimit = (inStream==null)
-                                  ?reader.read(inCharBuf)
-                                  :inStream.read(inByteBuf);
-                        inOff = 0;
-                        if (inLimit <= 0) {
-                            if (precedingBackslash) {
-                                len--;
-                            }
-                            return len;
+                    if (off >= limit) {
+                        inLimit = limit = fromStream ? inStream.read(byteBuf)
+                                                     : reader.read(charBuf);
+                        off = 0;
+                        if (limit <= 0) { // EOF
+                            return precedingBackslash ? len - 1 : len;
                         }
                     }
                     if (precedingBackslash) {
+                        // backslash at EOL is not part of the line
                         len -= 1;
-                        //skip the leading whitespace characters in following line
+                        // skip the leading whitespace characters in following line
                         skipWhiteSpace = true;
                         appendedLineBegin = true;
                         precedingBackslash = false;
+                        // take care not to include any subsequent \n
                         if (c == '\r') {
-                            skipLF = true;
+                            if (fromStream) {
+                                if (byteBuf[off] == '\n') {
+                                    off++;
                         }
                     } else {
+                                if (charBuf[off] == '\n') {
+                                    off++;
+                                }
+                            }
+                        }
+                    } else {
+                        inOff = off;
                         return len;
                     }
                 }
             }
         }

@@ -613,21 +625,14 @@
 
     /*
      * Converts encoded \uxxxx to unicode chars
      * and changes special saved chars to their original forms
      */
-    private String loadConvert (char[] in, int off, int len, char[] convtBuf) {
-        if (convtBuf.length < len) {
-            int newLen = len * 2;
-            if (newLen < 0) {
-                newLen = Integer.MAX_VALUE;
-            }
-            convtBuf = new char[newLen];
-        }
+    private String loadConvert(char[] in, int off, int len, StringBuilder out) {
+        // Reset the shared buffer
+        out.setLength(0);
         char aChar;
-        char[] out = convtBuf;
-        int outLen = 0;
         int end = off + len;
 
         while (off < end) {
             aChar = in[off++];
             if (aChar == '\\') {

@@ -658,23 +663,23 @@
                           default:
                               throw new IllegalArgumentException(
                                            "Malformed \\uxxxx encoding.");
                         }
                      }
-                    out[outLen++] = (char)value;
+                    out.append((char)value);
                 } else {
                     if (aChar == 't') aChar = '\t';
                     else if (aChar == 'r') aChar = '\r';
                     else if (aChar == 'n') aChar = '\n';
                     else if (aChar == 'f') aChar = '\f';
-                    out[outLen++] = aChar;
+                    out.append(aChar);
                 }
             } else {
-                out[outLen++] = aChar;
+                out.append(aChar);
             }
         }
-        return new String (out, 0, outLen);
+        return out.toString();
     }
 
     /*
      * Converts unicodes to encoded \uxxxx and escapes
      * special characters with a preceding slash
< prev index next >