8139414: java.util.Scanner hasNext() returns true, next() throws NoSuchElementException
8072582: Scanner delimits incorrectly when delimiter spans a buffer boundary
Reviewed-by: smarks
--- a/jdk/src/java.base/share/classes/java/util/Scanner.java Tue Jun 14 10:27:23 2016 -0700
+++ b/jdk/src/java.base/share/classes/java/util/Scanner.java Tue Jun 14 16:56:27 2016 -0700
@@ -793,7 +793,6 @@
private void readInput() {
if (buf.limit() == buf.capacity())
makeSpace();
-
// Prepare to receive data
int p = buf.position();
buf.position(buf.limit());
@@ -806,15 +805,12 @@
lastException = ioe;
n = -1;
}
-
if (n == -1) {
sourceClosed = true;
needInput = false;
}
-
if (n > 0)
needInput = false;
-
// Restore current position and limit for reading
buf.limit(buf.position());
buf.position(p);
@@ -871,15 +867,20 @@
matchValid = false;
matcher.usePattern(delimPattern);
matcher.region(position, buf.limit());
-
// Skip delims first
- if (matcher.lookingAt())
+ if (matcher.lookingAt()) {
+ if (matcher.hitEnd() && !sourceClosed) {
+ // more input might change the match of delims, in which
+ // might change whether or not if there is token left in
+ // buffer (don't update the "position" in this case)
+ needInput = true;
+ return false;
+ }
position = matcher.end();
-
+ }
// If we are sitting at the end, no more tokens in buffer
if (position == buf.limit())
return false;
-
return true;
}
@@ -900,7 +901,6 @@
*/
private String getCompleteTokenInBuffer(Pattern pattern) {
matchValid = false;
-
// Skip delims first
matcher.usePattern(delimPattern);
if (!skipped) { // Enforcing only one skip of leading delims
@@ -941,13 +941,16 @@
foundNextDelim = matcher.find();
}
if (foundNextDelim) {
- // In the rare case that more input could cause the match
- // to be lost and there is more input coming we must wait
- // for more input. Note that hitting the end is okay as long
- // as the match cannot go away. It is the beginning of the
- // next delims we want to be sure about, we don't care if
- // they potentially extend further.
- if (matcher.requireEnd() && !sourceClosed) {
+ // In two rare cases that more input might cause the match to be
+ // lost or change.
+ // (1) if requireEnd() is true, more input might cause the match
+ // to be lost, we must wait for more input.
+ // (2) while hitting the end is okay IF the match does not
+ // go away AND the beginning of the next delims does not change
+ // (we don't care if they potentially extend further). But it's
+ // possible that more input could cause the beginning of the
+ // delims change, so have to wait for more input as well.
+ if ((matcher.requireEnd() || matcher.hitEnd()) && !sourceClosed) {
needInput = true;
return null;
}
@@ -1341,8 +1344,9 @@
saveState();
modCount++;
while (!sourceClosed) {
- if (hasTokenInBuffer())
+ if (hasTokenInBuffer()) {
return revertState(true);
+ }
readInput();
}
boolean result = hasTokenInBuffer();
@@ -1365,7 +1369,6 @@
ensureOpen();
clearCaches();
modCount++;
-
while (true) {
String token = getCompleteTokenInBuffer(null);
if (token != null) {
--- a/jdk/test/java/util/Scanner/ScanTest.java Tue Jun 14 10:27:23 2016 -0700
+++ b/jdk/test/java/util/Scanner/ScanTest.java Tue Jun 14 16:56:27 2016 -0700
@@ -24,7 +24,7 @@
/**
* @test
* @bug 4313885 4926319 4927634 5032610 5032622 5049968 5059533 6223711 6277261 6269946 6288823
- * 8072722
+ * 8072722 8072582 8139414
* @summary Basic tests of java.util.Scanner methods
* @key randomness
* @modules jdk.localedata
@@ -70,6 +70,7 @@
ioExceptionTest();
matchTest();
delimiterTest();
+ boundaryDelimTest();
useLocaleTest();
closeTest();
cacheTest();
@@ -504,6 +505,54 @@
report("Single delim test");
}
+ private static void append(StringBuilder sb, char c, int n) {
+ for (int i = 0; i < n; i++) {
+ sb.append(c);
+ }
+ }
+
+ public static void boundaryDelimTest() throws Exception {
+ // 8072582
+ StringBuilder sb = new StringBuilder();
+ append(sb, 'a', 228); sb.append(",");
+ append(sb, 'b', 293); sb.append("#,#");
+ append(sb, 'c', 308); sb.append(",");
+ append(sb, 'd', 188); sb.append("#,#");
+ append(sb, 'e', 2);
+ try (Scanner scanner = new Scanner(sb.toString())) {
+ scanner.useDelimiter("(#,#)|(,)");
+ while(scanner.hasNext()){
+ String next = scanner.next();
+ if(next.contains("#")){
+ System.out.printf("[%s]%n", next);
+ failCount++;
+ }
+ }
+ }
+
+ // 8139414
+ int i = 1019;
+ sb = new StringBuilder();
+ sb.append("--;");
+ for (int j = 0; j < 1019; ++j) {
+ sb.append(j%10);
+ }
+ sb.append("-;-");
+ String text = sb.toString();
+ try (Scanner scanner = new Scanner(text)) {
+ scanner.useDelimiter("-;(-)?");
+ while (scanner.hasNext()) {
+ scanner.next();
+ }
+ } catch (NoSuchElementException e) {
+ System.out.println("Caught NoSuchElementException " + e);
+ e.printStackTrace();
+ failCount++;
+ }
+
+ report("delim at boundary test");
+ }
+
/*
* The hasNextPattern caches a match of a pattern called the regular cache
* The hasNextType caches a match of that type called the type cache