This repository was archived by the owner on Jan 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 90
Implements #70: Provide Q# call stack information with source locations in case Q# program fails #79
Merged
Merged
Implements #70: Provide Q# call stack information with source locations in case Q# program fails #79
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
c5c87ef
adding initial version of StackTrace collector with tests
vadym-kl 07b8ff5
adding more tests
vadym-kl db21e9f
adding placeholders; work in progress
vadym-kl 3af694e
work in progress
vadym-kl 24350c5
wip
vadym-kl 4cb1647
added PDB source extraction supoort
vadym-kl 40f7916
extending tests; fixing url issues
vadym-kl f098a43
adding portablePdbreader
vadym-kl 28ea1b8
fixing code generation tests to have base one line numbers
vadym-kl 1701ba1
incorporating feedback
vadym-kl 6408fe2
refactoring
vadym-kl 013bea6
refactoring
vadym-kl 12edebc
refactoring
vadym-kl 973cea5
Merge branch 'master' into vadym-kl/70-qsharp-call-stack
vadym-kl 7d37053
Merge branch 'master' into vadym-kl/70-qsharp-call-stack
vadym-kl 3a6bccb
Merge branch 'master' into vadym-kl/70-qsharp-call-stack
vadym-kl 52101ee
Merge branch 'master' into vadym-kl/70-qsharp-call-stack
vadym-kl 852b4ec
Merge branch 'master' into vadym-kl/70-qsharp-call-stack
bettinaheim ac0bfa2
addressing review; part1
vadym-kl 96839dd
addressing reviewer feedback; part 2
vadym-kl 9960314
addressing reviewer comments; part3
vadym-kl ebff325
Merge branch 'master' into vadym-kl/70-qsharp-call-stack
vadym-kl 0e4d977
Merge branch 'master' into vadym-kl/70-qsharp-call-stack
bettinaheim d77eac4
Always enable stack trace. Some extra tests. Minor clean up.
anpaz 6e5b18e
Merge remote-tracking branch 'origin/master' into vadym-kl/70-qsharp-…
anpaz cad26d9
fixing out of bound issue when Q# and C# stack traces are merged
vadym-kl 11a9fbd
Merge branch 'master' into vadym-kl/70-qsharp-call-stack
vadym-kl f722105
Update src/Simulation/Common/PortablePDBReader.cs
anpaz b39f5a6
Merge branch 'vadym-kl/70-qsharp-call-stack' of https://github.com/mi…
anpaz e01d0d8
Minor renames.
anpaz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,299 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using Newtonsoft.Json; | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Text; | ||
| using Microsoft.Quantum.Simulation.Core; | ||
| using System.Diagnostics; | ||
| using System.Linq; | ||
|
|
||
| namespace Microsoft.Quantum.Simulation.Common | ||
| { | ||
| /// <summary> | ||
| /// Stores information about Q# stack frames. During successful execution keeps track only of Callable and Argument. | ||
| /// When the exception happens, the rest of the information is populated by <see cref="PopulateSourceLocations(Stack{StackFrame}, System.Diagnostics.StackFrame[])"/> | ||
| /// method. | ||
| /// </summary> | ||
| [Serializable] | ||
| public class StackFrame | ||
| { | ||
| /// <summary> | ||
| /// Callable corresponding to the stack frame | ||
| /// </summary> | ||
| public ICallable Callable { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// Arguments passed to the callable in the stack frame | ||
| /// </summary> | ||
| public IApplyData Argument { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// The path to the source where operation is defined | ||
| /// </summary> | ||
| public string SourceFile { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// One based line number in the operation that resulted in failure. Note that for automatically derived Adjoint and Controlled | ||
| /// variants of the operation, the line always points to the operation declaration | ||
| /// </summary> | ||
| public int FailedLineNumber { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// One based line number where the declaration starts. | ||
| /// </summary> | ||
| public int DeclarationStartLineNumber { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// One based line number of the first line after the declaration. | ||
| /// The value -1, if the declaration ends on the last line of the file. | ||
| /// </summary> | ||
| public int DeclarationEndLineNumber { get; private set; } | ||
|
|
||
| public StackFrame(ICallable callable, IApplyData argument) | ||
| { | ||
| Callable = callable; | ||
| Argument = argument; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Uses PortablePDBs and SourceLink to get the source of failed operation. | ||
| /// </summary> | ||
| public string GetOperationSourceFromPDB() | ||
| { | ||
| string pdbFileLocation = PortablePdbSymbolReader.GetPDBLocation(Callable); | ||
| return PortablePDBEmbeddedFilesCache.GetEmbeddedFileRange( | ||
| pdbFileLocation, | ||
| SourceFile, | ||
| DeclarationStartLineNumber, | ||
| DeclarationEndLineNumber, | ||
| showLineNumbers: true, markedLine: FailedLineNumber); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Uses PortablePDBs and SourceLink to get URL for file and line number. | ||
| /// </summary> | ||
| /// <returns></returns> | ||
| public string GetURLFromPDB() | ||
| { | ||
| string pdbFileLocation = PortablePdbSymbolReader.GetPDBLocation(Callable); | ||
| string result = PortablePDBSourceLinkInfoCache.TryGetFileUrl(pdbFileLocation, SourceFile); | ||
| return PortablePdbSymbolReader.TryFormatGitHubUrl(result, FailedLineNumber); | ||
| } | ||
|
|
||
| private const string messageFormat = "{0} on {1}"; | ||
|
|
||
| public override string ToString() | ||
| { | ||
| return string.Format(messageFormat, Callable.FullName, $"{SourceFile}:line {FailedLineNumber}"); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// The same as <see cref="ToString"/>, but tries to point to best source location. | ||
| /// If the source is not available on local machine, source location will be replaced | ||
| /// by URL pointing to GitHub repository. | ||
| /// This is more costly than <see cref="ToString"/> because it checks if source file exists on disk. | ||
| /// If the file does not exist it calls <see cref="GetURLFromPDB"/> to get the URL | ||
| /// which is also more costly than <see cref="ToString"/>. | ||
| /// </summary> | ||
| 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); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Finds correspondence between Q# and C# stack frames and populates Q# stack frame information from C# stack frames | ||
| /// </summary> | ||
| public static StackFrame[] PopulateSourceLocations(Stack<StackFrame> qsharpCallStack, System.Diagnostics.StackFrame[] csharpCallStack) | ||
| { | ||
| foreach (StackFrame currentFrame in qsharpCallStack) | ||
| { | ||
| ICallable op = currentFrame.Callable.UnwrapCallable(); | ||
| object[] locations = op.GetType().GetCustomAttributes(typeof(SourceLocationAttribute), true); | ||
| foreach (object location in locations) | ||
| { | ||
| SourceLocationAttribute sourceLocation = (location as SourceLocationAttribute); | ||
| if (sourceLocation != null && sourceLocation.SpecializationKind == op.Variant) | ||
| { | ||
| currentFrame.SourceFile = System.IO.Path.GetFullPath(sourceLocation.SourceFile); | ||
| currentFrame.DeclarationStartLineNumber = sourceLocation.StartLine; | ||
| currentFrame.DeclarationEndLineNumber = sourceLocation.EndLine; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| StackFrame[] qsharpStackFrames = qsharpCallStack.ToArray(); | ||
| int qsharpStackFrameId = 0; | ||
| for (int csharpStackFrameId = 0; csharpStackFrameId < csharpCallStack.Length; ++csharpStackFrameId) | ||
| { | ||
| string fileName = csharpCallStack[csharpStackFrameId].GetFileName(); | ||
| if (fileName != null) | ||
| { | ||
| fileName = System.IO.Path.GetFullPath(fileName); | ||
| int failedLineNumber = csharpCallStack[csharpStackFrameId].GetFileLineNumber(); | ||
| StackFrame currentQsharpStackFrame = qsharpStackFrames[qsharpStackFrameId]; | ||
| if (fileName == currentQsharpStackFrame.SourceFile && | ||
| currentQsharpStackFrame.DeclarationStartLineNumber <= failedLineNumber && | ||
| ( | ||
| (failedLineNumber < currentQsharpStackFrame.DeclarationEndLineNumber) || | ||
| (currentQsharpStackFrame.DeclarationEndLineNumber == -1) | ||
| ) | ||
| ) | ||
| { | ||
| currentQsharpStackFrame.FailedLineNumber = failedLineNumber; | ||
| qsharpStackFrameId++; | ||
| if (qsharpStackFrameId == qsharpStackFrames.Length) break; | ||
| } | ||
| } | ||
| } | ||
| return qsharpStackFrames; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Tracks Q# operations call-stack till the first failure resulting in <see cref="SimulatorBase.OnFail"/> | ||
| /// event invocation. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// 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. | ||
| /// </remarks> | ||
| public class StackTraceCollector : IDisposable | ||
| { | ||
| private readonly Stack<StackFrame> callStack; | ||
| private readonly SimulatorBase sim; | ||
| private System.Diagnostics.StackFrame[] frames = null; | ||
| StackFrame[] stackFramesWithLocations = null; | ||
| bool hasFailed = false; | ||
|
|
||
| public StackTraceCollector(SimulatorBase sim) | ||
| { | ||
| callStack = new Stack<StackFrame>(); | ||
| this.sim = sim; | ||
|
|
||
| this.Start(); | ||
| } | ||
|
|
||
| private void Start() | ||
| { | ||
| sim.OnOperationStart += this.OnOperationStart; | ||
| sim.OnOperationEnd += this.OnOperationEnd; | ||
| sim.OnFail += this.OnFail; | ||
| } | ||
|
|
||
| private void Stop() | ||
| { | ||
| sim.OnOperationStart -= this.OnOperationStart; | ||
| sim.OnOperationEnd -= this.OnOperationEnd; | ||
| sim.OnFail -= this.OnFail; | ||
| } | ||
|
|
||
| void OnOperationStart(ICallable callable, IApplyData arg) | ||
| { | ||
| if (!hasFailed) | ||
| { | ||
| callStack.Push(new StackFrame(callable, arg)); | ||
| } | ||
| } | ||
|
|
||
| void OnOperationEnd(ICallable callable, IApplyData arg) | ||
| { | ||
| if (!hasFailed) | ||
| { | ||
| callStack.Pop(); | ||
| } | ||
| } | ||
|
|
||
| void OnFail(System.Runtime.ExceptionServices.ExceptionDispatchInfo exceptionInfo) | ||
| { | ||
| if (!hasFailed) | ||
| { | ||
| hasFailed = true; | ||
| } | ||
|
|
||
| System.Diagnostics.StackTrace stackTrace = new System.Diagnostics.StackTrace(exceptionInfo.SourceException, 0, true); | ||
| System.Diagnostics.StackFrame[] currentFrames = stackTrace.GetFrames(); | ||
|
|
||
| if (frames == null) | ||
| { | ||
| frames = currentFrames; | ||
| } | ||
| else | ||
| { | ||
| // When the exception is thrown, OnFail can be called multiple times. | ||
| // With every next call we see bigger part of the call stack, so we save the biggest call stack | ||
| if (currentFrames.Length > frames.Length) | ||
| { | ||
| Debug.Assert((frames.Length == 0) || (frames[0].ToString() == currentFrames[0].ToString())); | ||
| frames = currentFrames; | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
|
|
||
| /// <summary> | ||
| /// If failure has happened returns the call-stack at time of failure. | ||
| /// Returns null if the failure has not happened. | ||
| /// </summary> | ||
| public StackFrame[] CallStack | ||
| { | ||
| get | ||
| { | ||
| if (hasFailed) | ||
| { | ||
| if( stackFramesWithLocations == null ) | ||
| { | ||
| stackFramesWithLocations = StackFrame.PopulateSourceLocations(callStack, frames); | ||
| } | ||
| return stackFramesWithLocations; | ||
| } | ||
| else | ||
| { | ||
| return null; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #region IDisposable Support | ||
| private bool disposedValue = false; // To detect redundant calls | ||
|
|
||
| protected virtual void Dispose(bool disposing) | ||
| { | ||
| if (!disposedValue) | ||
| { | ||
| if (disposing) | ||
| { | ||
| this.Stop(); | ||
| } | ||
|
|
||
| disposedValue = true; | ||
| } | ||
| } | ||
|
|
||
| // This code added to correctly implement the disposable pattern. | ||
| public void Dispose() | ||
| { | ||
| // Do not change this code. Put cleanup code in Dispose(bool disposing) above. | ||
| Dispose(true); | ||
| } | ||
| #endregion | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.