提交 1ee8539e 编写于 作者: A Andy Gocke 提交者: Jonathon Marolf

Harden compiler against exceptions during stream dispose (#16574)

* Harden compiler against exceptions during stream dispose

It's possible (likely even) for one of the streams the compiler uses for
output files will throw during disposal. Currently this crashes the
compiler. This change adds a wrapper type around stream to catch
exceptions during disposal and report them as diagnostics instead.

Fixes https://devdiv.visualstudio.com/DevDiv/_workitems?id=217719

* Add VB tests

* Respond to PR comments

* Respond to PR comments

* Respond to PR comments

* Conform to style guidelines
上级 ae90e37e
......@@ -7069,6 +7069,101 @@ private byte[] ReadBytes(string path, int count)
}
}
[Fact]
public void IOFailure_DisposeOutputFile()
{
var srcPath = MakeTrivialExe(Temp.CreateDirectory().Path);
var exePath = Path.Combine(Path.GetDirectoryName(srcPath), "test.exe");
var csc = new MockCSharpCompiler(null, _baseDirectory, new[] { "/nologo", "/preferreduilang:en", $"/out:{exePath}", srcPath });
csc.FileOpen = (file, mode, access, share) =>
{
if (file == exePath)
{
return new TestStream(backingStream: new MemoryStream(),
dispose: () => { throw new IOException("Fake IOException"); });
}
return File.Open(file, mode, access, share);
};
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
Assert.Equal(1, csc.Run(outWriter));
Assert.Contains($"error CS0016: Could not write to output file '{exePath}' -- 'Fake IOException'{Environment.NewLine}", outWriter.ToString());
}
[Fact]
public void IOFailure_DisposePdbFile()
{
var srcPath = MakeTrivialExe(Temp.CreateDirectory().Path);
var exePath = Path.Combine(Path.GetDirectoryName(srcPath), "test.exe");
var pdbPath = Path.ChangeExtension(exePath, "pdb");
var csc = new MockCSharpCompiler(null, _baseDirectory, new[] { "/nologo", "/preferreduilang:en", "/debug", $"/out:{exePath}", srcPath });
csc.FileOpen = (file, mode, access, share) =>
{
if (file == pdbPath)
{
return new TestStream(backingStream: new MemoryStream(),
dispose: () => { throw new IOException("Fake IOException"); });
}
return File.Open(file, mode, access, share);
};
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
Assert.Equal(1, csc.Run(outWriter));
Assert.Contains($"error CS0016: Could not write to output file '{pdbPath}' -- 'Fake IOException'{Environment.NewLine}", outWriter.ToString());
}
[Fact]
public void IOFailure_DisposeXmlFile()
{
var srcPath = MakeTrivialExe(Temp.CreateDirectory().Path);
var xmlPath = Path.Combine(Path.GetDirectoryName(srcPath), "test.xml");
var csc = new MockCSharpCompiler(null, _baseDirectory, new[] { "/nologo", "/preferreduilang:en", $"/doc:{xmlPath}", srcPath });
csc.FileOpen = (file, mode, access, share) =>
{
if (file == xmlPath)
{
return new TestStream(backingStream: new MemoryStream(),
dispose: () => { throw new IOException("Fake IOException"); });
}
return File.Open(file, mode, access, share);
};
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
Assert.Equal(1, csc.Run(outWriter));
Assert.Equal($"error CS0016: Could not write to output file '{xmlPath}' -- 'Fake IOException'{Environment.NewLine}", outWriter.ToString());
}
[Fact]
public void IOFailure_DisposeSourceLinkFile()
{
var srcPath = MakeTrivialExe(Temp.CreateDirectory().Path);
var sourceLinkPath = Path.Combine(Path.GetDirectoryName(srcPath), "test.json");
var csc = new MockCSharpCompiler(null, _baseDirectory, new[] { "/nologo", "/preferreduilang:en", "/debug:portable", $"/sourcelink:{sourceLinkPath}", srcPath });
csc.FileOpen = (file, mode, access, share) =>
{
if (file == sourceLinkPath)
{
return new TestStream(backingStream: new MemoryStream(Encoding.UTF8.GetBytes(@"
{
""documents"": {
""f:/build/*"" : ""https://raw.githubusercontent.com/my-org/my-project/1111111111111111111111111111111111111111/*""
}
}
")),
dispose: () => { throw new IOException("Fake IOException"); });
}
return File.Open(file, mode, access, share);
};
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
Assert.Equal(1, csc.Run(outWriter));
Assert.Equal($"error CS0016: Could not write to output file '{sourceLinkPath}' -- 'Fake IOException'{Environment.NewLine}", outWriter.ToString());
}
[Fact]
public void IOFailure_OpenOutputFile()
{
......@@ -7151,9 +7246,9 @@ public void IOFailure_OpenXmlFinal()
CleanupAllGeneratedFiles(sourcePath);
}
private string MakeTrivialExe()
private string MakeTrivialExe(string directory = null)
{
return Temp.CreateFile(prefix: "", extension: ".cs").WriteAllText(@"
return Temp.CreateFile(directory: directory, prefix: "", extension: ".cs").WriteAllText(@"
class Program
{
public static void Main() { }
......
......@@ -31,6 +31,7 @@
</Content>
<Compile Include="DiagnosticAnalyzer\AnalyzerManager.AnalyzerExecutionContext.cs" />
<Compile Include="InternalUtilities\CommandLineUtilities.cs" />
<Compile Include="InternalUtilities\NoThrowStreamDisposer.cs" />
<Compile Include="InternalUtilities\OrderedMultiDictionary.cs" />
<Compile Include="InternalUtilities\PlatformInformation.cs" />
<Compile Include="Serialization\FixedObjectBinder.cs" />
......
......@@ -13,23 +13,34 @@ internal abstract partial class CommonCompiler
/// This implementation of <see cref="Compilation.EmitStreamProvider"/> will delay the creation
/// of the PE / PDB file until the compiler determines the compilation has succeeded. This prevents
/// the compiler from deleting output from the previous compilation when a new compilation
/// fails.
/// fails. The <see cref="Close"/> method must be called to retrieve all diagnostics.
/// </summary>
private sealed class CompilerEmitStreamProvider : Compilation.EmitStreamProvider, IDisposable
private sealed class CompilerEmitStreamProvider : Compilation.EmitStreamProvider
{
private readonly CommonCompiler _compiler;
private readonly string _filePath;
private Stream _streamToDispose;
internal CompilerEmitStreamProvider(CommonCompiler compiler, string filePath)
internal CompilerEmitStreamProvider(
CommonCompiler compiler,
string filePath)
{
_compiler = compiler;
_filePath = filePath;
}
public void Dispose()
public void Close(DiagnosticBag diagnostics)
{
_streamToDispose?.Dispose();
try
{
_streamToDispose?.Dispose();
}
catch (Exception e)
{
var messageProvider = _compiler.MessageProvider;
var diagnosticInfo = new DiagnosticInfo(messageProvider, messageProvider.ERR_OutputWriteFailed, _filePath, e.Message);
diagnostics.Add(messageProvider.CreateDiagnostic(diagnosticInfo));
}
}
public override Stream Stream => null;
......
......@@ -587,7 +587,7 @@ internal int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancella
var finalXmlFilePath = Arguments.DocumentationPath;
var diagnosticBag = DiagnosticBag.GetInstance();
Stream sourceLinkStreamOpt = null;
NoThrowStreamDisposer sourceLinkStreamDisposerOpt = null;
try
{
......@@ -611,7 +611,21 @@ internal int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancella
if (Arguments.SourceLink != null)
{
sourceLinkStreamOpt = OpenFile(Arguments.SourceLink, consoleOutput, FileMode.Open, FileAccess.Read, FileShare.Read);
var sourceLinkStreamOpt = OpenFile(
Arguments.SourceLink,
consoleOutput,
FileMode.Open,
FileAccess.Read,
FileShare.Read);
if (sourceLinkStreamOpt != null)
{
sourceLinkStreamDisposerOpt = new NoThrowStreamDisposer(
sourceLinkStreamOpt,
Arguments.SourceLink,
consoleOutput,
MessageProvider);
}
}
var moduleBeingBuilt = compilation.CheckOptionsAndCreateModuleBuilder(
......@@ -619,7 +633,7 @@ internal int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancella
Arguments.ManifestResources,
emitOptions,
debugEntryPoint: null,
sourceLinkStream: sourceLinkStreamOpt,
sourceLinkStream: sourceLinkStreamDisposerOpt?.Stream,
embeddedTexts: embeddedTexts,
testData: null,
cancellationToken: cancellationToken);
......@@ -641,24 +655,30 @@ internal int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancella
{
// NOTE: as native compiler does, we generate the documentation file
// NOTE: 'in place', replacing the contents of the file if it exists
Stream xmlStreamOpt = null;
NoThrowStreamDisposer xmlStreamDisposerOpt = null;
if (finalXmlFilePath != null)
{
xmlStreamOpt = OpenFile(finalXmlFilePath,
consoleOutput,
FileMode.OpenOrCreate,
FileAccess.Write,
FileShare.ReadWrite | FileShare.Delete);
var xmlStreamOpt = OpenFile(finalXmlFilePath,
consoleOutput,
FileMode.OpenOrCreate,
FileAccess.Write,
FileShare.ReadWrite | FileShare.Delete);
if (xmlStreamOpt == null)
{
return Failed;
}
xmlStreamOpt.SetLength(0);
xmlStreamDisposerOpt = new NoThrowStreamDisposer(
xmlStreamOpt,
finalXmlFilePath,
consoleOutput,
MessageProvider);
}
using (xmlStreamOpt)
using (xmlStreamDisposerOpt)
{
IEnumerable<DiagnosticInfo> errors;
using (var win32ResourceStreamOpt = GetWin32Resources(Arguments, compilation, out errors))
......@@ -670,17 +690,22 @@ internal int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancella
success = compilation.GenerateResourcesAndDocumentationComments(
moduleBeingBuilt,
xmlStreamOpt,
xmlStreamDisposerOpt?.Stream,
win32ResourceStreamOpt,
diagnosticBag,
cancellationToken);
}
}
// only report unused usings if we have success.
if (success)
{
compilation.ReportUnusedImports(null, diagnosticBag, cancellationToken);
}
if (xmlStreamDisposerOpt?.HasFailedToDispose == true)
{
return Failed;
}
// only report unused usings if we have success.
if (success)
{
compilation.ReportUnusedImports(null, diagnosticBag, cancellationToken);
}
}
......@@ -708,8 +733,10 @@ internal int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancella
{
bool emitPdbFile = Arguments.EmitPdb && emitOptions.DebugInformationFormat != Emit.DebugInformationFormat.Embedded;
using (var peStreamProvider = new CompilerEmitStreamProvider(this, finalPeFilePath))
using (var pdbStreamProviderOpt = emitPdbFile ? new CompilerEmitStreamProvider(this, finalPdbFilePath) : null)
var peStreamProvider = new CompilerEmitStreamProvider(this, finalPeFilePath);
var pdbStreamProviderOpt = emitPdbFile ? new CompilerEmitStreamProvider(this, finalPdbFilePath) : null;
try
{
success = compilation.SerializeToPeStream(
moduleBeingBuilt,
......@@ -719,15 +746,20 @@ internal int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancella
diagnostics: diagnosticBag,
metadataOnly: emitOptions.EmitMetadataOnly,
cancellationToken: cancellationToken);
}
finally
{
peStreamProvider.Close(diagnosticBag);
pdbStreamProviderOpt?.Close(diagnosticBag);
}
if (success && touchedFilesLogger != null)
if (success && touchedFilesLogger != null)
{
if (pdbStreamProviderOpt != null)
{
if (pdbStreamProviderOpt != null)
{
touchedFilesLogger.AddWritten(finalPdbFilePath);
}
touchedFilesLogger.AddWritten(finalPeFilePath);
touchedFilesLogger.AddWritten(finalPdbFilePath);
}
touchedFilesLogger.AddWritten(finalPeFilePath);
}
}
}
......@@ -741,7 +773,12 @@ internal int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancella
finally
{
diagnosticBag.Free();
sourceLinkStreamOpt?.Dispose();
sourceLinkStreamDisposerOpt?.Dispose();
}
if (sourceLinkStreamDisposerOpt?.HasFailedToDispose == true)
{
return Failed;
}
cancellationToken.ThrowIfCancellationRequested();
......@@ -776,26 +813,37 @@ internal int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancella
touchedFilesLogger.AddWritten(finalXmlFilePath);
}
var readStream = OpenFile(Arguments.TouchedFilesPath + ".read", consoleOutput, mode: FileMode.OpenOrCreate);
if (readStream == null)
string readFilesPath = Arguments.TouchedFilesPath + ".read";
string writtenFilesPath = Arguments.TouchedFilesPath + ".write";
var readStream = OpenFile(readFilesPath, consoleOutput, mode: FileMode.OpenOrCreate);
var writtenStream = OpenFile(writtenFilesPath, consoleOutput, mode: FileMode.OpenOrCreate);
if (readStream == null || writtenStream == null)
{
return Failed;
}
using (var writer = new StreamWriter(readStream))
string filePath = null;
try
{
touchedFilesLogger.WriteReadPaths(writer);
}
filePath = readFilesPath;
using (var writer = new StreamWriter(readStream))
{
touchedFilesLogger.WriteReadPaths(writer);
}
var writtenStream = OpenFile(Arguments.TouchedFilesPath + ".write", consoleOutput, mode: FileMode.OpenOrCreate);
if (writtenStream == null)
{
return Failed;
filePath = writtenFilesPath;
using (var writer = new StreamWriter(writtenStream))
{
touchedFilesLogger.WriteWrittenPaths(writer);
}
}
using (var writer = new StreamWriter(writtenStream))
catch (Exception e)
{
touchedFilesLogger.WriteWrittenPaths(writer);
Debug.Assert(filePath != null);
MessageProvider.ReportStreamWriteException(e, filePath, consoleOutput);
return Failed;
}
}
}
......@@ -926,11 +974,12 @@ protected virtual string GetOutputFileName(Compilation compilation, Cancellation
}
private Func<string, FileMode, FileAccess, FileShare, Stream> _fileOpen;
private Stream OpenFile(string filePath,
TextWriter consoleOutput,
FileMode mode = FileMode.Open,
FileAccess access = FileAccess.ReadWrite,
FileShare share = FileShare.None)
private Stream OpenFile(
string filePath,
TextWriter consoleOutput,
FileMode mode = FileMode.Open,
FileAccess access = FileAccess.ReadWrite,
FileShare share = FileShare.None)
{
try
{
......@@ -938,13 +987,7 @@ protected virtual string GetOutputFileName(Compilation compilation, Cancellation
}
catch (Exception e)
{
if (consoleOutput != null)
{
// TODO: distinct error message?
DiagnosticInfo diagnosticInfo = new DiagnosticInfo(MessageProvider, (int)MessageProvider.ERR_OutputWriteFailed, filePath, e.Message);
consoleOutput.WriteLine(diagnosticInfo.ToString(Culture));
}
MessageProvider.ReportStreamWriteException(e, filePath, consoleOutput);
return null;
}
}
......
......@@ -2,6 +2,7 @@
using System;
using System.Globalization;
using System.IO;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
......@@ -216,6 +217,18 @@ public DiagnosticInfo FilterDiagnosticInfo(DiagnosticInfo diagnosticInfo, Compil
public abstract int ERR_ModuleEmitFailure { get; }
public abstract int ERR_EncUpdateFailedMissingAttribute { get; }
/// <summary>
/// Takes an exception produced while writing to a file stream and produces a diagnostic.
/// </summary>
public void ReportStreamWriteException(Exception e, string filePath, TextWriter consoleOutput)
{
if (consoleOutput != null)
{
var diagnostic = new DiagnosticInfo(this, ERR_OutputWriteFailed, filePath, e.Message);
consoleOutput.WriteLine(diagnostic.ToString(consoleOutput.FormatProvider));
}
}
public abstract void ReportInvalidAttributeArgument(DiagnosticBag diagnostics, SyntaxNode attributeSyntax, int parameterIndex, AttributeData attribute);
public abstract void ReportInvalidNamedArgument(DiagnosticBag diagnostics, SyntaxNode attributeSyntax, int namedArgumentIndex, ITypeSymbol attributeClass, string parameterName);
public abstract void ReportParameterNotValidForType(DiagnosticBag diagnostics, SyntaxNode attributeSyntax, int namedArgumentIndex);
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.IO;
using Microsoft.CodeAnalysis;
namespace Roslyn.Utilities
{
/// <summary>
/// Catches exceptions thrown during disposal of the underlying stream and
/// writes them to the given <see cref="TextWriter"/>. Check
/// <see cref="HasFailedToDispose" /> after disposal to see if any
/// exceptions were thrown during disposal.
/// </summary>
internal class NoThrowStreamDisposer : IDisposable
{
private bool? _failed; // Nullable to assert that this is only checked after dispose
private readonly string _filePath;
private readonly TextWriter _writer;
private readonly CommonMessageProvider _messageProvider;
/// <summary>
/// Underlying stream
/// </summary>
public Stream Stream { get; }
/// <summary>
/// True iff an exception was thrown during a call to <see cref="Dispose"/>
/// </summary>
public bool HasFailedToDispose
{
get
{
Debug.Assert(_failed != null);
return _failed.GetValueOrDefault();
}
}
public NoThrowStreamDisposer(
Stream stream,
string filePath,
TextWriter writer,
CommonMessageProvider messageProvider)
{
Stream = stream;
_failed = null;
_filePath = filePath;
_writer = writer;
_messageProvider = messageProvider;
}
public void Dispose()
{
Debug.Assert(_failed == null);
try
{
Stream.Dispose();
if (_failed == null)
{
_failed = false;
}
}
catch (Exception e)
{
_messageProvider.ReportStreamWriteException(e, _filePath, _writer);
// Record if any exceptions are thrown during dispose
_failed = true;
}
}
}
}
......@@ -8020,6 +8020,94 @@ End Module
CleanupAllGeneratedFiles(sourceFile.Path)
End Sub
<Fact>
Public Sub IOFailure_DisposeOutputFile()
Dim srcPath = MakeTrivialExe(Temp.CreateDirectory().Path)
Dim exePath = Path.Combine(Path.GetDirectoryName(srcPath), "test.exe")
Dim csc = New MockVisualBasicCompiler(_baseDirectory, {"/nologo", "/preferreduilang:en", $"/out:{exePath}", srcPath})
csc.FileOpen = Function(filePath, mode, access, share)
If filePath = exePath Then
Return New TestStream(backingStream:=New MemoryStream(), dispose:=Sub() Throw New IOException("Fake IOException"))
End If
Return File.Open(filePath, mode, access, share)
End Function
Dim outWriter = New StringWriter(CultureInfo.InvariantCulture)
Assert.Equal(1, csc.Run(outWriter))
Assert.Equal($"vbc : error BC2012: can't open '{exePath}' for writing: Fake IOException{Environment.NewLine}", outWriter.ToString())
End Sub
<Fact>
Public Sub IOFailure_DisposePdbFile()
Dim srcPath = MakeTrivialExe(Temp.CreateDirectory().Path)
Dim exePath = Path.Combine(Path.GetDirectoryName(srcPath), "test.exe")
Dim pdbPath = Path.ChangeExtension(exePath, "pdb")
Dim csc = New MockVisualBasicCompiler(_baseDirectory, {"/nologo", "/preferreduilang:en", "/debug", $"/out:{exePath}", srcPath})
csc.FileOpen = Function(filePath, mode, access, share)
If filePath = pdbPath Then
Return New TestStream(backingStream:=New MemoryStream(), dispose:=Sub() Throw New IOException("Fake IOException"))
End If
Return File.Open(filePath, mode, access, share)
End Function
Dim outWriter = New StringWriter(CultureInfo.InvariantCulture)
Assert.Equal(1, csc.Run(outWriter))
Assert.Equal($"vbc : error BC2012: can't open '{pdbPath}' for writing: Fake IOException{Environment.NewLine}", outWriter.ToString())
End Sub
<Fact>
Public Sub IOFailure_DisposeXmlFile()
Dim srcPath = MakeTrivialExe(Temp.CreateDirectory().Path)
Dim xmlPath = Path.Combine(Path.GetDirectoryName(srcPath), "test.xml")
Dim csc = New MockVisualBasicCompiler(_baseDirectory, {"/nologo", "/preferreduilang:en", $"/doc:{xmlPath}", srcPath})
csc.FileOpen = Function(filePath, mode, access, share)
If filePath = xmlPath Then
Return New TestStream(backingStream:=New MemoryStream(), dispose:=Sub() Throw New IOException("Fake IOException"))
End If
Return File.Open(filePath, mode, access, share)
End Function
Dim outWriter = New StringWriter(CultureInfo.InvariantCulture)
Assert.Equal(1, csc.Run(outWriter))
Assert.Equal($"error BC2012: can't open '{xmlPath}' for writing: Fake IOException{Environment.NewLine}", outWriter.ToString())
End Sub
<Fact>
Public Sub IOFailure_DisposeSourceLinkFile()
Dim srcPath = MakeTrivialExe(Temp.CreateDirectory().Path)
Dim sourceLinkPath = Path.Combine(Path.GetDirectoryName(srcPath), "test.json")
Dim csc = New MockVisualBasicCompiler(_baseDirectory, {"/nologo", "/preferreduilang:en", "/debug:portable", $"/sourcelink:{sourceLinkPath}", srcPath})
csc.FileOpen = Function(filePath, mode, access, share)
If filePath = sourceLinkPath Then
Return New TestStream(
backingStream:=New MemoryStream(Encoding.UTF8.GetBytes("
{
""documents"": {
""f:/build/*"" : ""https://raw.githubusercontent.com/my-org/my-project/1111111111111111111111111111111111111111/*""
}
}
")),
dispose:=Sub() Throw New IOException("Fake IOException"))
End If
Return File.Open(filePath, mode, access, share)
End Function
Dim outWriter = New StringWriter(CultureInfo.InvariantCulture)
Assert.Equal(1, csc.Run(outWriter))
Assert.Equal($"error BC2012: can't open '{sourceLinkPath}' for writing: Fake IOException{Environment.NewLine}", outWriter.ToString())
End Sub
Private Function MakeTrivialExe(Optional directory As String = Nothing) As String
Return Temp.CreateFile(directory:=directory, prefix:="", extension:=".vb").WriteAllText("
Class Program
Public Shared Sub Main()
End Sub
End Class").Path
End Function
End Class
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
......
......@@ -7,65 +7,105 @@ namespace Roslyn.Test.Utilities
{
public class TestStream : Stream
{
private readonly bool _canRead, _canSeek, _canWrite;
private readonly bool? _canRead, _canSeek, _canWrite;
private readonly Func<byte[], int, int, int> _readFunc;
private readonly long _length;
private readonly long? _length;
private readonly Func<long> _getPosition;
private readonly Action<long> _setPosition;
private readonly Stream _backingStream;
private readonly Action _dispose;
public TestStream(
bool canRead = false,
bool canSeek = false,
bool canWrite = false,
bool? canRead = null,
bool? canSeek = null,
bool? canWrite = null,
Func<byte[], int, int, int> readFunc = null,
long length = 0,
long? length = null,
Func<long> getPosition = null,
Action<long> setPosition = null)
Action<long> setPosition = null,
Stream backingStream = null,
Action dispose = null)
{
_canRead = canRead;
_canSeek = canSeek;
_canWrite = canWrite;
_readFunc = readFunc != null
? readFunc
: (_1, _2, _3) => { throw new NotImplementedException(); };
_readFunc = readFunc;
_length = length;
_getPosition = getPosition != null
? getPosition
: () => { throw new NotImplementedException(); };
_setPosition = setPosition != null
? setPosition
: (_1) => { throw new NotImplementedException(); };
_getPosition = getPosition;
_setPosition = setPosition;
_backingStream = backingStream;
_dispose = dispose;
}
public override bool CanRead => _canRead;
public override bool CanRead => _canRead ?? _backingStream?.CanRead ?? false;
public override bool CanSeek => _canSeek;
public override bool CanSeek => _canSeek ?? _backingStream?.CanSeek ?? false;
public override bool CanWrite => _canWrite;
public override bool CanWrite => _canWrite ?? _backingStream?.CanWrite ?? false;
public override long Length => _length;
public override long Length => _length ?? _backingStream?.Length ?? 0;
public override long Position
{
get
{
if (!CanSeek) throw new NotSupportedException();
return _getPosition();
if (_getPosition != null)
{
return _getPosition();
}
if (_backingStream != null)
{
return _backingStream.Position;
}
throw new NotImplementedException();
}
set
{
if (!CanSeek) throw new NotSupportedException();
_setPosition(value);
if (!CanSeek)
{
throw new NotSupportedException();
}
if (_setPosition != null)
{
_setPosition(value);
}
else if (_backingStream != null)
{
_backingStream.Position = value;
}
else
{
throw new NotImplementedException();
}
}
}
public override void Flush()
{
throw new NotSupportedException();
if (_backingStream == null)
{
throw new NotSupportedException();
}
_backingStream.Flush();
}
public override int Read(byte[] buffer, int offset, int count) => _readFunc(buffer, offset, count);
public override int Read(byte[] buffer, int offset, int count)
{
if (_readFunc != null)
{
return _readFunc(buffer, offset, count);
}
else if (_backingStream != null)
{
return _backingStream.Read(buffer, offset, count);
}
else
{
throw new NotImplementedException();
}
}
public override long Seek(long offset, SeekOrigin origin)
{
......@@ -79,7 +119,11 @@ public override long Seek(long offset, SeekOrigin origin)
public override void SetLength(long value)
{
throw new NotSupportedException();
if (_backingStream == null)
{
throw new NotSupportedException();
}
_backingStream.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
......@@ -89,5 +133,18 @@ public override void Write(byte[] buffer, int offset, int count)
throw new NotSupportedException();
}
}
protected override void Dispose(bool disposing)
{
if (_dispose != null)
{
_dispose();
}
else
{
_backingStream?.Dispose();
}
base.Dispose(disposing);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册