1 /*
2  * Copyright 2023 Code Intelligence GmbH
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.code_intelligence.jazzer.mutation.support;
18 
19 import static com.code_intelligence.jazzer.mutation.support.Preconditions.require;
20 import static java.lang.Math.max;
21 import static java.lang.Math.min;
22 import static java.util.Objects.requireNonNull;
23 
24 import java.io.ByteArrayInputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.util.ArrayDeque;
28 import java.util.Arrays;
29 import java.util.Queue;
30 
31 public final class InputStreamSupport {
readAllBytes(InputStream stream)32   public static byte[] readAllBytes(InputStream stream) throws IOException {
33     requireNonNull(stream);
34     Queue<byte[]> buffers = new ArrayDeque<>();
35     int arrayLength = 0;
36   outer:
37     while (true) {
38       byte[] buffer = new byte[max(8192, stream.available())];
39       buffers.add(buffer);
40       int off = 0;
41       while (off < buffer.length) {
42         int bytesRead = stream.read(buffer, off, buffer.length - off);
43         if (bytesRead == -1) {
44           break outer;
45         }
46         off += bytesRead;
47         arrayLength += bytesRead;
48       }
49     }
50 
51     byte[] result = new byte[arrayLength];
52     int offset = 0;
53     byte[] buffer;
54     int remaining = arrayLength;
55     while ((buffer = buffers.poll()) != null) {
56       int toCopy = min(buffer.length, remaining);
57       System.arraycopy(buffer, 0, result, offset, toCopy);
58       remaining -= toCopy;
59     }
60     return result;
61   }
62 
63   private static final InputStream infiniteZerosStream = new ExtendWithNullInputStream();
64 
65   /**
66    * @return an infinite stream consisting of 0s
67    */
infiniteZeros()68   public static InputStream infiniteZeros() {
69     return infiniteZerosStream;
70   }
71 
72   /**
73    * @return {@code stream} extended with 0s to an infinite stream
74    */
extendWithZeros(InputStream stream)75   public static InputStream extendWithZeros(InputStream stream) {
76     if (stream instanceof ExtendWithNullInputStream) {
77       return stream;
78     }
79     return new ExtendWithNullInputStream(requireNonNull(stream));
80   }
81 
82   public static final class ExtendWithNullInputStream extends InputStream {
83     private static final InputStream ALWAYS_EOF = new ByteArrayInputStream(new byte[0]);
84     private final InputStream stream;
85     private boolean eof;
86 
ExtendWithNullInputStream()87     private ExtendWithNullInputStream() {
88       this.stream = ALWAYS_EOF;
89       this.eof = true;
90     }
91 
ExtendWithNullInputStream(InputStream stream)92     private ExtendWithNullInputStream(InputStream stream) {
93       this.stream = stream;
94       this.eof = false;
95     }
96 
97     @Override
read()98     public int read() throws IOException {
99       if (eof) {
100         return 0;
101       }
102 
103       int res = stream.read();
104       if (res != -1) {
105         return res;
106       } else {
107         eof = true;
108         return 0;
109       }
110     }
111 
112     @Override
read(byte[] b, int off, int len)113     public int read(byte[] b, int off, int len) throws IOException {
114       if (eof) {
115         Arrays.fill(b, off, off + len, (byte) 0);
116       } else {
117         int bytesRead = stream.read(b, off, len);
118         if (bytesRead < len) {
119           eof = true;
120           Arrays.fill(b, max(off, off + bytesRead), off + len, (byte) 0);
121         }
122       }
123       return len;
124     }
125 
126     @Override
available()127     public int available() throws IOException {
128       if (eof) {
129         return Integer.MAX_VALUE;
130       } else {
131         return stream.available();
132       }
133     }
134 
135     @Override
close()136     public void close() throws IOException {
137       stream.close();
138     }
139   }
140 
141   /**
142    * @return a stream with the first {@code bytes} bytes of {@code stream}
143    */
cap(InputStream stream, long bytes)144   public static InputStream cap(InputStream stream, long bytes) {
145     requireNonNull(stream);
146     require(bytes >= 0, "bytes must be non-negative");
147     return new CappedInputStream(stream, bytes);
148   }
149 
150   private static final class CappedInputStream extends InputStream {
151     private final InputStream stream;
152     private long remaining;
153 
CappedInputStream(InputStream stream, long remaining)154     CappedInputStream(InputStream stream, long remaining) {
155       this.stream = stream;
156       this.remaining = remaining;
157     }
158 
159     @Override
read()160     public int read() throws IOException {
161       if (remaining == 0) {
162         return -1;
163       }
164 
165       int res = stream.read();
166       if (res != -1) {
167         --remaining;
168       }
169       return res;
170     }
171 
172     @Override
read(byte[] b, int off, int len)173     public int read(byte[] b, int off, int len) throws IOException {
174       if (remaining == 0) {
175         return -1;
176       }
177 
178       int res = stream.read(b, off, (int) min(len, remaining));
179       if (res != -1) {
180         remaining -= res;
181       }
182       return res;
183     }
184 
185     @Override
available()186     public int available() throws IOException {
187       return (int) min(stream.available(), remaining);
188     }
189 
190     @Override
close()191     public void close() throws IOException {
192       stream.close();
193     }
194   }
195 
196   /**
197    * Wraps a given stream with the functionality to detect if it was read exactly.
198    * To do so, the stream must provide an accurate implementation of {@link
199    * InputStream#available()}, hence it's restricted to {@link ByteArrayInputStream} for now.
200    *
201    * @return {@code stream} extended that detects if it was consumed exactly
202    */
extendWithReadExactly(ByteArrayInputStream stream)203   public static ReadExactlyInputStream extendWithReadExactly(ByteArrayInputStream stream) {
204     return new ReadExactlyInputStream(requireNonNull(stream));
205   }
206 
207   public static final class ReadExactlyInputStream extends InputStream {
208     private final InputStream stream;
209     private boolean eof;
210 
ReadExactlyInputStream(InputStream stream)211     private ReadExactlyInputStream(InputStream stream) {
212       this.stream = stream;
213       this.eof = false;
214     }
215 
isConsumedExactly()216     public boolean isConsumedExactly() {
217       try {
218         // Forwards availability check to the underlying ByteInputStream,
219         // which is accurate for the number of available bytes.
220         return !eof && available() == 0;
221       } catch (IOException e) {
222         return false;
223       }
224     }
225 
226     @Override
read()227     public int read() throws IOException {
228       int res = stream.read();
229       if (res == -1) {
230         eof = true;
231       }
232       return res;
233     }
234 
235     @Override
read(byte[] b, int off, int len)236     public int read(byte[] b, int off, int len) throws IOException {
237       int read = stream.read(b, off, len);
238       if (read < len) {
239         eof = true;
240       }
241       return read;
242     }
243 
244     @Override
available()245     public int available() throws IOException {
246       return stream.available();
247     }
248 
249     @Override
close()250     public void close() throws IOException {
251       stream.close();
252     }
253   }
254 
InputStreamSupport()255   private InputStreamSupport() {}
256 }
257