提交 12e5e17d 编写于 作者: T Tomáš Matoušek

Improvements to Scripting API exception handling (#11416)

上级 aad4fe55
......@@ -59,13 +59,18 @@ private struct EvaluationState
internal readonly ScriptOptions ScriptOptions;
internal EvaluationState(
ScriptState<object> scriptState,
ScriptState<object> scriptStateOpt,
ScriptOptions scriptOptions,
ImmutableArray<string> sourceSearchPaths,
ImmutableArray<string> referenceSearchPaths,
string workingDirectory)
{
ScriptStateOpt = scriptState;
Debug.Assert(scriptOptions != null);
Debug.Assert(!sourceSearchPaths.IsDefault);
Debug.Assert(!referenceSearchPaths.IsDefault);
Debug.Assert(workingDirectory != null);
ScriptStateOpt = scriptStateOpt;
ScriptOptions = scriptOptions;
SourceSearchPaths = sourceSearchPaths;
ReferenceSearchPaths = referenceSearchPaths;
......@@ -74,6 +79,8 @@ private struct EvaluationState
internal EvaluationState WithScriptState(ScriptState<object> state)
{
Debug.Assert(state != null);
return new EvaluationState(
state,
ScriptOptions,
......@@ -84,6 +91,8 @@ internal EvaluationState WithScriptState(ScriptState<object> state)
internal EvaluationState WithOptions(ScriptOptions options)
{
Debug.Assert(options != null);
return new EvaluationState(
ScriptStateOpt,
options,
......@@ -104,7 +113,7 @@ internal EvaluationState WithOptions(ScriptOptions options)
public Service()
{
var initialState = new EvaluationState(
scriptState: null,
scriptStateOpt: null,
scriptOptions: ScriptOptions.Default,
sourceSearchPaths: ImmutableArray<string>.Empty,
referenceSearchPaths: ImmutableArray<string>.Empty,
......@@ -442,12 +451,8 @@ private async Task<EvaluationState> ExecuteAsync(Task<EvaluationState> lastTask,
// remove references and imports from the options, they have been applied and will be inherited from now on:
state = state.WithOptions(state.ScriptOptions.RemoveImportsAndReferences());
var newScriptState = await ExecuteOnUIThread(script, state.ScriptStateOpt).ConfigureAwait(false);
if (newScriptState != null)
{
DisplaySubmissionResult(newScriptState);
state = state.WithScriptState(newScriptState);
}
var newScriptState = await ExecuteOnUIThread(script, state.ScriptStateOpt, displayResult: true).ConfigureAwait(false);
state = state.WithScriptState(newScriptState);
}
}
catch (Exception e)
......@@ -462,11 +467,15 @@ private async Task<EvaluationState> ExecuteAsync(Task<EvaluationState> lastTask,
return state;
}
private void DisplaySubmissionResult(ScriptState<object> state)
private void DisplayException(Exception e)
{
if (state.Script.HasReturnValue())
if (e is FileLoadException && e.InnerException is InteractiveAssemblyLoaderException)
{
_globals.Print(state.ReturnValue);
Console.Error.WriteLine(e.InnerException.Message);
}
else
{
Console.Error.Write(_replServiceProvider.ObjectFormatter.FormatException(e));
}
}
......@@ -633,7 +642,7 @@ private static void ReportUnhandledException(Exception e)
if (scriptPathOpt != null)
{
var newScriptState = await ExecuteFileAsync(rspState, scriptPathOpt).ConfigureAwait(false);
var newScriptState = await TryExecuteFileAsync(rspState, scriptPathOpt).ConfigureAwait(false);
if (newScriptState != null)
{
// remove references and imports from the options, they have been applied and will be inherited from now on:
......@@ -725,61 +734,53 @@ private Script<object> TryCompile(Script previousScript, string code, string pat
string path)
{
var state = await ReportUnhandledExceptionIfAny(lastTask).ConfigureAwait(false);
var success = false;
try
string fullPath = ResolveRelativePath(path, state.WorkingDirectory, state.SourceSearchPaths, displayPath: false);
if (fullPath != null)
{
var fullPath = ResolveRelativePath(path, state.WorkingDirectory, state.SourceSearchPaths, displayPath: false);
var newScriptState = await ExecuteFileAsync(state, fullPath).ConfigureAwait(false);
var newScriptState = await TryExecuteFileAsync(state, fullPath).ConfigureAwait(false);
if (newScriptState != null)
{
success = true;
state = state.WithScriptState(newScriptState);
return CompleteExecution(state.WithScriptState(newScriptState), operation, success: newScriptState.Exception == null);
}
}
finally
{
state = CompleteExecution(state, operation, success);
}
return state;
return CompleteExecution(state, operation, success: false);
}
/// <summary>
/// Executes specified script file as a submission.
/// </summary>
/// <returns>True if the code has been executed. False if the code doesn't compile.</returns>
/// <remarks>
/// All errors are written to the error output stream.
/// Uses source search paths to resolve unrooted paths.
/// </remarks>
private async Task<ScriptState<object>> ExecuteFileAsync(EvaluationState state, string fullPath)
private async Task<ScriptState<object>> TryExecuteFileAsync(EvaluationState state, string fullPath)
{
Debug.Assert(PathUtilities.IsAbsolute(fullPath));
string content = null;
if (fullPath != null)
try
{
Debug.Assert(PathUtilities.IsAbsolute(fullPath));
try
{
content = File.ReadAllText(fullPath);
}
catch (Exception e)
using (var reader = File.OpenText(fullPath))
{
Console.Error.WriteLine(e.Message);
content = await reader.ReadToEndAsync().ConfigureAwait(false);
}
}
catch (Exception e)
{
// file read errors:
Console.Error.WriteLine(e.Message);
return null;
}
ScriptState<object> newScriptState = null;
if (content != null)
Script<object> script = TryCompile(state.ScriptStateOpt?.Script, content, fullPath, state.ScriptOptions);
if (script == null)
{
Script<object> script = TryCompile(state.ScriptStateOpt?.Script, content, fullPath, state.ScriptOptions);
if (script != null)
{
newScriptState = await ExecuteOnUIThread(script, state.ScriptStateOpt).ConfigureAwait(false);
}
// compilation errors:
return null;
}
return newScriptState;
return await ExecuteOnUIThread(script, state.ScriptStateOpt, displayResult: false).ConfigureAwait(false);
}
private static void DisplaySearchPaths(TextWriter writer, List<string> attemptedFilePaths)
......@@ -801,28 +802,28 @@ private static void DisplaySearchPaths(TextWriter writer, List<string> attempted
}
}
private async Task<ScriptState<object>> ExecuteOnUIThread(Script<object> script, ScriptState<object> stateOpt)
private async Task<ScriptState<object>> ExecuteOnUIThread(Script<object> script, ScriptState<object> stateOpt, bool displayResult)
{
return await ((Task<ScriptState<object>>)s_control.Invoke(
(Func<Task<ScriptState<object>>>)(async () =>
{
try
{
var task = (stateOpt == null) ?
script.RunAsync(_globals, CancellationToken.None) :
script.RunFromAsync(stateOpt, CancellationToken.None);
return await task.ConfigureAwait(false);
}
catch (FileLoadException e) when (e.InnerException is InteractiveAssemblyLoaderException)
var task = (stateOpt == null) ?
script.RunAsync(_globals, catchException: e => true, cancellationToken: CancellationToken.None) :
script.RunFromAsync(stateOpt, catchException: e => true, cancellationToken: CancellationToken.None);
var newState = await task.ConfigureAwait(false);
if (newState.Exception != null)
{
Console.Error.WriteLine(e.InnerException.Message);
return null;
DisplayException(newState.Exception);
}
catch (Exception e)
else if (displayResult && newState.Script.HasReturnValue())
{
Console.Error.Write(_replServiceProvider.ObjectFormatter.FormatException(e));
return null;
_globals.Print(newState.ReturnValue);
}
return newState;
}))).ConfigureAwait(false);
}
......
......@@ -10,6 +10,7 @@
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
......@@ -366,11 +367,10 @@ public void AsyncExecuteFile_SourceKind()
}
[Fact]
public void AsyncExecuteFile_NonExistingFile()
public async Task AsyncExecuteFile_NonExistingFile()
{
var task = _host.ExecuteFileAsync("non existing file");
task.Wait();
Assert.False(task.Result.Success);
var result = await _host.ExecuteFileAsync("non existing file");
Assert.False(result.Success);
var errorOut = ReadErrorOutputToEnd().Trim();
Assert.Contains(FeaturesResources.SpecifiedFileNotFound, errorOut, StringComparison.Ordinal);
......@@ -1155,6 +1155,20 @@ public void Exception()
Assert.True(error.StartsWith(new Exception().Message));
}
[Fact, WorkItem(10883, "https://github.com/dotnet/roslyn/issues/10883")]
public void PreservingDeclarationsOnException()
{
Execute(@"int i = 100;");
Execute(@"int j = 20; throw new System.Exception(""Bang!""); int k = 3;");
Execute(@"i + j + k");
var output = ReadOutputToEnd();
var error = ReadErrorOutputToEnd();
AssertEx.AssertEqualToleratingWhitespaceDifferences("120", output);
AssertEx.AssertEqualToleratingWhitespaceDifferences("Bang!", error);
}
#region Submission result printing - null/void/value.
[Fact]
......
......@@ -820,5 +820,35 @@ public class Lib2
«Gray»
> ", runner.Console.Out.ToString());
}
[Fact]
[WorkItem(6580, "https://github.com/dotnet/roslyn/issues/6580")]
public void PreservingDeclarationsOnException()
{
var runner = CreateRunner(input:
@"int i = 100;
int j = 20; throw new System.Exception(""Bang!""); int k = 3;
i + j + k
");
runner.RunInteractive();
AssertEx.AssertEqualToleratingWhitespaceDifferences(
$@"Microsoft (R) Visual C# Interactive Compiler version {s_compilerVersion}
Copyright (C) Microsoft Corporation. All rights reserved.
Type ""#help"" for more information.
> int i = 100;
> int j = 20; throw new System.Exception(""Bang!""); int k = 3;
«Red»
Bang!
«Gray»
> i + j + k
120
> ", runner.Console.Out.ToString());
AssertEx.AssertEqualToleratingWhitespaceDifferences(
@"Bang!",
runner.Console.Error.ToString());
}
}
}
......@@ -6,11 +6,12 @@
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Scripting.Hosting;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.Scripting.Hosting;
using Microsoft.CodeAnalysis.Scripting.Test;
......@@ -2190,5 +2191,217 @@ public void HostObjectAssemblyReference3()
}
#endregion
#region Exceptions
[Fact]
[WorkItem(6580, "https://github.com/dotnet/roslyn/issues/6580")]
[WorkItem(10883, "https://github.com/dotnet/roslyn/issues/10883")]
public async Task PreservingDeclarationsOnException1()
{
var s0 = CSharpScript.Create(@"
int i = 10;
throw new System.Exception(""Bang!"");
int j = 2;
");
var s1 = s0.ContinueWith(@"
int F() => i + j;
");
var state1 = await s1.RunAsync(catchException: e => true);
Assert.Equal("Bang!", state1.Exception.Message);
var state2 = await state1.ContinueWithAsync<int>("F()");
Assert.Equal(10, state2.ReturnValue);
}
[Fact]
[WorkItem(6580, "https://github.com/dotnet/roslyn/issues/6580")]
[WorkItem(10883, "https://github.com/dotnet/roslyn/issues/10883")]
public async Task PreservingDeclarationsOnException2()
{
var s0 = CSharpScript.Create(@"
int i = 100;
");
var s1 = s0.ContinueWith(@"
int j = 20;
throw new System.Exception(""Bang!"");
int k = 3;
");
var s2 = s1.ContinueWith(@"
int F() => i + j + k;
");
var state2 = await s2.RunAsync(catchException: e => true);
Assert.Equal("Bang!", state2.Exception.Message);
var state3 = await state2.ContinueWithAsync<int>("F()");
Assert.Equal(120, state3.ReturnValue);
}
[Fact]
[WorkItem(6580, "https://github.com/dotnet/roslyn/issues/6580")]
[WorkItem(10883, "https://github.com/dotnet/roslyn/issues/10883")]
public async Task PreservingDeclarationsOnException3()
{
var s0 = CSharpScript.Create(@"
int i = 1000;
");
var s1 = s0.ContinueWith(@"
int j = 200;
throw new System.Exception(""Bang!"");
int k = 30;
");
var s2 = s1.ContinueWith(@"
int l = 4;
");
var s3 = s2.ContinueWith(@"
int F() => i + j + k + l;
");
var state3 = await s3.RunAsync(catchException: e => true);
Assert.Equal("Bang!", state3.Exception.Message);
var state4 = await state3.ContinueWithAsync<int>("F()");
Assert.Equal(1200, state4.ReturnValue);
}
[Fact]
[WorkItem(6580, "https://github.com/dotnet/roslyn/issues/6580")]
[WorkItem(10883, "https://github.com/dotnet/roslyn/issues/10883")]
public async Task PreservingDeclarationsOnException4()
{
var state0 = await CSharpScript.RunAsync(@"
int i = 1000;
");
var state1 = await state0.ContinueWithAsync(@"
int j = 200;
throw new System.Exception(""Bang 1!"");
int k = 30;
", catchException: e => true);
Assert.Equal("Bang 1!", state1.Exception.Message);
var state2 = await state1.ContinueWithAsync<int>(@"
int l = 4;
throw new System.Exception(""Bang 2!"");
1
", catchException: e => true);
Assert.Equal("Bang 2!", state2.Exception.Message);
var state4 = await state2.ContinueWithAsync(@"
i + j + k + l
");
Assert.Equal(1204, state4.ReturnValue);
}
[Fact]
public async Task PreservingDeclarationsOnCancellation1()
{
var cancellationSource = new CancellationTokenSource();
var globals = new StrongBox<CancellationTokenSource>();
globals.Value = cancellationSource;
var s0 = CSharpScript.Create(@"
int i = 1000;
", globalsType: globals.GetType());
var s1 = s0.ContinueWith(@"
int j = 200;
Value.Cancel();
int k = 30;
");
// cancellation exception is thrown just before we start evaluating s2:
var s2 = s1.ContinueWith(@"
int l = 4;
");
var s3 = s2.ContinueWith(@"
int F() => i + j + k + l;
");
var state3 = await s3.RunAsync(globals, catchException: e => true, cancellationToken: cancellationSource.Token);
Assert.IsType<OperationCanceledException>(state3.Exception);
var state4 = await state3.ContinueWithAsync<int>("F()");
Assert.Equal(1230, state4.ReturnValue);
}
[Fact]
public async Task PreservingDeclarationsOnCancellation2()
{
var cancellationSource = new CancellationTokenSource();
var globals = new StrongBox<CancellationTokenSource>();
globals.Value = cancellationSource;
var s0 = CSharpScript.Create(@"
int i = 1000;
", globalsType: globals.GetType());
var s1 = s0.ContinueWith(@"
int j = 200;
int k = 30;
");
var s2 = s1.ContinueWith(@"
int l = 4;
Value.Cancel();
");
// cancellation exception is thrown just before we start evaluating s3:
var s3 = s2.ContinueWith(@"
int F() => i + j + k + l;
");
var state3 = await s3.RunAsync(globals, catchException: e => true, cancellationToken: cancellationSource.Token);
Assert.IsType<OperationCanceledException>(state3.Exception);
var state4 = await state3.ContinueWithAsync<int>("F()");
Assert.Equal(1234, state4.ReturnValue);
}
[Fact]
public async Task PreservingDeclarationsOnCancellation3()
{
var cancellationSource = new CancellationTokenSource();
var globals = new StrongBox<CancellationTokenSource>();
globals.Value = cancellationSource;
var s0 = CSharpScript.Create(@"
int i = 1000;
", globalsType: globals.GetType());
var s1 = s0.ContinueWith(@"
int j = 200;
Value.Cancel();
int k = 30;
");
// cancellation exception is thrown just before we start evaluating s2:
var s2 = s1.ContinueWith(@"
int l = 4;
");
var s3 = s2.ContinueWith(@"
int F() => i + j + k + l;
");
await Assert.ThrowsAsync<OperationCanceledException>(() =>
s3.RunAsync(globals, catchException: e => !(e is OperationCanceledException), cancellationToken: cancellationSource.Token));
}
#endregion
}
}
......@@ -191,7 +191,7 @@ private void RunInteractiveLoop(ScriptOptions options, string initialScriptCodeO
if (initialScriptCodeOpt != null)
{
var script = Script.CreateInitialScript<object>(_scriptCompiler, initialScriptCodeOpt, options, globals.GetType(), assemblyLoaderOpt: null);
TryBuildAndRun(script, globals, ref state, ref options, cancellationToken);
BuildAndRun(script, globals, ref state, ref options, displayResult: false, cancellationToken: cancellationToken);
}
while (true)
......@@ -249,52 +249,34 @@ private void RunInteractiveLoop(ScriptOptions options, string initialScriptCodeO
newScript = state.Script.ContinueWith(code, options);
}
if (!TryBuildAndRun(newScript, globals, ref state, ref options, cancellationToken))
{
continue;
}
if (newScript.HasReturnValue())
{
globals.Print(state.ReturnValue);
}
BuildAndRun(newScript, globals, ref state, ref options, displayResult: true, cancellationToken: cancellationToken);
}
}
private bool TryBuildAndRun(Script<object> newScript, InteractiveScriptGlobals globals, ref ScriptState<object> state, ref ScriptOptions options, CancellationToken cancellationToken)
private void BuildAndRun(Script<object> newScript, InteractiveScriptGlobals globals, ref ScriptState<object> state, ref ScriptOptions options, bool displayResult, CancellationToken cancellationToken)
{
var diagnostics = newScript.Compile(cancellationToken);
DisplayDiagnostics(diagnostics);
if (diagnostics.HasAnyErrors())
{
return false;
return;
}
try
{
var task = (state == null) ?
newScript.RunAsync(globals, cancellationToken) :
newScript.RunFromAsync(state, cancellationToken);
var task = (state == null) ?
newScript.RunAsync(globals, catchException: e => true, cancellationToken: cancellationToken) :
newScript.RunFromAsync(state, catchException: e => true, cancellationToken: cancellationToken);
state = task.GetAwaiter().GetResult();
}
catch (FileLoadException e) when (e.InnerException is InteractiveAssemblyLoaderException)
state = task.GetAwaiter().GetResult();
if (state.Exception != null)
{
_console.ForegroundColor = ConsoleColor.Red;
_console.Error.WriteLine(e.InnerException.Message);
_console.ResetColor();
return false;
DisplayException(state.Exception);
}
catch (Exception e)
else if (displayResult && newScript.HasReturnValue())
{
DisplayException(e);
return false;
globals.Print(state.ReturnValue);
}
options = UpdateOptions(options, globals);
return true;
}
private static ScriptOptions UpdateOptions(ScriptOptions options, InteractiveScriptGlobals globals)
......@@ -324,7 +306,15 @@ private void DisplayException(Exception e)
try
{
_console.ForegroundColor = ConsoleColor.Red;
_console.Error.Write(_objectFormatter.FormatException(e));
if (e is FileLoadException && e.InnerException is InteractiveAssemblyLoaderException)
{
_console.Error.WriteLine(e.InnerException.Message);
}
else
{
_console.Error.Write(_objectFormatter.FormatException(e));
}
}
finally
{
......
......@@ -59,13 +59,9 @@ Microsoft.CodeAnalysis.Scripting.Script.GetCompilation() -> Microsoft.CodeAnalys
Microsoft.CodeAnalysis.Scripting.Script.GlobalsType.get -> System.Type
Microsoft.CodeAnalysis.Scripting.Script.Options.get -> Microsoft.CodeAnalysis.Scripting.ScriptOptions
Microsoft.CodeAnalysis.Scripting.Script.Previous.get -> Microsoft.CodeAnalysis.Scripting.Script
Microsoft.CodeAnalysis.Scripting.Script.RunAsync(object globals = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState>
Microsoft.CodeAnalysis.Scripting.Script.RunFromAsync(Microsoft.CodeAnalysis.Scripting.ScriptState previousState, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState>
Microsoft.CodeAnalysis.Scripting.Script.WithOptions(Microsoft.CodeAnalysis.Scripting.ScriptOptions options) -> Microsoft.CodeAnalysis.Scripting.Script
Microsoft.CodeAnalysis.Scripting.Script<T>
Microsoft.CodeAnalysis.Scripting.Script<T>.CreateDelegate(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.Scripting.ScriptRunner<T>
Microsoft.CodeAnalysis.Scripting.Script<T>.RunAsync(object globals = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState<T>>
Microsoft.CodeAnalysis.Scripting.Script<T>.RunFromAsync(Microsoft.CodeAnalysis.Scripting.ScriptState previousState, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState<T>>
Microsoft.CodeAnalysis.Scripting.Script<T>.WithOptions(Microsoft.CodeAnalysis.Scripting.ScriptOptions options) -> Microsoft.CodeAnalysis.Scripting.Script<T>
Microsoft.CodeAnalysis.Scripting.ScriptMetadataResolver
Microsoft.CodeAnalysis.Scripting.ScriptMetadataResolver.BaseDirectory.get -> string
......@@ -108,8 +104,6 @@ Microsoft.CodeAnalysis.Scripting.ScriptSourceResolver.WithSearchPaths(System.Col
Microsoft.CodeAnalysis.Scripting.ScriptSourceResolver.WithSearchPaths(System.Collections.Immutable.ImmutableArray<string> searchPaths) -> Microsoft.CodeAnalysis.Scripting.ScriptSourceResolver
Microsoft.CodeAnalysis.Scripting.ScriptSourceResolver.WithSearchPaths(params string[] searchPaths) -> Microsoft.CodeAnalysis.Scripting.ScriptSourceResolver
Microsoft.CodeAnalysis.Scripting.ScriptState
Microsoft.CodeAnalysis.Scripting.ScriptState.ContinueWithAsync(string code, Microsoft.CodeAnalysis.Scripting.ScriptOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState<object>>
Microsoft.CodeAnalysis.Scripting.ScriptState.ContinueWithAsync<TResult>(string code, Microsoft.CodeAnalysis.Scripting.ScriptOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState<TResult>>
Microsoft.CodeAnalysis.Scripting.ScriptState.GetVariable(string name) -> Microsoft.CodeAnalysis.Scripting.ScriptVariable
Microsoft.CodeAnalysis.Scripting.ScriptState.ReturnValue.get -> object
Microsoft.CodeAnalysis.Scripting.ScriptState.Script.get -> Microsoft.CodeAnalysis.Scripting.Script
......
Microsoft.CodeAnalysis.Scripting.Script.RunAsync(object globals = null, System.Func<System.Exception, bool> catchException = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState>
Microsoft.CodeAnalysis.Scripting.Script.RunAsync(object globals, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState>
Microsoft.CodeAnalysis.Scripting.Script.RunFromAsync(Microsoft.CodeAnalysis.Scripting.ScriptState previousState, System.Func<System.Exception, bool> catchException = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState>
Microsoft.CodeAnalysis.Scripting.Script.RunFromAsync(Microsoft.CodeAnalysis.Scripting.ScriptState previousState, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState>
Microsoft.CodeAnalysis.Scripting.Script<T>.RunAsync(object globals = null, System.Func<System.Exception, bool> catchException = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState<T>>
Microsoft.CodeAnalysis.Scripting.Script<T>.RunAsync(object globals, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState<T>>
Microsoft.CodeAnalysis.Scripting.Script<T>.RunFromAsync(Microsoft.CodeAnalysis.Scripting.ScriptState previousState, System.Func<System.Exception, bool> catchException = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState<T>>
Microsoft.CodeAnalysis.Scripting.Script<T>.RunFromAsync(Microsoft.CodeAnalysis.Scripting.ScriptState previousState, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState<T>>
Microsoft.CodeAnalysis.Scripting.ScriptState.ContinueWithAsync(string code, Microsoft.CodeAnalysis.Scripting.ScriptOptions options = null, System.Func<System.Exception, bool> catchException = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState<object>>
Microsoft.CodeAnalysis.Scripting.ScriptState.ContinueWithAsync(string code, Microsoft.CodeAnalysis.Scripting.ScriptOptions options, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState<object>>
Microsoft.CodeAnalysis.Scripting.ScriptState.ContinueWithAsync<TResult>(string code, Microsoft.CodeAnalysis.Scripting.ScriptOptions options = null, System.Func<System.Exception, bool> catchException = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState<TResult>>
Microsoft.CodeAnalysis.Scripting.ScriptState.ContinueWithAsync<TResult>(string code, Microsoft.CodeAnalysis.Scripting.ScriptOptions options, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Scripting.ScriptState<TResult>>
Microsoft.CodeAnalysis.Scripting.ScriptState.Exception.get -> System.Exception
\ No newline at end of file
......@@ -11,6 +11,7 @@
using Microsoft.CodeAnalysis;
using Roslyn.Utilities;
using Microsoft.CodeAnalysis.Scripting.Hosting;
using System.Runtime.CompilerServices;
namespace Microsoft.CodeAnalysis.Scripting
{
......@@ -136,10 +137,26 @@ public Compilation GetCompilation()
/// </param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="ScriptState"/> that represents the state after running the script, including all declared variables and return value.</returns>
public Task<ScriptState> RunAsync(object globals = null, CancellationToken cancellationToken = default(CancellationToken)) =>
CommonRunAsync(globals, cancellationToken);
public Task<ScriptState> RunAsync(object globals, CancellationToken cancellationToken) =>
CommonRunAsync(globals, null, cancellationToken);
internal abstract Task<ScriptState> CommonRunAsync(object globals, CancellationToken cancellationToken);
/// <summary>
/// Runs the script from the beginning.
/// </summary>
/// <param name="globals">
/// An instance of <see cref="Script.GlobalsType"/> holding on values for global variables accessible from the script.
/// Must be specified if and only if the script was created with <see cref="Script.GlobalsType"/>.
/// </param>
/// <param name="catchException">
/// If specified, any exception thrown by the script top-level code is passed to <paramref name="catchException"/>.
/// If it returns true the exception is caught and stored on the resulting <see cref="ScriptState"/>, otherwise the exception is propagated to the caller.
/// </param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="ScriptState"/> that represents the state after running the script, including all declared variables and return value.</returns>
public Task<ScriptState> RunAsync(object globals = null, Func<Exception, bool> catchException = null, CancellationToken cancellationToken = default(CancellationToken)) =>
CommonRunAsync(globals, catchException, cancellationToken);
internal abstract Task<ScriptState> CommonRunAsync(object globals, Func<Exception, bool> catchException, CancellationToken cancellationToken);
/// <summary>
/// Run the script from the specified state.
......@@ -149,10 +166,25 @@ public Compilation GetCompilation()
/// </param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="ScriptState"/> that represents the state after running the script, including all declared variables and return value.</returns>
public Task<ScriptState> RunFromAsync(ScriptState previousState, CancellationToken cancellationToken = default(CancellationToken)) =>
CommonRunFromAsync(previousState, cancellationToken);
public Task<ScriptState> RunFromAsync(ScriptState previousState, CancellationToken cancellationToken) =>
CommonRunFromAsync(previousState, null, cancellationToken);
internal abstract Task<ScriptState> CommonRunFromAsync(ScriptState previousState, CancellationToken cancellationToken);
/// <summary>
/// Run the script from the specified state.
/// </summary>
/// <param name="previousState">
/// Previous state of the script execution.
/// </param>
/// <param name="catchException">
/// If specified, any exception thrown by the script top-level code is passed to <paramref name="catchException"/>.
/// If it returns true the exception is caught and stored on the resulting <see cref="ScriptState"/>, otherwise the exception is propagated to the caller.
/// </param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="ScriptState"/> that represents the state after running the script, including all declared variables and return value.</returns>
public Task<ScriptState> RunFromAsync(ScriptState previousState, Func<Exception, bool> catchException = null, CancellationToken cancellationToken = default(CancellationToken)) =>
CommonRunFromAsync(previousState, catchException, cancellationToken);
internal abstract Task<ScriptState> CommonRunFromAsync(ScriptState previousState, Func<Exception, bool> catchException, CancellationToken cancellationToken);
/// <summary>
/// Forces the script through the compilation step.
......@@ -282,11 +314,11 @@ internal override ImmutableArray<Diagnostic> CommonCompile(CancellationToken can
internal override Task<object> CommonEvaluateAsync(object globals, CancellationToken cancellationToken) =>
EvaluateAsync(globals, cancellationToken).CastAsync<T, object>();
internal override Task<ScriptState> CommonRunAsync(object globals, CancellationToken cancellationToken) =>
RunAsync(globals, cancellationToken).CastAsync<ScriptState<T>, ScriptState>();
internal override Task<ScriptState> CommonRunAsync(object globals, Func<Exception, bool> catchException, CancellationToken cancellationToken) =>
RunAsync(globals, catchException, cancellationToken).CastAsync<ScriptState<T>, ScriptState>();
internal override Task<ScriptState> CommonRunFromAsync(ScriptState previousState, CancellationToken cancellationToken) =>
RunFromAsync(previousState, cancellationToken).CastAsync<ScriptState<T>, ScriptState>();
internal override Task<ScriptState> CommonRunFromAsync(ScriptState previousState, Func<Exception, bool> catchException, CancellationToken cancellationToken) =>
RunFromAsync(previousState, catchException, cancellationToken).CastAsync<ScriptState<T>, ScriptState>();
/// <exception cref="CompilationErrorException">Compilation has errors.</exception>
private Func<object[], Task<T>> GetExecutor(CancellationToken cancellationToken)
......@@ -370,7 +402,25 @@ internal override ImmutableArray<Diagnostic> CommonCompile(CancellationToken can
/// <returns>A <see cref="ScriptState"/> that represents the state after running the script, including all declared variables and return value.</returns>
/// <exception cref="CompilationErrorException">Compilation has errors.</exception>
/// <exception cref="ArgumentException">The type of <paramref name="globals"/> doesn't match <see cref="Script.GlobalsType"/>.</exception>
public new Task<ScriptState<T>> RunAsync(object globals = null, CancellationToken cancellationToken = default(CancellationToken))
public new Task<ScriptState<T>> RunAsync(object globals, CancellationToken cancellationToken)
=> RunAsync(globals, null, cancellationToken);
/// <summary>
/// Runs the script from the beginning.
/// </summary>
/// <param name="globals">
/// An instance of <see cref="Script.GlobalsType"/> holding on values for global variables accessible from the script.
/// Must be specified if and only if the script was created with <see cref="Script.GlobalsType"/>.
/// </param>
/// <param name="catchException">
/// If specified, any exception thrown by the script top-level code is passed to <paramref name="catchException"/>.
/// If it returns true the exception is caught and stored on the resulting <see cref="ScriptState"/>, otherwise the exception is propagated to the caller.
/// </param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="ScriptState"/> that represents the state after running the script, including all declared variables and return value.</returns>
/// <exception cref="CompilationErrorException">Compilation has errors.</exception>
/// <exception cref="ArgumentException">The type of <paramref name="globals"/> doesn't match <see cref="Script.GlobalsType"/>.</exception>
public new Task<ScriptState<T>> RunAsync(object globals = null, Func<Exception, bool> catchException = null, CancellationToken cancellationToken = default(CancellationToken))
{
// The following validation and executor construction may throw;
// do so synchronously so that the exception is not wrapped in the task.
......@@ -381,7 +431,7 @@ public new Task<ScriptState<T>> RunAsync(object globals = null, CancellationToke
var precedingExecutors = GetPrecedingExecutors(cancellationToken);
var currentExecutor = GetExecutor(cancellationToken);
return RunSubmissionsAsync(executionState, precedingExecutors, currentExecutor, cancellationToken);
return RunSubmissionsAsync(executionState, precedingExecutors, currentExecutor, catchException, cancellationToken);
}
/// <summary>
......@@ -399,7 +449,7 @@ public ScriptRunner<T> CreateDelegate(CancellationToken cancellationToken = defa
return (globals, token) =>
{
ValidateGlobals(globals, globalsType);
return ScriptExecutionState.Create(globals).RunSubmissionsAsync<T>(precedingExecutors, currentExecutor, token);
return ScriptExecutionState.Create(globals).RunSubmissionsAsync<T>(precedingExecutors, currentExecutor, null, null, token);
};
}
......@@ -413,7 +463,24 @@ public ScriptRunner<T> CreateDelegate(CancellationToken cancellationToken = defa
/// <returns>A <see cref="ScriptState"/> that represents the state after running the script, including all declared variables and return value.</returns>
/// <exception cref="ArgumentNullException"><paramref name="previousState"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="previousState"/> is not a previous execution state of this script.</exception>
public new Task<ScriptState<T>> RunFromAsync(ScriptState previousState, CancellationToken cancellationToken = default(CancellationToken))
public new Task<ScriptState<T>> RunFromAsync(ScriptState previousState, CancellationToken cancellationToken)
=> RunFromAsync(previousState, null, cancellationToken);
/// <summary>
/// Run the script from the specified state.
/// </summary>
/// <param name="previousState">
/// Previous state of the script execution.
/// </param>
/// <param name="catchException">
/// If specified, any exception thrown by the script top-level code is passed to <paramref name="catchException"/>.
/// If it returns true the exception is caught and stored on the resulting <see cref="ScriptState"/>, otherwise the exception is propagated to the caller.
/// </param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="ScriptState"/> that represents the state after running the script, including all declared variables and return value.</returns>
/// <exception cref="ArgumentNullException"><paramref name="previousState"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="previousState"/> is not a previous execution state of this script.</exception>
public new Task<ScriptState<T>> RunFromAsync(ScriptState previousState, Func<Exception, bool> catchException = null, CancellationToken cancellationToken = default(CancellationToken))
{
// The following validation and executor construction may throw;
// do so synchronously so that the exception is not wrapped in the task.
......@@ -438,13 +505,19 @@ public new Task<ScriptState<T>> RunFromAsync(ScriptState previousState, Cancella
var currentExecutor = GetExecutor(cancellationToken);
ScriptExecutionState newExecutionState = previousState.ExecutionState.FreezeAndClone();
return RunSubmissionsAsync(newExecutionState, precedingExecutors, currentExecutor, cancellationToken);
return RunSubmissionsAsync(newExecutionState, precedingExecutors, currentExecutor, catchException, cancellationToken);
}
private async Task<ScriptState<T>> RunSubmissionsAsync(ScriptExecutionState executionState, ImmutableArray<Func<object[], Task>> precedingExecutors, Func<object[], Task> currentExecutor, CancellationToken cancellationToken)
private async Task<ScriptState<T>> RunSubmissionsAsync(
ScriptExecutionState executionState,
ImmutableArray<Func<object[], Task>> precedingExecutors,
Func<object[], Task> currentExecutor,
Func<Exception, bool> catchExceptionOpt,
CancellationToken cancellationToken)
{
var result = await executionState.RunSubmissionsAsync<T>(precedingExecutors, currentExecutor, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
return new ScriptState<T>(executionState, result, this);
var exceptionOpt = (catchExceptionOpt != null) ? new StrongBox<Exception>() : null;
T result = await executionState.RunSubmissionsAsync<T>(precedingExecutors, currentExecutor, exceptionOpt, catchExceptionOpt, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
return new ScriptState<T>(executionState, this, result, exceptionOpt?.Value);
}
private static void ValidateGlobals(object globals, Type globalsType)
......
......@@ -3,8 +3,11 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Scripting
{
......@@ -62,26 +65,90 @@ public object GetSubmissionState(int index)
internal async Task<TResult> RunSubmissionsAsync<TResult>(
ImmutableArray<Func<object[], Task>> precedingExecutors,
Func<object[], Task> currentExecutor,
StrongBox<Exception> exceptionHolderOpt,
Func<Exception, bool> catchExceptionOpt,
CancellationToken cancellationToken)
{
Debug.Assert(_frozen == 0);
Debug.Assert((exceptionHolderOpt != null) == (catchExceptionOpt != null));
foreach (var executor in precedingExecutors)
// Each executor points to a <Factory> method of the Submission class.
// The method creates an instance of the Submission class passing the submission states to its constructor.
// The consturctor initializes the links between submissions and stores the Submission instance to
// a slot in submission states that corresponds to the submission.
// The <Factory> method then calls the <Initialize> method that consists of top-level script code statements.
int executorIndex = 0;
try
{
while (executorIndex < precedingExecutors.Length)
{
cancellationToken.ThrowIfCancellationRequested();
EnsureStateCapacity();
try
{
await precedingExecutors[executorIndex++](_submissionStates).ConfigureAwait(continueOnCapturedContext: false);
}
finally
{
// The submission constructor always runs into completion (unless we emitted bad code).
// We need to advance the counter to reflect the updates to submission states done in the constructor.
AdvanceStateCounter();
}
}
cancellationToken.ThrowIfCancellationRequested();
TResult result;
EnsureStateCapacity();
await executor(_submissionStates).ConfigureAwait(continueOnCapturedContext: false);
AdvanceStateCounter();
try
{
executorIndex++;
result = await ((Task<TResult>)currentExecutor(_submissionStates)).ConfigureAwait(continueOnCapturedContext: false);
}
finally
{
// The submission constructor always runs into completion (unless we emitted bad code).
// We need to advance the counter to reflect the updates to submission states done in the constructor.
AdvanceStateCounter();
}
return result;
}
catch (Exception exception) when (catchExceptionOpt?.Invoke(exception) == true)
{
// The following code creates instances of all submissions without executing the user code.
// The constructors don't contain any user code.
var submissionCtorArgs = new object[] { null };
while (executorIndex < precedingExecutors.Length)
{
EnsureStateCapacity();
cancellationToken.ThrowIfCancellationRequested();
// update the value since the array might have been resized:
submissionCtorArgs[0] = _submissionStates;
EnsureStateCapacity();
TResult result = await ((Task<TResult>)currentExecutor(_submissionStates)).ConfigureAwait(continueOnCapturedContext: false);
AdvanceStateCounter();
Activator.CreateInstance(precedingExecutors[executorIndex++].GetMethodInfo().DeclaringType, submissionCtorArgs);
AdvanceStateCounter();
}
return result;
if (executorIndex == precedingExecutors.Length)
{
EnsureStateCapacity();
// update the value since the array might have been resized:
submissionCtorArgs[0] = _submissionStates;
Activator.CreateInstance(currentExecutor.GetMethodInfo().DeclaringType, submissionCtorArgs);
AdvanceStateCounter();
}
exceptionHolderOpt.Value = exception;
return default(TResult);
}
}
private void EnsureStateCapacity()
......
......@@ -20,18 +20,28 @@ public abstract class ScriptState
/// </summary>
public Script Script { get; }
/// <summary>
/// Caught exception originating from the script top-level code.
/// </summary>
/// <remarks>
/// Exceptions are only caught and stored here if the API returning the <see cref="ScriptState"/> is instructed to do so.
/// By default they are propagated to the caller of the API.
/// </remarks>
public Exception Exception { get; }
internal ScriptExecutionState ExecutionState { get; }
private ImmutableArray<ScriptVariable> _lazyVariables;
private IReadOnlyDictionary<string, int> _lazyVariableMap;
internal ScriptState(ScriptExecutionState executionState, Script script)
internal ScriptState(ScriptExecutionState executionState, Script script, Exception exceptionOpt)
{
Debug.Assert(executionState != null);
Debug.Assert(script != null);
ExecutionState = executionState;
Script = script;
Exception = exceptionOpt;
}
/// <summary>
......@@ -39,7 +49,7 @@ internal ScriptState(ScriptExecutionState executionState, Script script)
/// </summary>
public object ReturnValue => GetReturnValue();
internal abstract object GetReturnValue();
/// <summary>
/// Returns variables defined by the scripts in the declaration order.
/// </summary>
......@@ -117,15 +127,53 @@ private ImmutableArray<ScriptVariable> CreateVariables()
return _lazyVariableMap;
}
public Task<ScriptState<object>> ContinueWithAsync(string code, ScriptOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
{
return ContinueWithAsync<object>(code, options, cancellationToken);
}
/// <summary>
/// Continues script execution from the state represented by this instance by running the specified code snippet.
/// </summary>
/// <param name="code">The code to be executed.</param>
/// <param name="options">Options.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="ScriptState"/> that represents the state after running <paramref name="code"/>, including all declared variables and return value.</returns>
public Task<ScriptState<object>> ContinueWithAsync(string code, ScriptOptions options, CancellationToken cancellationToken)
=> ContinueWithAsync<object>(code, options, null, cancellationToken);
public Task<ScriptState<TResult>> ContinueWithAsync<TResult>(string code, ScriptOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
{
return Script.ContinueWith<TResult>(code, options).RunFromAsync(this, cancellationToken);
}
/// <summary>
/// Continues script execution from the state represented by this instance by running the specified code snippet.
/// </summary>
/// <param name="code">The code to be executed.</param>
/// <param name="options">Options.</param>
/// <param name="catchException">
/// If specified, any exception thrown by the script top-level code is passed to <paramref name="catchException"/>.
/// If it returns true the exception is caught and stored on the resulting <see cref="ScriptState"/>, otherwise the exception is propagated to the caller.
/// </param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="ScriptState"/> that represents the state after running <paramref name="code"/>, including all declared variables, return value and caught exception (if applicable).</returns>
public Task<ScriptState<object>> ContinueWithAsync(string code, ScriptOptions options = null, Func<Exception, bool> catchException = null, CancellationToken cancellationToken = default(CancellationToken))
=> Script.ContinueWith<object>(code, options).RunFromAsync(this, catchException, cancellationToken);
/// <summary>
/// Continues script execution from the state represented by this instance by running the specified code snippet.
/// </summary>
/// <param name="code">The code to be executed.</param>
/// <param name="options">Options.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="ScriptState"/> that represents the state after running <paramref name="code"/>, including all declared variables and return value.</returns>
public Task<ScriptState<TResult>> ContinueWithAsync<TResult>(string code, ScriptOptions options, CancellationToken cancellationToken)
=> ContinueWithAsync<TResult>(code, options, null, cancellationToken);
/// <summary>
/// Continues script execution from the state represented by this instance by running the specified code snippet.
/// </summary>
/// <param name="code">The code to be executed.</param>
/// <param name="options">Options.</param>
/// <param name="catchException">
/// If specified, any exception thrown by the script top-level code is passed to <paramref name="catchException"/>.
/// If it returns true the exception is caught and stored on the resulting <see cref="ScriptState"/>, otherwise the exception is propagated to the caller.
/// </param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="ScriptState"/> that represents the state after running <paramref name="code"/>, including all declared variables, return value and caught exception (if applicable).</returns>
public Task<ScriptState<TResult>> ContinueWithAsync<TResult>(string code, ScriptOptions options = null, Func<Exception, bool> catchException = null, CancellationToken cancellationToken = default(CancellationToken))
=> Script.ContinueWith<TResult>(code, options).RunFromAsync(this, catchException, cancellationToken);
// How do we resolve overloads? We should use the language semantics.
// https://github.com/dotnet/roslyn/issues/3720
......@@ -219,8 +267,8 @@ public sealed class ScriptState<T> : ScriptState
public new T ReturnValue { get; }
internal override object GetReturnValue() => ReturnValue;
internal ScriptState(ScriptExecutionState executionState, T value, Script script)
: base(executionState, script)
internal ScriptState(ScriptExecutionState executionState, Script script, T value, Exception exceptionOpt)
: base(executionState, script, exceptionOpt)
{
ReturnValue = value;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册