changeset 6513:c9e42ad7fb24

8020669: (fs) Files.readAllBytes() does not read any data when Files.size() is 0 Reviewed-by: alanb, chegar, martin, rriggs
author igerasim
date Fri, 02 Aug 2013 17:57:00 +0400
parents 258b1aa653f8
children 91cdaff2fed3 0dd8397766ba
files src/share/classes/java/nio/file/Files.java test/java/nio/file/Files/BytesAndLines.java
diffstat 2 files changed, 74 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/nio/file/Files.java	Wed Jul 31 20:12:06 2013 +0400
+++ b/src/share/classes/java/nio/file/Files.java	Fri Aug 02 17:57:00 2013 +0400
@@ -28,6 +28,8 @@
 import java.nio.file.attribute.*;
 import java.nio.file.spi.FileSystemProvider;
 import java.nio.file.spi.FileTypeDetector;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
 import java.nio.channels.SeekableByteChannel;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -2896,41 +2898,63 @@
     }
 
     /**
-     * Read all the bytes from an input stream. The {@code initialSize}
-     * parameter indicates the initial size of the byte[] to allocate.
+     * The maximum size of array to allocate.
+     * Some VMs reserve some header words in an array.
+     * Attempts to allocate larger arrays may result in
+     * OutOfMemoryError: Requested array size exceeds VM limit
+     */
+    private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
+
+    /**
+     * Reads all the bytes from an input stream. Uses {@code initialSize} as a hint
+     * about how many bytes the stream will have.
+     *
+     * @param   source
+     *          the input stream to read from
+     * @param   initialSize
+     *          the initial size of the byte array to allocate
+     *
+     * @return  a byte array containing the bytes read from the file
+     *
+     * @throws  IOException
+     *          if an I/O error occurs reading from the stream
+     * @throws  OutOfMemoryError
+     *          if an array of the required size cannot be allocated
      */
     private static byte[] read(InputStream source, int initialSize)
-        throws IOException
+            throws IOException
     {
         int capacity = initialSize;
         byte[] buf = new byte[capacity];
         int nread = 0;
-        int rem = buf.length;
         int n;
-        // read to EOF which may read more or less than initialSize (eg: file
-        // is truncated while we are reading)
-        while ((n = source.read(buf, nread, rem)) > 0) {
-            nread += n;
-            rem -= n;
-            assert rem >= 0;
-            if (rem == 0) {
-                // need larger buffer
-                int newCapacity = capacity << 1;
-                if (newCapacity < 0) {
-                    if (capacity == Integer.MAX_VALUE)
-                        throw new OutOfMemoryError("Required array size too large");
-                    newCapacity = Integer.MAX_VALUE;
-                }
-                rem = newCapacity - capacity;
-                buf = Arrays.copyOf(buf, newCapacity);
-                capacity = newCapacity;
+        for (;;) {
+            // read to EOF which may read more or less than initialSize (eg: file
+            // is truncated while we are reading)
+            while ((n = source.read(buf, nread, capacity - nread)) > 0)
+                nread += n;
+
+            // if last call to source.read() returned -1, we are done
+            // otherwise, try to read one more byte; if that failed we're done too
+            if (n < 0 || (n = source.read()) < 0)
+                break;
+
+            // one more byte was read; need to allocate a larger buffer
+            if (capacity <= MAX_BUFFER_SIZE - capacity) {
+                capacity = Math.max(capacity << 1, BUFFER_SIZE);
+            } else {
+                if (capacity == MAX_BUFFER_SIZE)
+                    throw new OutOfMemoryError("Required array size too large");
+                capacity = MAX_BUFFER_SIZE;
             }
+            buf = Arrays.copyOf(buf, capacity);
+            buf[nread++] = (byte)n;
         }
         return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
     }
 
     /**
-     * Read all the bytes from a file. The method ensures that the file is
+     * Reads all the bytes from a file. The method ensures that the file is
      * closed when all bytes have been read or an I/O error, or other runtime
      * exception, is thrown.
      *
@@ -2954,12 +2978,13 @@
      *          method is invoked to check read access to the file.
      */
     public static byte[] readAllBytes(Path path) throws IOException {
-        long size = size(path);
-        if (size > (long)Integer.MAX_VALUE)
-            throw new OutOfMemoryError("Required array size too large");
+        try (FileChannel fc = FileChannel.open(path);
+             InputStream is = Channels.newInputStream(fc)) {
+            long size = fc.size();
+            if (size > (long)MAX_BUFFER_SIZE)
+                throw new OutOfMemoryError("Required array size too large");
 
-        try (InputStream in = newInputStream(path)) {
-             return read(in, (int)size);
+            return read(is, (int)size);
         }
     }
 
--- a/test/java/nio/file/Files/BytesAndLines.java	Wed Jul 31 20:12:06 2013 +0400
+++ b/test/java/nio/file/Files/BytesAndLines.java	Fri Aug 02 17:57:00 2013 +0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 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
@@ -22,7 +22,7 @@
  */
 
 /* @test
- * @bug 7006126
+ * @bug 7006126 8020669
  * @summary Unit test for methods for Files readAllBytes, readAllLines and
  *     and write methods.
  */
@@ -82,6 +82,16 @@
             write(file, lines, Charset.defaultCharset(), opts);
             throw new RuntimeException("NullPointerException expected");
         } catch (NullPointerException ignore) { }
+
+        // read from procfs
+        if (System.getProperty("os.name").equals("Linux")) {
+            // Refer to the Linux proc(5) man page for details about /proc/self/stat file
+            // procfs reports it to be zero sized, even though data can be read from it
+            String statFile = "/proc/self/stat";
+            Path pathStat = Paths.get(statFile);
+            byte[] data = Files.readAllBytes(pathStat);
+            assertTrue(data.length > 0, "Files.readAllBytes('" + statFile + "') failed to read");
+        }
     }
 
 
@@ -174,6 +184,16 @@
                 throw new RuntimeException("NullPointerException expected");
             } catch (NullPointerException ignore) { }
 
+            // read from procfs
+            if (System.getProperty("os.name").equals("Linux")) {
+                // Refer to the Linux proc(5) man page for details about /proc/self/status file
+                // procfs reports this file to be zero sized, even though data can be read from it
+                String statusFile = "/proc/self/status";
+                Path pathStatus = Paths.get(statusFile);
+                lines = Files.readAllLines(pathStatus, US_ASCII);
+                assertTrue(lines.size() > 0, "Files.readAllLines('" + pathStatus + "') failed to read");
+            }
+
         } finally {
             delete(tmpfile);
         }