diff --git a/src/Simulation/Common/SimulatorBase.cs b/src/Simulation/Common/SimulatorBase.cs index 66999f33bac..1440e2112db 100644 --- a/src/Simulation/Common/SimulatorBase.cs +++ b/src/Simulation/Common/SimulatorBase.cs @@ -8,6 +8,8 @@ using Microsoft.Quantum.Simulation.Core; +#nullable enable + namespace Microsoft.Quantum.Simulation.Common { /// @@ -19,16 +21,17 @@ namespace Microsoft.Quantum.Simulation.Common /// public abstract class SimulatorBase : AbstractFactory, IOperationFactory { - public event Action OnOperationStart = null; - public event Action OnOperationEnd = null; - public event Action OnFail = null; - public event Action OnAllocateQubits = null; - public event Action> OnReleaseQubits = null; - public event Action OnBorrowQubits = null; - public event Action> OnReturnQubits = null; - public event Action OnLog = null; - - public IQubitManager QubitManager { get; } + public event Action? OnOperationStart = null; + public event Action? OnOperationEnd = null; + public event Action? OnFail = null; + public event Action? OnAllocateQubits = null; + public event Action>? OnReleaseQubits = null; + public event Action? OnBorrowQubits = null; + public event Action>? OnReturnQubits = null; + public event Action? OnLog = null; + public event Action>? OnException = null; + + public IQubitManager? QubitManager { get; } public abstract string Name { get; } @@ -42,15 +45,16 @@ public abstract class SimulatorBase : AbstractFactory, IOperat /// Only Q# operations are tracked and reported in the stack trace. Q# functions or calls from /// classical hosts like C# or Python are not included. /// - public StackFrame[] CallStack { get; private set; } + public StackFrame[]? CallStack { get; private set; } - public SimulatorBase(IQubitManager qubitManager = null) + public SimulatorBase(IQubitManager? qubitManager = null) { this.QubitManager = qubitManager; this.InitBuiltinOperations(this.GetType()); EnableLogToConsole(); + EnableExceptionPrinting(); if (this.QubitManager != null) { @@ -92,7 +96,7 @@ public override AbstractCallable CreateInstance(Type t) return new GenericCallable(this, t); } - AbstractCallable result = null; + AbstractCallable? result = null; t = t.GetNativeImplementation() ?? t; @@ -110,6 +114,19 @@ public override AbstractCallable CreateInstance(Type t) return result; } + protected void WriteStackTraceToLog(Exception exception, IEnumerable callStack) + { + OnLog?.Invoke($"Unhandled exception. {exception.GetType().FullName}: {exception.Message}"); + var first = true; + foreach (var sf in callStack) + { + var msg = (first ? " ---> " : " at ") + sf.ToStringWithBestSourceLocation(); + OnLog?.Invoke(msg); + first = false; + } + OnLog?.Invoke(""); + } + public virtual O Execute(I args) where T : AbstractCallable, ICallable { StackTraceCollector stackTraceCollector = new StackTraceCollector(this); @@ -124,16 +141,7 @@ public virtual O Execute(I args) where T : AbstractCallable, ICallable catch (Exception e) // Dumps q# call-stack in case of exception if CallStack tracking was enabled { this.CallStack = stackTraceCollector.CallStack; - OnLog?.Invoke($"Unhandled exception. {e.GetType().FullName}: {e.Message}"); - bool first = true; - foreach (StackFrame sf in this.CallStack) - { - var msg = (first ? " ---> " : " at ") + sf.ToStringWithBestSourceLocation(); - OnLog?.Invoke(msg); - first = false; - } - OnLog?.Invoke(""); - + this.OnException?.Invoke(e, this.CallStack); throw; } finally @@ -165,6 +173,25 @@ public void DisableLogToConsole() OnLog -= Console.WriteLine; } + /// + /// Adds an event to the OnException event that logs stack traces + /// as plain text via the OnLog event (e.g.: for console output). + /// + public void EnableExceptionPrinting() + { + OnException += WriteStackTraceToLog; + } + + + /// + /// Disables default handling of stack traces, such that stack + /// traces are not written to the OnLog event handler. + /// + public void DisableExceptionPrinting() + { + OnException -= WriteStackTraceToLog; + } + /// /// Verifies that the Qubit can be used as part of an operation. /// @@ -210,7 +237,7 @@ public void CheckQubits(IQArray qubits, string arrayName) public class Allocate : Intrinsic.Allocate { private SimulatorBase sim; - private IQubitManager manager; + private IQubitManager? manager; public Allocate(SimulatorBase m) : base(m) { diff --git a/src/Simulation/Common/StackTrace.cs b/src/Simulation/Common/StackTrace.cs index 335c13e6b4b..c0840d02100 100644 --- a/src/Simulation/Common/StackTrace.cs +++ b/src/Simulation/Common/StackTrace.cs @@ -89,6 +89,22 @@ public override string ToString() return string.Format(messageFormat, Callable.FullName, $"{SourceFile}:line {FailedLineNumber}"); } + /// + /// Gets the best possible source location for this stack frame. + /// If the source is not available on local machine, the source + // location will be replaced by a URL pointing to GitHub repository. + /// + /// + /// This is more costly than because it + /// checks if source file exists on disk. + /// If the file does not exist it calls to get the URL + /// which is also more costly than . + /// + public virtual string GetBestSourceLocation() => + System.IO.File.Exists(SourceFile) + ? SourceFile + : GetURLFromPDB() ?? SourceFile; + /// /// The same as , but tries to point to best source location. /// If the source is not available on local machine, source location will be replaced @@ -97,26 +113,8 @@ public override string ToString() /// If the file does not exist it calls to get the URL /// which is also more costly than . /// - public virtual string ToStringWithBestSourceLocation() - { - string message = ToString(); - if (System.IO.File.Exists(SourceFile)) - { - return message; - } - else - { - string url = GetURLFromPDB(); - if (url == null) - { - return message; - } - else - { - return string.Format(messageFormat, Callable.FullName, url); - } - } - } + public virtual string ToStringWithBestSourceLocation() => + string.Format(messageFormat, Callable.FullName, $"{GetBestSourceLocation()}:line {FailedLineNumber}"); /// /// Finds correspondence between Q# and C# stack frames and populates Q# stack frame information from C# stack frames