8139414: java.util.Scanner hasNext() returns true, next() throws NoSuchElementException
authorsherman
Tue, 14 Jun 2016 16:56:27 -0700
changeset 38953 2cd777b7c655
parent 38952 ca428fb526d4
child 38954 a5bac7d9a6ba
8139414: java.util.Scanner hasNext() returns true, next() throws NoSuchElementException 8072582: Scanner delimits incorrectly when delimiter spans a buffer boundary Reviewed-by: smarks
jdk/src/java.base/share/classes/java/util/Scanner.java
jdk/test/java/util/Scanner/ScanTest.java
--- 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