Merge pull request #3389 from lambdageek/bug-43099
[mono.git] / mcs / class / referencesource / mscorlib / system / io / binaryreader.cs
1 // ==++==
2 // 
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6 /*============================================================
7 **
8 ** Class: BinaryReader
9 ** 
10 ** <OWNER>gpaperin</OWNER>
11 **
12 **
13 ** Purpose: Wraps a stream and provides convenient read functionality
14 ** for strings and primitive types.
15 **
16 **
17 ============================================================*/
18 namespace System.IO {
19
20     using System;
21     using System.Runtime;
22     using System.Text;
23     using System.Globalization;
24     using System.Diagnostics.Contracts;
25     using System.Security;
26
27 [System.Runtime.InteropServices.ComVisible(true)]
28     public class BinaryReader : IDisposable
29     {
30         private const int MaxCharBytesSize = 128;
31
32         private Stream   m_stream;
33         private byte[]   m_buffer;
34         private Decoder  m_decoder;
35         private byte[]   m_charBytes;
36         private char[]   m_singleChar;
37         private char[]   m_charBuffer;
38         private int      m_maxCharsSize;  // From MaxCharBytesSize & Encoding
39
40         // Performance optimization for Read() w/ Unicode.  Speeds us up by ~40% 
41         private bool     m_2BytesPerChar;
42         private bool     m_isMemoryStream; // "do we sit on MemoryStream?" for Read/ReadInt32 perf
43         private bool     m_leaveOpen;
44
45         public BinaryReader(Stream input) : this(input, new UTF8Encoding(), false) {
46         }
47
48         public BinaryReader(Stream input, Encoding encoding) : this(input, encoding, false) {
49         }
50
51         public BinaryReader(Stream input, Encoding encoding, bool leaveOpen) {
52             if (input==null) {
53                 throw new ArgumentNullException("input");
54             }
55             if (encoding==null) {
56                 throw new ArgumentNullException("encoding");
57             }
58             if (!input.CanRead)
59                 throw new ArgumentException(Environment.GetResourceString("Argument_StreamNotReadable"));
60             Contract.EndContractBlock();
61             m_stream = input;
62             m_decoder = encoding.GetDecoder();
63             m_maxCharsSize = encoding.GetMaxCharCount(MaxCharBytesSize);
64             int minBufferSize = encoding.GetMaxByteCount(1);  // max bytes per one char
65             if (minBufferSize < 16) 
66                 minBufferSize = 16;
67             m_buffer = new byte[minBufferSize];
68             // m_charBuffer and m_charBytes will be left null.
69
70             // For Encodings that always use 2 bytes per char (or more), 
71             // special case them here to make Read() & Peek() faster.
72             m_2BytesPerChar = encoding is UnicodeEncoding;
73             // check if BinaryReader is based on MemoryStream, and keep this for it's life
74             // we cannot use "as" operator, since derived classes are not allowed
75             m_isMemoryStream = (m_stream.GetType() == typeof(MemoryStream));
76             m_leaveOpen = leaveOpen;
77
78             Contract.Assert(m_decoder!=null, "[BinaryReader.ctor]m_decoder!=null");
79         }
80
81         public virtual Stream BaseStream {
82             get {
83                 return m_stream;
84             }
85         }
86
87         public virtual void Close() {
88             Dispose(true);
89         }
90
91         protected virtual void Dispose(bool disposing) {
92             if (disposing) {
93                 Stream copyOfStream = m_stream;
94                 m_stream = null;
95                 if (copyOfStream != null && !m_leaveOpen)
96                     copyOfStream.Close();
97             }
98             m_stream = null;
99             m_buffer = null;
100             m_decoder = null;
101             m_charBytes = null;
102             m_singleChar = null;
103             m_charBuffer = null;
104         }
105
106         public void Dispose()
107         {
108             Dispose(true);
109         }
110
111         public virtual int PeekChar() {
112             Contract.Ensures(Contract.Result<int>() >= -1);
113
114             if (m_stream==null) __Error.FileNotOpen();
115
116             if (!m_stream.CanSeek)
117                 return -1;
118             long origPos = m_stream.Position;
119             int ch = Read();
120             m_stream.Position = origPos;
121             return ch;
122         }
123         
124         public virtual int Read() {
125             Contract.Ensures(Contract.Result<int>() >= -1);
126
127             if (m_stream==null) {
128                 __Error.FileNotOpen();
129             }
130             return InternalReadOneChar();
131         }
132
133         public virtual bool ReadBoolean(){
134             FillBuffer(1);
135             return (m_buffer[0]!=0);
136         }
137
138         public virtual byte ReadByte() {
139             // Inlined to avoid some method call overhead with FillBuffer.
140             if (m_stream==null) __Error.FileNotOpen();
141
142             int b = m_stream.ReadByte();
143             if (b == -1)
144                 __Error.EndOfFile();
145             return (byte) b;
146         }
147
148         [CLSCompliant(false)]
149         public virtual sbyte ReadSByte() {
150             FillBuffer(1);
151             return (sbyte)(m_buffer[0]);
152         }
153
154         public virtual char ReadChar() {
155             int value = Read();
156             if (value==-1) {
157                 __Error.EndOfFile();
158             }
159             return (char)value;
160         }
161
162         public virtual short ReadInt16() {
163             FillBuffer(2);
164             return (short)(m_buffer[0] | m_buffer[1] << 8);
165         }
166
167         [CLSCompliant(false)]
168         public virtual ushort ReadUInt16(){
169             FillBuffer(2);
170             return (ushort)(m_buffer[0] | m_buffer[1] << 8);
171         }
172
173         public virtual int ReadInt32() {
174             if (m_isMemoryStream) {
175                 if (m_stream==null) __Error.FileNotOpen();
176                 // read directly from MemoryStream buffer
177                 MemoryStream mStream = m_stream as MemoryStream;
178                 Contract.Assert(mStream != null, "m_stream as MemoryStream != null");
179
180                 return mStream.InternalReadInt32();
181             }
182             else
183             {
184                 FillBuffer(4);
185                 return (int)(m_buffer[0] | m_buffer[1] << 8 | m_buffer[2] << 16 | m_buffer[3] << 24);
186             }
187         }
188
189         [CLSCompliant(false)]
190         public virtual uint ReadUInt32() {
191             FillBuffer(4);
192             return (uint)(m_buffer[0] | m_buffer[1] << 8 | m_buffer[2] << 16 | m_buffer[3] << 24);
193         }
194
195         public virtual long ReadInt64() {
196             FillBuffer(8);
197             uint lo = (uint)(m_buffer[0] | m_buffer[1] << 8 |
198                              m_buffer[2] << 16 | m_buffer[3] << 24);
199             uint hi = (uint)(m_buffer[4] | m_buffer[5] << 8 |
200                              m_buffer[6] << 16 | m_buffer[7] << 24);
201             return (long) ((ulong)hi) << 32 | lo;
202         }
203
204         [CLSCompliant(false)]
205         public virtual ulong ReadUInt64() {
206             FillBuffer(8);
207             uint lo = (uint)(m_buffer[0] | m_buffer[1] << 8 |
208                              m_buffer[2] << 16 | m_buffer[3] << 24);
209             uint hi = (uint)(m_buffer[4] | m_buffer[5] << 8 |
210                              m_buffer[6] << 16 | m_buffer[7] << 24);
211             return ((ulong)hi) << 32 | lo;
212         }
213
214         [System.Security.SecuritySafeCritical]  // auto-generated
215         public virtual unsafe float ReadSingle() {
216             FillBuffer(4);
217 #if MONO
218             return Mono.Security.BitConverterLE.ToSingle (m_buffer, 0);
219 #else
220             uint tmpBuffer = (uint)(m_buffer[0] | m_buffer[1] << 8 | m_buffer[2] << 16 | m_buffer[3] << 24);
221             return *((float*)&tmpBuffer);
222 #endif
223         }
224
225         [System.Security.SecuritySafeCritical]  // auto-generated
226         public virtual unsafe double ReadDouble() {
227             FillBuffer(8);
228 #if MONO
229             return Mono.Security.BitConverterLE.ToDouble (m_buffer, 0);
230 #else
231             uint lo = (uint)(m_buffer[0] | m_buffer[1] << 8 |
232                 m_buffer[2] << 16 | m_buffer[3] << 24);
233             uint hi = (uint)(m_buffer[4] | m_buffer[5] << 8 |
234                 m_buffer[6] << 16 | m_buffer[7] << 24);
235
236             ulong tmpBuffer = ((ulong)hi) << 32 | lo;
237             return *((double*)&tmpBuffer);
238 #endif
239         }
240
241         public virtual decimal ReadDecimal() {
242             FillBuffer(16);
243             try {
244                 return Decimal.ToDecimal(m_buffer);
245             }
246             catch (ArgumentException e) {
247                 // ReadDecimal cannot leak out ArgumentException
248                 throw new IOException(Environment.GetResourceString("Arg_DecBitCtor"), e);
249             }
250         }
251
252         public virtual String ReadString() {
253             Contract.Ensures(Contract.Result<String>() != null);
254
255             if (m_stream == null)
256                 __Error.FileNotOpen();
257
258             int currPos = 0;
259             int n;
260             int stringLength;
261             int readLength;
262             int charsRead;
263
264             // Length of the string in bytes, not chars
265             stringLength = Read7BitEncodedInt();
266             if (stringLength<0) {
267                 throw new IOException(Environment.GetResourceString("IO.IO_InvalidStringLen_Len", stringLength));
268             }
269
270             if (stringLength==0) {
271                 return String.Empty;
272             }
273
274             if (m_charBytes==null) {
275                 m_charBytes  = new byte[MaxCharBytesSize];
276             }
277             
278             if (m_charBuffer == null) {
279                 m_charBuffer = new char[m_maxCharsSize];
280             }
281             
282             StringBuilder sb = null; 
283             do
284             {
285                 readLength = ((stringLength - currPos)>MaxCharBytesSize)?MaxCharBytesSize:(stringLength - currPos);
286
287                 n = m_stream.Read(m_charBytes, 0, readLength);
288                 if (n==0) {
289                     __Error.EndOfFile();
290                 }
291
292                 charsRead = m_decoder.GetChars(m_charBytes, 0, n, m_charBuffer, 0);
293
294                 if (currPos == 0 && n == stringLength)
295                     return new String(m_charBuffer, 0, charsRead);
296
297                 if (sb == null)
298                     sb = StringBuilderCache.Acquire(stringLength); // Actual string length in chars may be smaller.
299                 sb.Append(m_charBuffer, 0, charsRead);
300                 currPos +=n;
301             
302             } while (currPos<stringLength);
303
304             return StringBuilderCache.GetStringAndRelease(sb);
305         }
306
307         [SecuritySafeCritical]
308         public virtual int Read(char[] buffer, int index, int count) {
309             if (buffer==null) {
310                 throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
311             }
312             if (index < 0) {
313                 throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
314             }
315             if (count < 0) {
316                 throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
317             }
318             if (buffer.Length - index < count) {
319                 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
320             }
321             Contract.Ensures(Contract.Result<int>() >= 0);
322             Contract.Ensures(Contract.Result<int>() <= count);
323             Contract.EndContractBlock();
324
325             if (m_stream==null)
326                 __Error.FileNotOpen();
327
328             // SafeCritical: index and count have already been verified to be a valid range for the buffer
329             return InternalReadChars(buffer, index, count);
330         }
331
332         [SecurityCritical]
333         private int InternalReadChars(char[] buffer, int index, int count) {
334             Contract.Requires(buffer != null);
335             Contract.Requires(index >= 0 && count >= 0);
336             Contract.Assert(m_stream != null);
337
338             int numBytes = 0;
339             int charsRemaining = count;
340
341             if (m_charBytes==null) {
342                 m_charBytes = new byte[MaxCharBytesSize];
343             }
344
345             while (charsRemaining > 0) {
346                 int charsRead = 0;
347                 // We really want to know what the minimum number of bytes per char
348                 // is for our encoding.  Otherwise for UnicodeEncoding we'd have to
349                 // do ~1+log(n) reads to read n characters.
350                 numBytes = charsRemaining;
351
352                 // special case for DecoderNLS subclasses when there is a hanging byte from the previous loop
353                 DecoderNLS decoder = m_decoder as DecoderNLS;
354                 if (decoder != null && decoder.HasState && numBytes > 1) {
355                     numBytes -= 1;
356                 }
357
358                 if (m_2BytesPerChar)
359                     numBytes <<= 1;
360                 if (numBytes > MaxCharBytesSize)
361                     numBytes = MaxCharBytesSize;
362
363                 int position = 0;
364                 byte[] byteBuffer = null;
365                 if (m_isMemoryStream)
366                 {
367                     MemoryStream mStream = m_stream as MemoryStream;
368                     Contract.Assert(mStream != null, "m_stream as MemoryStream != null");
369
370                     position = mStream.InternalGetPosition();
371                     numBytes = mStream.InternalEmulateRead(numBytes);
372                     byteBuffer = mStream.InternalGetBuffer();
373                 }
374                 else
375                 {
376                     numBytes = m_stream.Read(m_charBytes, 0, numBytes);
377                     byteBuffer = m_charBytes;                  
378                 }
379
380                 if (numBytes == 0) {
381                     return (count - charsRemaining);
382                 }
383
384                 Contract.Assert(byteBuffer != null, "expected byteBuffer to be non-null");
385
386                 checked { 
387
388                     if (position < 0 || numBytes < 0 || position + numBytes > byteBuffer.Length) {
389                         throw new ArgumentOutOfRangeException("byteCount");
390                     }
391
392                     if (index < 0 || charsRemaining < 0 || index + charsRemaining > buffer.Length) {
393                        throw new ArgumentOutOfRangeException("charsRemaining");
394                     }
395
396                     unsafe {
397                         fixed (byte* pBytes = byteBuffer) {
398                             fixed (char* pChars = buffer) {
399                                 charsRead = m_decoder.GetChars(pBytes + position, numBytes, pChars + index, charsRemaining, false);
400                             }
401                         }
402                     }
403                 }
404
405                 charsRemaining -= charsRead;
406                 index+=charsRead;
407             }
408
409             // this should never fail
410             Contract.Assert(charsRemaining >= 0, "We read too many characters.");
411
412             // we may have read fewer than the number of characters requested if end of stream reached 
413             // or if the encoding makes the char count too big for the buffer (e.g. fallback sequence)
414             return (count - charsRemaining);
415         }
416
417         private int InternalReadOneChar() {
418             // I know having a separate InternalReadOneChar method seems a little 
419             // redundant, but this makes a scenario like the security parser code
420             // 20% faster, in addition to the optimizations for UnicodeEncoding I
421             // put in InternalReadChars.   
422             int charsRead = 0;
423             int numBytes = 0;
424             long posSav = posSav = 0;
425             
426             if (m_stream.CanSeek)
427                 posSav = m_stream.Position;
428
429             if (m_charBytes==null) {
430                 m_charBytes = new byte[MaxCharBytesSize]; //
431             }
432             if (m_singleChar==null) {
433                 m_singleChar = new char[1];
434             }
435
436             while (charsRead == 0) {
437                 // We really want to know what the minimum number of bytes per char
438                 // is for our encoding.  Otherwise for UnicodeEncoding we'd have to
439                 // do ~1+log(n) reads to read n characters.
440                 // Assume 1 byte can be 1 char unless m_2BytesPerChar is true.
441                 numBytes = m_2BytesPerChar ? 2 : 1;
442
443                 int r = m_stream.ReadByte();
444                 m_charBytes[0] = (byte) r;
445                 if (r == -1)
446                     numBytes = 0;
447                 if (numBytes == 2) {
448                     r = m_stream.ReadByte();
449                     m_charBytes[1] = (byte) r;
450                     if (r == -1)
451                         numBytes = 1;
452                 }
453
454                 if (numBytes==0) {
455                     // Console.WriteLine("Found no bytes.  We're outta here.");
456                     return -1;
457                 }
458
459                 Contract.Assert(numBytes == 1 || numBytes == 2, "BinaryReader::InternalReadOneChar assumes it's reading one or 2 bytes only.");
460
461                 try {
462
463                     charsRead = m_decoder.GetChars(m_charBytes, 0, numBytes, m_singleChar, 0);
464                 }
465                 catch
466                 {
467                     // Handle surrogate char 
468
469                     if (m_stream.CanSeek)
470                         m_stream.Seek((posSav - m_stream.Position), SeekOrigin.Current);
471                     // else - we can't do much here
472
473                     throw;
474                 }
475
476                 Contract.Assert(charsRead < 2, "InternalReadOneChar - assuming we only got 0 or 1 char, not 2!");
477                 //                Console.WriteLine("That became: " + charsRead + " characters.");
478             }
479             if (charsRead == 0)
480                 return -1;
481             return m_singleChar[0];
482         }
483
484         [SecuritySafeCritical]
485         public virtual char[] ReadChars(int count) {
486             if (count<0) {
487                 throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
488             }
489             Contract.Ensures(Contract.Result<char[]>() != null);
490             Contract.Ensures(Contract.Result<char[]>().Length <= count);
491             Contract.EndContractBlock();
492             if (m_stream == null) {
493                 __Error.FileNotOpen();
494             }
495
496             if (count == 0) {
497                 return EmptyArray<Char>.Value;
498             }
499
500             // SafeCritical: we own the chars buffer, and therefore can guarantee that the index and count are valid
501             char[] chars = new char[count];
502             int n = InternalReadChars(chars, 0, count);
503             if (n!=count) {
504                 char[] copy = new char[n];
505                 Buffer.InternalBlockCopy(chars, 0, copy, 0, 2*n); // sizeof(char)
506                 chars = copy;
507             }
508
509             return chars;
510         }
511
512         public virtual int Read(byte[] buffer, int index, int count) {
513             if (buffer==null)
514                 throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
515             if (index < 0)
516                 throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
517             if (count < 0)
518                 throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
519             if (buffer.Length - index < count)
520                 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
521             Contract.Ensures(Contract.Result<int>() >= 0);
522             Contract.Ensures(Contract.Result<int>() <= count);
523             Contract.EndContractBlock();
524
525             if (m_stream==null) __Error.FileNotOpen();
526             return m_stream.Read(buffer, index, count);
527         }
528
529         public virtual byte[] ReadBytes(int count) {
530             if (count < 0) throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
531             Contract.Ensures(Contract.Result<byte[]>() != null);
532             Contract.Ensures(Contract.Result<byte[]>().Length <= Contract.OldValue(count));
533             Contract.EndContractBlock();
534             if (m_stream==null) __Error.FileNotOpen();
535
536             if (count == 0) {
537                 return EmptyArray<Byte>.Value;
538             }
539
540             byte[] result = new byte[count];
541
542             int numRead = 0;
543             do {
544                 int n = m_stream.Read(result, numRead, count);
545                 if (n == 0)
546                     break;
547                 numRead += n;
548                 count -= n;
549             } while (count > 0);
550
551             if (numRead != result.Length) {
552                 // Trim array.  This should happen on EOF & possibly net streams.
553                 byte[] copy = new byte[numRead];
554                 Buffer.InternalBlockCopy(result, 0, copy, 0, numRead);
555                 result = copy;
556             }
557
558             return result;
559         }
560
561         protected virtual void FillBuffer(int numBytes) {
562             if (m_buffer != null && (numBytes < 0 || numBytes > m_buffer.Length)) {
563                 throw new ArgumentOutOfRangeException("numBytes", Environment.GetResourceString("ArgumentOutOfRange_BinaryReaderFillBuffer"));
564             }
565             int bytesRead=0;
566             int n = 0;
567
568             if (m_stream==null) __Error.FileNotOpen();
569
570             // Need to find a good threshold for calling ReadByte() repeatedly
571             // vs. calling Read(byte[], int, int) for both buffered & unbuffered
572             // streams.
573             if (numBytes==1) {
574                 n = m_stream.ReadByte();
575                 if (n==-1)
576                     __Error.EndOfFile();
577                 m_buffer[0] = (byte)n;
578                 return;
579             }
580
581             do {
582                 n = m_stream.Read(m_buffer, bytesRead, numBytes-bytesRead);
583                 if (n==0) {
584                     __Error.EndOfFile();
585                 }
586                 bytesRead+=n;
587             } while (bytesRead<numBytes);
588         }
589
590         internal protected int Read7BitEncodedInt() {
591             // Read out an Int32 7 bits at a time.  The high bit
592             // of the byte when on means to continue reading more bytes.
593             int count = 0;
594             int shift = 0;
595             byte b;
596             do {
597                 // Check for a corrupted stream.  Read a max of 5 bytes.
598                 // In a future version, add a DataFormatException.
599                 if (shift == 5 * 7)  // 5 bytes max per Int32, shift += 7
600                     throw new FormatException(Environment.GetResourceString("Format_Bad7BitInt32"));
601
602                 // ReadByte handles end of stream cases for us.
603                 b = ReadByte();
604                 count |= (b & 0x7F) << shift;
605                 shift += 7;
606             } while ((b & 0x80) != 0);
607             return count;
608         }
609     }
610 }