< 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,55 **** --- 46,56 ---- 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,430 **** 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]; 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]; //need check if escaped. if ((c == '=' || c == ':') && !precedingBackslash) { valueStart = keyLen + 1; hasSep = true; break; --- 403,429 ---- 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 { ! StringBuilder outBuffer = new StringBuilder(128); int limit; int keyLen; int valueStart; boolean hasSep; boolean precedingBackslash; while ((limit = lr.readLine()) >= 0) { keyLen = 0; valueStart = limit; hasSep = false; //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">"); precedingBackslash = false; while (keyLen < limit) { ! char c = lr.lineBuf[keyLen]; //need check if escaped. if ((c == '=' || c == ':') && !precedingBackslash) { valueStart = keyLen + 1; hasSep = true; break;
*** 438,611 **** precedingBackslash = false; } keyLen++; } while (valueStart < limit) { ! 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); 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) { this.inStream = inStream; inByteBuf = new byte[8192]; } ! public 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; int readLine() throws IOException { int len = 0; ! char c = 0; boolean skipWhiteSpace = true; - boolean isCommentLine = false; - boolean isNewLine = true; boolean appendedLineBegin = false; boolean precedingBackslash = false; ! boolean skipLF = false; while (true) { ! if (inOff >= inLimit) { ! inLimit = (inStream==null)?reader.read(inCharBuf) ! :inStream.read(inByteBuf); ! inOff = 0; ! if (inLimit <= 0) { ! if (len == 0 || isCommentLine) { return -1; } ! if (precedingBackslash) { ! len--; ! } ! return len; } } ! if (inStream != null) { ! //The line below is equivalent to calling a ! //ISO8859-1 decoder. ! c = (char)(inByteBuf[inOff++] & 0xFF); } else { ! c = inCharBuf[inOff++]; ! } ! if (skipLF) { ! skipLF = false; ! if (c == '\n') { ! continue; ! } } 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 (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; } } } else { ! while (inOff < inLimit) { ! c = inCharBuf[inOff++]; ! if (c == '\n' || c == '\r' || c == '\\') { ! break; } } } ! isCommentLine = true; } } if (c != '\n' && c != '\r') { lineBuf[len++] = c; if (len == lineBuf.length) { ! int newLength = lineBuf.length * 2; ! if (newLength < 0) { ! newLength = Integer.MAX_VALUE; ! } char[] buf = new char[newLength]; System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length); lineBuf = buf; } ! //flip the preceding backslash flag if (c == '\\') { precedingBackslash = !precedingBackslash; } else { precedingBackslash = false; } ! } ! else { // reached EOL ! if (isCommentLine || len == 0) { ! isCommentLine = false; ! isNewLine = true; 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 (precedingBackslash) { len -= 1; ! //skip the leading whitespace characters in following line skipWhiteSpace = true; appendedLineBegin = true; precedingBackslash = false; if (c == '\r') { ! skipLF = true; } } else { return len; } } } } --- 437,623 ---- precedingBackslash = false; } keyLen++; } while (valueStart < limit) { ! 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, 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". */ ! private static class LineReader { ! LineReader(InputStream inStream) { this.inStream = inStream; inByteBuf = new byte[8192]; } ! LineReader(Reader reader) { this.reader = reader; inCharBuf = new char[8192]; } char[] lineBuf = new char[1024]; ! 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; ! int off = inOff; ! int limit = inLimit; boolean skipWhiteSpace = true; boolean appendedLineBegin = false; boolean precedingBackslash = false; ! boolean fromStream = inStream != null; ! byte[] byteBuf = inByteBuf; ! char[] charBuf = inCharBuf; ! char c; while (true) { ! if (off >= limit) { ! inLimit = limit = fromStream ? inStream.read(byteBuf) ! : reader.read(charBuf); ! if (limit <= 0) { ! if (len == 0) { return -1; } ! return precedingBackslash ? len - 1 : len; } + off = 0; } ! if (fromStream) { ! // The line below is equivalent to calling a ! // ISO8859-1 decoder. ! c = (char) (byteBuf[off++] & 0xFF); } else { ! c = charBuf[off++]; } if (skipWhiteSpace) { if (c == ' ' || c == '\t' || c == '\f') { continue; } if (!appendedLineBegin && (c == '\r' || c == '\n')) { continue; } skipWhiteSpace = false; appendedLineBegin = false; + } ! if (len == 0) { // still on a new logical line if (c == '#' || c == '!') { ! // 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 (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; } } ! } ! skipWhiteSpace = true; ! continue; } } if (c != '\n' && c != '\r') { lineBuf[len++] = c; if (len == lineBuf.length) { ! 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 if (c == '\\') { precedingBackslash = !precedingBackslash; } else { precedingBackslash = false; } ! } else { // reached EOL ! if (len == 0) { skipWhiteSpace = true; len = 0; continue; } ! 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 skipWhiteSpace = true; appendedLineBegin = true; precedingBackslash = false; + // take care not to include any subsequent \n if (c == '\r') { ! if (fromStream) { ! if (byteBuf[off] == '\n') { ! off++; } } else { + if (charBuf[off] == '\n') { + off++; + } + } + } + } else { + inOff = off; return len; } } } }
*** 613,633 **** /* * 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]; ! } char aChar; - char[] out = convtBuf; - int outLen = 0; int end = off + len; while (off < end) { aChar = in[off++]; if (aChar == '\\') { --- 625,638 ---- /* * Converts encoded \uxxxx to unicode chars * and changes special saved chars to their original forms */ ! private String loadConvert(char[] in, int off, int len, StringBuilder out) { ! // Reset the shared buffer ! out.setLength(0); char aChar; int end = off + len; while (off < end) { aChar = in[off++]; if (aChar == '\\') {
*** 658,680 **** default: throw new IllegalArgumentException( "Malformed \\uxxxx encoding."); } } ! out[outLen++] = (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; } } else { ! out[outLen++] = aChar; } } ! return new String (out, 0, outLen); } /* * Converts unicodes to encoded \uxxxx and escapes * special characters with a preceding slash --- 663,685 ---- default: throw new IllegalArgumentException( "Malformed \\uxxxx encoding."); } } ! 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.append(aChar); } } else { ! out.append(aChar); } } ! return out.toString(); } /* * Converts unicodes to encoded \uxxxx and escapes * special characters with a preceding slash
< prev index next >