• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 /*
3  * Copyright 2024, Google LLC
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  * Neither the name of Google LLC nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 package com.android.tools.smali.util;
33 
34 import static java.lang.Math.max;
35 import static java.lang.Math.min;
36 
37 import java.io.DataInput;
38 import java.io.EOFException;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.io.OutputStream;
42 import java.util.ArrayDeque;
43 import java.util.Arrays;
44 import java.util.Queue;
45 
46 import javax.annotation.Nonnull;
47 
48 /**
49  * Utility methods for working with {@link InputStream}. Based on guava ByteStreams.
50  */
51 public final class InputStreamUtil {
52 
53     private static final int BUFFER_SIZE = 8192;
54 
55     /** Max array length on JVM. */
56     private static final int MAX_ARRAY_LEN = Integer.MAX_VALUE - 8;
57 
58     /** Large enough to never need to expand, given the geometric progression of buffer sizes. */
59     private static final int TO_BYTE_ARRAY_DEQUE_SIZE = 20;
60 
61     /**
62      * Reads all bytes from an input stream into a byte array. Does not close the stream.
63      *
64      * @param in the input stream to read from
65      * @return a byte array containing all the bytes from the stream
66      * @throws IOException if an I/O error occurs
67      */
toByteArray(InputStream in)68     public static byte[] toByteArray(InputStream in) throws IOException {
69         int totalLen = 0;
70         ArrayDeque<byte[]> bufs = new ArrayDeque<byte[]>(TO_BYTE_ARRAY_DEQUE_SIZE);
71 
72         // Roughly size to match what has been read already. Some file systems, such as procfs,
73         // return 0
74         // as their length. These files are very small, so it's wasteful to allocate an 8KB buffer.
75         int initialBufferSize = min(BUFFER_SIZE, max(128, Integer.highestOneBit(totalLen) * 2));
76         // Starting with an 8k buffer, double the size of each successive buffer. Smaller buffers
77         // quadruple in size until they reach 8k, to minimize the number of small reads for longer
78         // streams. Buffers are retained in a deque so that there's no copying between buffers while
79         // reading and so all of the bytes in each new allocated buffer are available for reading
80         // from
81         // the stream.
82         for (int bufSize = initialBufferSize; totalLen < MAX_ARRAY_LEN; bufSize =
83                 saturatedMultiply(bufSize, bufSize < 4096 ? 4 : 2)) {
84             byte[] buf = new byte[min(bufSize, MAX_ARRAY_LEN - totalLen)];
85             bufs.add(buf);
86             int off = 0;
87             while (off < buf.length) {
88                 // always OK to fill buf; its size plus the rest of bufs is never more than
89                 // MAX_ARRAY_LEN
90                 int r = in.read(buf, off, buf.length - off);
91                 if (r == -1) {
92                     return combineBuffers(bufs, totalLen);
93                 }
94                 off += r;
95                 totalLen += r;
96             }
97         }
98 
99         // read MAX_ARRAY_LEN bytes without seeing end of stream
100         if (in.read() == -1) {
101             // oh, there's the end of the stream
102             return combineBuffers(bufs, MAX_ARRAY_LEN);
103         } else {
104             throw new OutOfMemoryError("input is too large to fit in a byte array");
105         }
106     }
107 
saturatedMultiply(int a, int b)108     private static int saturatedMultiply(int a, int b) {
109         long value = (long) a * b;
110         if (value > Integer.MAX_VALUE) {
111             return Integer.MAX_VALUE;
112           }
113           if (value < Integer.MIN_VALUE) {
114             return Integer.MIN_VALUE;
115           }
116           return (int) value;
117     }
118 
combineBuffers(Queue<byte[]> bufs, int totalLen)119     private static byte[] combineBuffers(Queue<byte[]> bufs, int totalLen) {
120         if (bufs.isEmpty()) {
121             return new byte[0];
122         }
123         byte[] result = bufs.remove();
124         if (result.length == totalLen) {
125             return result;
126         }
127         int remaining = totalLen - result.length;
128         result = Arrays.copyOf(result, totalLen);
129         while (remaining > 0) {
130             byte[] buf = bufs.remove();
131             int bytesToCopy = min(remaining, buf.length);
132             int resultOffset = totalLen - remaining;
133             System.arraycopy(buf, 0, result, resultOffset, bytesToCopy);
134             remaining -= bytesToCopy;
135         }
136         return result;
137     }
138 
139     /**
140      * Discards {@code n} bytes of data from the input stream. This method will block until the full
141      * amount has been skipped. Does not close the stream.
142      *
143      * @param in the input stream to read from
144      * @param n the number of bytes to skip
145      * @throws EOFException if this stream reaches the end before skipping all the bytes
146      * @throws IOException if an I/O error occurs, or the stream does not support skipping
147      */
skipFully(InputStream in, long n)148     public static void skipFully(InputStream in, long n) throws IOException {
149         long skipped = skipUpTo(in, n);
150         if (skipped < n) {
151             throw new EOFException(
152                     "reached end of stream after skipping " + skipped + " bytes; " + n
153                             + " bytes expected");
154         }
155     }
156 
157     /**
158      * Discards up to {@code n} bytes of data from the input stream. This method will block until
159      * either the full amount has been skipped or until the end of the stream is reached, whichever
160      * happens first. Returns the total number of bytes skipped.
161      */
skipUpTo(InputStream in, long n)162     static long skipUpTo(InputStream in, long n) throws IOException {
163         long totalSkipped = 0;
164         // A buffer is allocated if skipSafely does not skip any bytes.
165         byte[] buf = null;
166 
167         while (totalSkipped < n) {
168             long remaining = n - totalSkipped;
169             long skipped = skipSafely(in, remaining);
170 
171             if (skipped == 0) {
172                 // Do a buffered read since skipSafely could return 0 repeatedly, for example if
173                 // in.available() always returns 0 (the default).
174                 int skip = (int) Math.min(remaining, BUFFER_SIZE);
175                 if (buf == null) {
176                     // Allocate a buffer bounded by the maximum size that can be requested, for
177                     // example an array of BUFFER_SIZE is unnecessary when the value of remaining
178                     // is smaller.
179                     buf = new byte[skip];
180                 }
181                 if ((skipped = in.read(buf, 0, skip)) == -1) {
182                     // Reached EOF
183                     break;
184                 }
185             }
186 
187             totalSkipped += skipped;
188         }
189 
190         return totalSkipped;
191     }
192 
193     /**
194      * Attempts to skip up to {@code n} bytes from the given input stream, but not more than {@code
195      * in.available()} bytes. This prevents {@code FileInputStream} from skipping more bytes than
196      * actually remain in the file, something that it {@linkplain java.io.FileInputStream#skip(long)
197      * specifies} it can do in its Javadoc despite the fact that it is violating the contract of
198      * {@code InputStream.skip()}.
199      */
skipSafely(InputStream in, long n)200     private static long skipSafely(InputStream in, long n) throws IOException {
201         int available = in.available();
202         return available == 0 ? 0 : in.skip(Math.min(available, n));
203     }
204 
205     /**
206      * Attempts to read enough bytes from the stream to fill the given byte array, with the same
207      * behavior as {@link DataInput#readFully(byte[])}. Does not close the stream.
208      *
209      * @param in the input stream to read from.
210      * @param b the buffer into which the data is read.
211      * @throws EOFException if this stream reaches the end before reading all the bytes.
212      * @throws IOException if an I/O error occurs.
213      */
readFully(@onnull InputStream in, @Nonnull byte[] b)214     public static void readFully(@Nonnull InputStream in, @Nonnull byte[] b) throws IOException {
215         int read = read(in, b, 0, b.length);
216         if (read != b.length) {
217             throw new EOFException(
218                     "reached end of stream after reading " + read + " bytes; " + b.length
219                             + " bytes expected");
220         }
221     }
222 
223     /**
224      * Reads some bytes from an input stream and stores them into the buffer array {@code b}. This
225      * method blocks until {@code len} bytes of input data have been read into the array, or end of
226      * file is detected. The number of bytes read is returned, possibly zero. Does not close the
227      * stream.
228      * <p>
229      * A caller can detect EOF if the number of bytes read is less than {@code len}. All subsequent
230      * calls on the same stream will return zero.
231      * <p>
232      * If {@code b} is null, a {@code NullPointerException} is thrown. If {@code off} is negative,
233      * or {@code len} is negative, or {@code off+len} is greater than the length of the array {@code
234      * b}, then an {@code IndexOutOfBoundsException} is thrown. If {@code len} is zero, then no
235      * bytes are read. Otherwise, the first byte read is stored into element {@code b[off]}, the
236      * next one into {@code b[off+1]}, and so on. The number of bytes read is, at most, equal to
237      * {@code len}.
238      *
239      * @param in the input stream to read from
240      * @param b the buffer into which the data is read
241      * @param off an int specifying the offset into the data
242      * @param len an int specifying the number of bytes to read
243      * @return the number of bytes read
244      * @throws IOException if an I/O error occurs
245      * @throws IndexOutOfBoundsException if {@code off} is negative, if {@code len} is negative, or
246      *             if {@code off + len} is greater than {@code b.length}
247      */
read(@onnull InputStream in, @Nonnull byte[] b, int off, int len)248     public static int read(@Nonnull InputStream in, @Nonnull byte[] b, int off, int len) throws IOException {
249         if (off < 0 || len < 0 || off + len > b.length) {
250             throw new IndexOutOfBoundsException("trying to read invalid offset/length range");
251         }
252 
253         int total = 0;
254         while (total < len) {
255             int result = in.read(b, off + total, len - total);
256             if (result == -1) {
257                 break;
258             }
259             total += result;
260         }
261         return total;
262     }
263 
264     /**
265      * Copies all bytes from the input stream to the output stream. Does not close or flush either
266      * stream.
267      *
268      * @param from the input stream to read from
269      * @param to the output stream to write to
270      * @return the number of bytes copied
271      * @throws IOException if an I/O error occurs
272      */
copy(InputStream from, OutputStream to)273     public static long copy(InputStream from, OutputStream to) throws IOException {
274         if (from == null || to == null) {
275             throw new NullPointerException();
276         }
277         byte[] buf = new byte[BUFFER_SIZE];
278         long total = 0;
279         while (true) {
280             int r = from.read(buf);
281             if (r == -1) {
282                 break;
283             }
284             to.write(buf, 0, r);
285             total += r;
286         }
287         return total;
288     }
289 }
290