From c5c87ef21d39f79df4a14de13937c6dc21c12bbe Mon Sep 17 00:00:00 2001 From: Vadym Kliuchnikov Date: Sun, 27 Oct 2019 16:04:53 -0700 Subject: [PATCH 01/20] adding initial version of StackTrace collector with tests --- src/Simulation/Common/StackTrace.cs | 153 ++++++++++++++++++ src/Simulation/Core/Generics/Adjoint.cs | 3 +- src/Simulation/Core/Generics/Controlled.cs | 3 +- .../Core/Generics/GenericPartial.cs | 3 +- src/Simulation/Core/Operations/Adjoint.cs | 3 +- src/Simulation/Core/Operations/Controlled.cs | 3 +- src/Simulation/Core/Operations/Operation.cs | 10 +- .../Core/Operations/OperationPartial.cs | 4 +- .../Simulators.Tests/Circuits/Fail.qs | 33 +++- .../Simulators.Tests/StackTraceTests.cs | 95 +++++++++++ 10 files changed, 301 insertions(+), 9 deletions(-) create mode 100644 src/Simulation/Common/StackTrace.cs create mode 100644 src/Simulation/Simulators.Tests/StackTraceTests.cs diff --git a/src/Simulation/Common/StackTrace.cs b/src/Simulation/Common/StackTrace.cs new file mode 100644 index 00000000000..648a5d574c5 --- /dev/null +++ b/src/Simulation/Common/StackTrace.cs @@ -0,0 +1,153 @@ +// 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; + +namespace Microsoft.Quantum.Simulation.Common +{ + [Serializable] + public class StackFrame + { + public readonly ICallable operation; + public readonly IApplyData argument; + + /// + /// The path to the source where operation is defined + /// + public string sourceFile; + + /// + /// Line 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 + /// + public int failedLineNumber; + + public int declarationStartLineNumber; + public int declarationEndLineNumber; + + public StackFrame(ICallable _operation, IApplyData _argument) + { + operation = _operation; + argument = _argument; + sourceFile = null; + failedLineNumber = -1; + declarationStartLineNumber = -1; + declarationEndLineNumber = -1; + } + } + + public class StackTraceCollector + { + private readonly Stack callStack; + private System.Diagnostics.StackFrame[] frames = null; + bool hasNotFailed = true; + + public StackTraceCollector(SimulatorBase sim) + { + sim.OnOperationStart += OnOperationStart; + sim.OnOperationEnd += OnOperationEnd; + sim.OnFail += OnFail; + callStack = new Stack(); + } + + void OnOperationStart(ICallable callable, IApplyData arg) + { + callStack.Push(new StackFrame(callable, arg)); + } + + void OnOperationEnd(ICallable callable, IApplyData arg) + { + if (hasNotFailed) + { + callStack.Pop(); + } + } + + void OnFail(System.Runtime.ExceptionServices.ExceptionDispatchInfo exceptionInfo) + { + if (hasNotFailed) + { + hasNotFailed = false; + } + + 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 mutiple 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) + { + frames = currentFrames; + } + } + } + + public static ICallable UnwrapCallable(ICallable op) + { + ICallable res = op; + while (res as IWrappedOperation != null) + { + res = (res as IWrappedOperation).BaseOperation; + } + return res; + } + + static void PopulateSourceLocations(Stack qsharpCallStack, System.Diagnostics.StackFrame[] csharpCallStack) + { + List> qsharpSourceLocations = new List>(); + foreach (System.Diagnostics.StackFrame csStackFrame in csharpCallStack) + { + string fileName = csStackFrame.GetFileName(); + if (System.IO.Path.GetExtension(fileName) == ".qs") + { + qsharpSourceLocations.Add(new Tuple(fileName, csStackFrame.GetFileLineNumber())); + } + } + + StackFrame[] stackFrames = qsharpCallStack.ToArray(); + for (int i = 0; i < stackFrames.Length; ++i) + { + ICallable op = UnwrapCallable(stackFrames[i].operation); + 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) + { + stackFrames[i].sourceFile = System.IO.Path.GetFullPath(sourceLocation.SourceFile); + stackFrames[i].declarationStartLineNumber = sourceLocation.StartLine; + stackFrames[i].declarationEndLineNumber = sourceLocation.EndLine; + + string fileName = System.IO.Path.GetFullPath(qsharpSourceLocations[i].Item1); + int failedLineNumber = qsharpSourceLocations[i].Item2; + if (fileName == stackFrames[i].sourceFile && + sourceLocation.StartLine <= failedLineNumber && + ((failedLineNumber <= sourceLocation.EndLine) || sourceLocation.EndLine == -1)) + { + stackFrames[i].failedLineNumber = failedLineNumber; + } + } + } + } + } + + public Stack CallStack + { + get + { + PopulateSourceLocations(callStack, frames); + return callStack; + } + } + } +} diff --git a/src/Simulation/Core/Generics/Adjoint.cs b/src/Simulation/Core/Generics/Adjoint.cs index b00116bb7c8..524df1feda1 100644 --- a/src/Simulation/Core/Generics/Adjoint.cs +++ b/src/Simulation/Core/Generics/Adjoint.cs @@ -23,7 +23,7 @@ public interface IAdjointable : ICallable /// input Type is not resolved until it gets Applied at runtime. /// [DebuggerTypeProxy(typeof(GenericAdjoint.DebuggerProxy))] - public class GenericAdjoint : GenericCallable, IApplyData + public class GenericAdjoint : GenericCallable, IApplyData, IWrappedOperation { public GenericAdjoint(GenericCallable baseOp) : base(baseOp.Factory, null) { @@ -31,6 +31,7 @@ public GenericAdjoint(GenericCallable baseOp) : base(baseOp.Factory, null) } public GenericCallable BaseOp { get; } + ICallable IWrappedOperation.BaseOperation => BaseOp; IEnumerable IApplyData.Qubits => ((IApplyData)this.BaseOp)?.Qubits; diff --git a/src/Simulation/Core/Generics/Controlled.cs b/src/Simulation/Core/Generics/Controlled.cs index 65d5235c0bb..15f57783dfa 100644 --- a/src/Simulation/Core/Generics/Controlled.cs +++ b/src/Simulation/Core/Generics/Controlled.cs @@ -25,7 +25,7 @@ public partial interface IControllable : ICallable /// input Type is not resolved until it gets Applied at runtime. /// [DebuggerTypeProxy(typeof(GenericControlled.DebuggerProxy))] - public class GenericControlled : GenericCallable, IApplyData + public class GenericControlled : GenericCallable, IApplyData, IWrappedOperation { public GenericControlled(GenericCallable baseOp) : base(baseOp.Factory, null) { @@ -33,6 +33,7 @@ public GenericControlled(GenericCallable baseOp) : base(baseOp.Factory, null) } public GenericCallable BaseOp { get; } + ICallable IWrappedOperation.BaseOperation => BaseOp; IEnumerable IApplyData.Qubits => ((IApplyData)this.BaseOp)?.Qubits; diff --git a/src/Simulation/Core/Generics/GenericPartial.cs b/src/Simulation/Core/Generics/GenericPartial.cs index 295502d7c8f..3d8408d8ceb 100644 --- a/src/Simulation/Core/Generics/GenericPartial.cs +++ b/src/Simulation/Core/Generics/GenericPartial.cs @@ -14,7 +14,7 @@ namespace Microsoft.Quantum.Simulation.Core /// input Type is not resolved until it gets Applied at runtime. /// [DebuggerTypeProxy(typeof(GenericPartial.DebuggerProxy))] - public class GenericPartial : GenericCallable, IApplyData + public class GenericPartial : GenericCallable, IApplyData, IWrappedOperation { private Lazy __qubits = null; @@ -29,6 +29,7 @@ public GenericPartial(GenericCallable baseOp, object partialValues) : base(baseO } public GenericCallable BaseOp { get; } + ICallable IWrappedOperation.BaseOperation => BaseOp; public override string Name => this.BaseOp.Name; public override string FullName => this.BaseOp.FullName; diff --git a/src/Simulation/Core/Operations/Adjoint.cs b/src/Simulation/Core/Operations/Adjoint.cs index 428858f4c66..20bb62fc5fc 100644 --- a/src/Simulation/Core/Operations/Adjoint.cs +++ b/src/Simulation/Core/Operations/Adjoint.cs @@ -39,7 +39,7 @@ public Adjointable(IOperationFactory m) : base(m) /// Class used to represents an operation that has been adjointed. /// [DebuggerTypeProxy(typeof(AdjointedOperation<,>.DebuggerProxy))] - public class AdjointedOperation : Unitary, IApplyData, ICallable + public class AdjointedOperation : Unitary, IApplyData, ICallable, IWrappedOperation { public AdjointedOperation(Operation op) : base(op.Factory) { @@ -50,6 +50,7 @@ public AdjointedOperation(Operation op) : base(op.Factory) } public Operation BaseOp { get; } + ICallable IWrappedOperation.BaseOperation => BaseOp; public override void Init() { } diff --git a/src/Simulation/Core/Operations/Controlled.cs b/src/Simulation/Core/Operations/Controlled.cs index 2c9f1e183cc..1f7be787f66 100644 --- a/src/Simulation/Core/Operations/Controlled.cs +++ b/src/Simulation/Core/Operations/Controlled.cs @@ -38,7 +38,7 @@ public Controllable(IOperationFactory m) : base(m) { } /// This class is used to represents an operation that has been controlled. /// [DebuggerTypeProxy(typeof(ControlledOperation<,>.DebuggerProxy))] - public class ControlledOperation : Unitary<(IQArray, I)>, IApplyData, ICallable + public class ControlledOperation : Unitary<(IQArray, I)>, IApplyData, ICallable, IWrappedOperation { public class In : IApplyData { @@ -66,6 +66,7 @@ public ControlledOperation(Operation op) : base(op.Factory) } public Operation BaseOp { get; } + ICallable IWrappedOperation.BaseOperation => BaseOp; public override void Init() { } diff --git a/src/Simulation/Core/Operations/Operation.cs b/src/Simulation/Core/Operations/Operation.cs index 7963654f389..2349b738c88 100644 --- a/src/Simulation/Core/Operations/Operation.cs +++ b/src/Simulation/Core/Operations/Operation.cs @@ -17,7 +17,15 @@ public partial interface ICallable : ICallable ICallable Partial

(Func mapper); } - + ///

+ /// An operation that wrapps another operation, for example + /// , , + /// , + /// + public interface IWrappedOperation + { + ICallable BaseOperation { get; } + } /// /// The base class for all ClosedType quantum operations. diff --git a/src/Simulation/Core/Operations/OperationPartial.cs b/src/Simulation/Core/Operations/OperationPartial.cs index 4b059962132..46b2407b616 100644 --- a/src/Simulation/Core/Operations/OperationPartial.cs +++ b/src/Simulation/Core/Operations/OperationPartial.cs @@ -17,7 +17,7 @@ namespace Microsoft.Quantum.Simulation.Core /// Optionally it can receive a Mapper to do the same. /// [DebuggerTypeProxy(typeof(OperationPartial<,,>.DebuggerProxy))] - public class OperationPartial : Operation, IUnitary

+ public class OperationPartial : Operation, IUnitary

, IWrappedOperation { private Lazy __qubits = null; @@ -59,6 +59,7 @@ public OperationPartial(Operation op, object partialTuple) : base(op.Facto public override void Init() { } public Operation BaseOp { get; } + ICallable IWrappedOperation.BaseOperation => BaseOp; public Func Mapper { get; } @@ -137,6 +138,7 @@ ICallable ICallable.Partial(Func mapper) IUnitary

IUnitary

.Adjoint => base.Adjoint; IUnitary<(IQArray, P)> IUnitary

.Controlled => base.Controlled; + IUnitary IUnitary

.Partial(Func mapper) => new OperationPartial(this, mapper); public override string ToString() => $"{this.BaseOp}{{_}}"; diff --git a/src/Simulation/Simulators.Tests/Circuits/Fail.qs b/src/Simulation/Simulators.Tests/Circuits/Fail.qs index 3fb9302255c..0188a3ca41f 100644 --- a/src/Simulation/Simulators.Tests/Circuits/Fail.qs +++ b/src/Simulation/Simulators.Tests/Circuits/Fail.qs @@ -3,10 +3,39 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { - operation AlwaysFail() : Unit { + operation AlwaysFail() : Unit is Adj + Ctl { fail "Always fail"; } -} + operation AlwaysFail1() : Unit is Adj + Ctl{ + AlwaysFail(); + } + operation AlwaysFail2() : Unit is Adj + Ctl { + Controlled AlwaysFail1(new Qubit[0],()); + } + operation AlwaysFail3() : Unit is Adj + Ctl { + Adjoint AlwaysFail2(); + } + operation AlwaysFail4() : Unit is Adj + Ctl { + Adjoint AlwaysFail3(); + } + operation GenericFail<'T,'U>( a : 'T, b : 'U ) : Unit is Adj + Ctl { + AlwaysFail(); + } + + operation GenericFail1() : Unit is Adj + Ctl { + GenericFail(5,6); + } + + operation PartialFail( a : Int, b : Int ) : Unit is Adj + Ctl { + AlwaysFail(); + } + + operation PartialFail1() : Unit is Adj + Ctl { + let op = PartialFail(0,_); + op(2); + } + +} \ No newline at end of file diff --git a/src/Simulation/Simulators.Tests/StackTraceTests.cs b/src/Simulation/Simulators.Tests/StackTraceTests.cs new file mode 100644 index 00000000000..4e3d6850b80 --- /dev/null +++ b/src/Simulation/Simulators.Tests/StackTraceTests.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Xunit; + +using System; +using System.Threading.Tasks; + +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Common; +using Microsoft.Quantum.Simulation.Simulators.Tests.Circuits; +using Microsoft.Quantum.Simulation.Simulators.Exceptions; +using Xunit.Abstractions; + +namespace Microsoft.Quantum.Simulation.Simulators.Tests +{ + public class StackTraceTests + { + const string namespacePrefix = "Microsoft.Quantum.Simulation.Simulators.Tests.Circuits."; + + private readonly ITestOutputHelper output; + public StackTraceTests(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void AlwaysFail4Test() + { + ToffoliSimulator sim = new ToffoliSimulator(); + StackTraceCollector sc = new StackTraceCollector(sim); + ICallable op = sim.Get(); + try + { + QVoid res = op.Apply(QVoid.Instance); + } + catch (ExecutionFailException) + { + StackFrame[] stackFrames = sc.CallStack.ToArray(); + + Assert.Equal(5, stackFrames.Length); + + Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].operation.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail1", stackFrames[1].operation.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail2", stackFrames[2].operation.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail3", stackFrames[3].operation.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail4", stackFrames[4].operation.FullName); + + Assert.Equal(OperationFunctor.Controlled, stackFrames[0].operation.Variant); + Assert.Equal(OperationFunctor.Controlled, stackFrames[1].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); + Assert.Equal(OperationFunctor.Adjoint, stackFrames[3].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[4].operation.Variant); + + Assert.Equal(14, stackFrames[2].failedLineNumber); + Assert.Equal(21, stackFrames[4].failedLineNumber); + + // For Adjoint and Controlled we expect failedLineNumber to be equal to declarationStartLineNumber + Assert.Equal(stackFrames[0].declarationStartLineNumber, stackFrames[0].failedLineNumber); + Assert.Equal(stackFrames[1].declarationStartLineNumber, stackFrames[1].failedLineNumber); + Assert.Equal(stackFrames[3].declarationStartLineNumber, stackFrames[3].failedLineNumber); + } + } + + [Fact] + public void GenericFail1Test() + { + ToffoliSimulator sim = new ToffoliSimulator(); + StackTraceCollector sc = new StackTraceCollector(sim); + ICallable op = sim.Get(); + try + { + QVoid res = op.Apply(QVoid.Instance); + } + catch (ExecutionFailException) + { + StackFrame[] stackFrames = sc.CallStack.ToArray(); + + Assert.Equal(3, stackFrames.Length); + + Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].operation.FullName); + Assert.Equal(namespacePrefix + "GenericFail", stackFrames[1].operation.FullName); + Assert.Equal(namespacePrefix + "GenericFail1", stackFrames[2].operation.FullName); + + Assert.Equal(OperationFunctor.Body, stackFrames[0].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[1].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); + + Assert.Equal(7, stackFrames[0].failedLineNumber); + Assert.Equal(25, stackFrames[1].failedLineNumber); + Assert.Equal(29, stackFrames[2].failedLineNumber); + } + } + } +} \ No newline at end of file From 07b8ff567a67198ab247c47f24931f04857ab342 Mon Sep 17 00:00:00 2001 From: Vadym Kliuchnikov Date: Sun, 27 Oct 2019 21:00:18 -0700 Subject: [PATCH 02/20] adding more tests --- .../Simulators.Tests/Circuits/Fail.qs | 17 ++ .../Simulators.Tests/StackTraceTests.cs | 176 ++++++++++++++++-- 2 files changed, 177 insertions(+), 16 deletions(-) diff --git a/src/Simulation/Simulators.Tests/Circuits/Fail.qs b/src/Simulation/Simulators.Tests/Circuits/Fail.qs index 0188a3ca41f..5ecd7552afd 100644 --- a/src/Simulation/Simulators.Tests/Circuits/Fail.qs +++ b/src/Simulation/Simulators.Tests/Circuits/Fail.qs @@ -38,4 +38,21 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { op(2); } + operation PartialAdjFail1() : Unit is Adj + Ctl { + let op = PartialFail(0,_); + Adjoint op(2); + } + + operation PartialCtlFail1() : Unit is Adj + Ctl { + let op = PartialFail(0,_); + Controlled op(new Qubit[0], 2); + } + + operation GenericAdjFail1() : Unit is Adj + Ctl { + Adjoint GenericFail(5,6); + } + + operation GenericCtlFail1() : Unit is Adj + Ctl { + Controlled GenericFail( new Qubit[0], (5,6)); + } } \ No newline at end of file diff --git a/src/Simulation/Simulators.Tests/StackTraceTests.cs b/src/Simulation/Simulators.Tests/StackTraceTests.cs index 4e3d6850b80..851bae4e0e7 100644 --- a/src/Simulation/Simulators.Tests/StackTraceTests.cs +++ b/src/Simulation/Simulators.Tests/StackTraceTests.cs @@ -66,29 +66,173 @@ public void AlwaysFail4Test() public void GenericFail1Test() { ToffoliSimulator sim = new ToffoliSimulator(); - StackTraceCollector sc = new StackTraceCollector(sim); - ICallable op = sim.Get(); - try + { - QVoid res = op.Apply(QVoid.Instance); + StackTraceCollector sc = new StackTraceCollector(sim); + ICallable op = sim.Get(); + try + { + QVoid res = op.Apply(QVoid.Instance); + } + catch (ExecutionFailException) + { + StackFrame[] stackFrames = sc.CallStack.ToArray(); + + Assert.Equal(3, stackFrames.Length); + + Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].operation.FullName); + Assert.Equal(namespacePrefix + "GenericFail", stackFrames[1].operation.FullName); + Assert.Equal(namespacePrefix + "GenericFail1", stackFrames[2].operation.FullName); + + Assert.Equal(OperationFunctor.Body, stackFrames[0].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[1].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); + + Assert.Equal(7, stackFrames[0].failedLineNumber); + Assert.Equal(25, stackFrames[1].failedLineNumber); + Assert.Equal(29, stackFrames[2].failedLineNumber); + } } - catch (ExecutionFailException) + { - StackFrame[] stackFrames = sc.CallStack.ToArray(); + StackTraceCollector sc = new StackTraceCollector(sim); + ICallable op = sim.Get(); + try + { + QVoid res = op.Apply(QVoid.Instance); + } + catch (ExecutionFailException) + { + StackFrame[] stackFrames = sc.CallStack.ToArray(); + + Assert.Equal(3, stackFrames.Length); + + Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].operation.FullName); + Assert.Equal(namespacePrefix + "GenericFail", stackFrames[1].operation.FullName); + Assert.Equal(namespacePrefix + "GenericAdjFail1", stackFrames[2].operation.FullName); + + Assert.Equal(OperationFunctor.Adjoint, stackFrames[0].operation.Variant); + Assert.Equal(OperationFunctor.Adjoint, stackFrames[1].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); + + Assert.Equal(5, stackFrames[0].failedLineNumber); + Assert.Equal(23, stackFrames[1].failedLineNumber); + Assert.Equal(52, stackFrames[2].failedLineNumber); + } + } + + { + StackTraceCollector sc = new StackTraceCollector(sim); + ICallable op = sim.Get(); + try + { + QVoid res = op.Apply(QVoid.Instance); + } + catch (ExecutionFailException) + { + StackFrame[] stackFrames = sc.CallStack.ToArray(); + + Assert.Equal(3, stackFrames.Length); + + Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].operation.FullName); + Assert.Equal(namespacePrefix + "GenericFail", stackFrames[1].operation.FullName); + Assert.Equal(namespacePrefix + "GenericCtlFail1", stackFrames[2].operation.FullName); + + Assert.Equal(OperationFunctor.Controlled, stackFrames[0].operation.Variant); + Assert.Equal(OperationFunctor.Controlled, stackFrames[1].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); + + Assert.Equal(5, stackFrames[0].failedLineNumber); + Assert.Equal(23, stackFrames[1].failedLineNumber); + Assert.Equal(56, stackFrames[2].failedLineNumber); + } + } + } - Assert.Equal(3, stackFrames.Length); + [Fact] + public void PartialFail1Test() + { + ToffoliSimulator sim = new ToffoliSimulator(); - Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].operation.FullName); - Assert.Equal(namespacePrefix + "GenericFail", stackFrames[1].operation.FullName); - Assert.Equal(namespacePrefix + "GenericFail1", stackFrames[2].operation.FullName); + { + StackTraceCollector sc = new StackTraceCollector(sim); + ICallable op = sim.Get(); + try + { + QVoid res = op.Apply(QVoid.Instance); + } + catch (ExecutionFailException) + { + StackFrame[] stackFrames = sc.CallStack.ToArray(); + + Assert.Equal(3, stackFrames.Length); + + Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].operation.FullName); + Assert.Equal(namespacePrefix + "PartialFail", stackFrames[1].operation.FullName); + Assert.Equal(namespacePrefix + "PartialFail1", stackFrames[2].operation.FullName); + + Assert.Equal(OperationFunctor.Body, stackFrames[0].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[1].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); + + Assert.Equal(7, stackFrames[0].failedLineNumber); + Assert.Equal(33, stackFrames[1].failedLineNumber); + Assert.Equal(38, stackFrames[2].failedLineNumber); + } + } - Assert.Equal(OperationFunctor.Body, stackFrames[0].operation.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[1].operation.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); + { + StackTraceCollector sc = new StackTraceCollector(sim); + ICallable op = sim.Get(); + try + { + QVoid res = op.Apply(QVoid.Instance); + } + catch (ExecutionFailException) + { + StackFrame[] stackFrames = sc.CallStack.ToArray(); + + Assert.Equal(3, stackFrames.Length); + + Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].operation.FullName); + Assert.Equal(namespacePrefix + "PartialFail", stackFrames[1].operation.FullName); + Assert.Equal(namespacePrefix + "PartialAdjFail1", stackFrames[2].operation.FullName); + + Assert.Equal(OperationFunctor.Adjoint, stackFrames[0].operation.Variant); + Assert.Equal(OperationFunctor.Adjoint, stackFrames[1].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); + + Assert.Equal(5, stackFrames[0].failedLineNumber); + Assert.Equal(31, stackFrames[1].failedLineNumber); + Assert.Equal(43, stackFrames[2].failedLineNumber); + } + } - Assert.Equal(7, stackFrames[0].failedLineNumber); - Assert.Equal(25, stackFrames[1].failedLineNumber); - Assert.Equal(29, stackFrames[2].failedLineNumber); + { + StackTraceCollector sc = new StackTraceCollector(sim); + ICallable op = sim.Get(); + try + { + QVoid res = op.Apply(QVoid.Instance); + } + catch (ExecutionFailException) + { + StackFrame[] stackFrames = sc.CallStack.ToArray(); + + Assert.Equal(3, stackFrames.Length); + + Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].operation.FullName); + Assert.Equal(namespacePrefix + "PartialFail", stackFrames[1].operation.FullName); + Assert.Equal(namespacePrefix + "PartialCtlFail1", stackFrames[2].operation.FullName); + + Assert.Equal(OperationFunctor.Controlled, stackFrames[0].operation.Variant); + Assert.Equal(OperationFunctor.Controlled, stackFrames[1].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); + + Assert.Equal(5, stackFrames[0].failedLineNumber); + Assert.Equal(31, stackFrames[1].failedLineNumber); + Assert.Equal(48, stackFrames[2].failedLineNumber); + } } } } From db21e9fcaf337de97be8092a555664d916649ea5 Mon Sep 17 00:00:00 2001 From: Vadym Kliuchnikov Date: Sun, 27 Oct 2019 21:14:10 -0700 Subject: [PATCH 03/20] adding placeholders; work in progress --- src/Simulation/Common/StackTrace.cs | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/Simulation/Common/StackTrace.cs b/src/Simulation/Common/StackTrace.cs index 648a5d574c5..6e4f2f5d5d8 100644 --- a/src/Simulation/Common/StackTrace.cs +++ b/src/Simulation/Common/StackTrace.cs @@ -38,6 +38,40 @@ public StackFrame(ICallable _operation, IApplyData _argument) declarationStartLineNumber = -1; declarationEndLineNumber = -1; } + + public string GetOperationSourceFromPDB() + { + string result = null; + try + { + //TODO: + } + catch ( Exception ) + { + + } + return result; + } + + public string GetURLFromPDB() + { + string result = null; + try + { + //TODO: + } + catch (Exception) + { + + } + return result; + } + + public override string ToString() + { + //TODO: + return base.ToString(); + } } public class StackTraceCollector From 3af694e9d6679a4124e9923f1eae2ed0c77f94b6 Mon Sep 17 00:00:00 2001 From: Vadym Date: Mon, 28 Oct 2019 17:28:45 -0700 Subject: [PATCH 04/20] work in progress --- src/Simulation/Common/StackTrace.cs | 31 +++++++++++++------ src/Simulation/Core/Operations/Operation.cs | 2 +- .../Simulators.Tests/Circuits/Fail.qs | 2 +- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/Simulation/Common/StackTrace.cs b/src/Simulation/Common/StackTrace.cs index 6e4f2f5d5d8..c32c5f7296f 100644 --- a/src/Simulation/Common/StackTrace.cs +++ b/src/Simulation/Common/StackTrace.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Text; using Microsoft.Quantum.Simulation.Core; +using System.Diagnostics; namespace Microsoft.Quantum.Simulation.Common { @@ -21,12 +22,20 @@ public class StackFrame public string sourceFile; ///

- /// Line in the operation that resulted in failure. Note that for automatically derived Adjoint and Controlled + /// 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 /// public int failedLineNumber; + /// + /// One based line number where the declaration starts. + /// public int declarationStartLineNumber; + + /// + /// 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. + /// public int declarationEndLineNumber; public StackFrame(ICallable _operation, IApplyData _argument) @@ -46,7 +55,7 @@ public string GetOperationSourceFromPDB() { //TODO: } - catch ( Exception ) + catch (Exception) { } @@ -78,13 +87,13 @@ public class StackTraceCollector { private readonly Stack callStack; private System.Diagnostics.StackFrame[] frames = null; - bool hasNotFailed = true; + bool hasFailed = false; public StackTraceCollector(SimulatorBase sim) { - sim.OnOperationStart += OnOperationStart; - sim.OnOperationEnd += OnOperationEnd; - sim.OnFail += OnFail; + sim.OnOperationStart += this.OnOperationStart; + sim.OnOperationEnd += this.OnOperationEnd; + sim.OnFail += this.OnFail; callStack = new Stack(); } @@ -95,7 +104,7 @@ void OnOperationStart(ICallable callable, IApplyData arg) void OnOperationEnd(ICallable callable, IApplyData arg) { - if (hasNotFailed) + if (!hasFailed) { callStack.Pop(); } @@ -103,9 +112,9 @@ void OnOperationEnd(ICallable callable, IApplyData arg) void OnFail(System.Runtime.ExceptionServices.ExceptionDispatchInfo exceptionInfo) { - if (hasNotFailed) + if (!hasFailed) { - hasNotFailed = false; + hasFailed = true; } System.Diagnostics.StackTrace stackTrace = new System.Diagnostics.StackTrace(exceptionInfo.SourceException, 0, true); @@ -117,10 +126,11 @@ void OnFail(System.Runtime.ExceptionServices.ExceptionDispatchInfo exceptionInfo } else { - // When the exception is thrown, OnFail can be called mutiple times. + // 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; } } @@ -138,6 +148,7 @@ public static ICallable UnwrapCallable(ICallable op) static void PopulateSourceLocations(Stack qsharpCallStack, System.Diagnostics.StackFrame[] csharpCallStack) { + // TODO: change logic to check if given location in the known Q# call-stack locations List> qsharpSourceLocations = new List>(); foreach (System.Diagnostics.StackFrame csStackFrame in csharpCallStack) { diff --git a/src/Simulation/Core/Operations/Operation.cs b/src/Simulation/Core/Operations/Operation.cs index 2349b738c88..c654dc6b299 100644 --- a/src/Simulation/Core/Operations/Operation.cs +++ b/src/Simulation/Core/Operations/Operation.cs @@ -18,7 +18,7 @@ public partial interface ICallable : ICallable } /// - /// An operation that wrapps another operation, for example + /// An operation that wraps another operation, for example /// , , /// , /// diff --git a/src/Simulation/Simulators.Tests/Circuits/Fail.qs b/src/Simulation/Simulators.Tests/Circuits/Fail.qs index 5ecd7552afd..834ef2f4af9 100644 --- a/src/Simulation/Simulators.Tests/Circuits/Fail.qs +++ b/src/Simulation/Simulators.Tests/Circuits/Fail.qs @@ -3,7 +3,7 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { - operation AlwaysFail() : Unit is Adj + Ctl { + function AlwaysFail() : Unit { fail "Always fail"; } From 24350c5f5c84d1026f57aa73865707118a06fff0 Mon Sep 17 00:00:00 2001 From: Vadym Kliuchnikov Date: Mon, 28 Oct 2019 21:55:05 -0700 Subject: [PATCH 05/20] wip --- src/Simulation/Common/StackTrace.cs | 112 ++++++++++++++++++++++------ 1 file changed, 90 insertions(+), 22 deletions(-) diff --git a/src/Simulation/Common/StackTrace.cs b/src/Simulation/Common/StackTrace.cs index c32c5f7296f..5eead854b85 100644 --- a/src/Simulation/Common/StackTrace.cs +++ b/src/Simulation/Common/StackTrace.cs @@ -7,6 +7,7 @@ using System.Text; using Microsoft.Quantum.Simulation.Core; using System.Diagnostics; +using System.Linq; namespace Microsoft.Quantum.Simulation.Common { @@ -78,8 +79,8 @@ public string GetURLFromPDB() public override string ToString() { - //TODO: - return base.ToString(); + string location = GetURLFromPDB() ?? $"{sourceFile}:line {failedLineNumber}"; + return $"in {operation.FullName} on {location}"; } } @@ -146,40 +147,107 @@ public static ICallable UnwrapCallable(ICallable op) return res; } - static void PopulateSourceLocations(Stack qsharpCallStack, System.Diagnostics.StackFrame[] csharpCallStack) + class HalfOpenInterval : IEquatable { - // TODO: change logic to check if given location in the known Q# call-stack locations - List> qsharpSourceLocations = new List>(); - foreach (System.Diagnostics.StackFrame csStackFrame in csharpCallStack) + public readonly int start; + public readonly int end; + + public HalfOpenInterval(int start, int end) + { + if( start > end ) throw new ArgumentException($"Interval start:{start} must be less or equal to end:{end}."); + this.start = start; + this.end = end; + } + + public override bool Equals(object obj) + { + return Equals(obj as HalfOpenInterval); + } + + public bool Equals(HalfOpenInterval other) + { + return other != null && + start == other.start && + end == other.end; + } + + public override int GetHashCode() + { + return HashCode.Combine(start, end); + } + } + + class NonOverlappingHalfOpenIntervalSet + { + class NonOverlappinIntervalsComparator : IComparer { - string fileName = csStackFrame.GetFileName(); - if (System.IO.Path.GetExtension(fileName) == ".qs") + public int Compare(HalfOpenInterval x, HalfOpenInterval y) { - qsharpSourceLocations.Add(new Tuple(fileName, csStackFrame.GetFileLineNumber())); + if (x.end <= y.start) return -1; + if (x.start >= y.end) return 1; + if (x.start == y.start && x.end == y.end) return 0; + throw new ArgumentException("Compared intervals must be non-overlapping"); } } - StackFrame[] stackFrames = qsharpCallStack.ToArray(); - for (int i = 0; i < stackFrames.Length; ++i) + public NonOverlappingHalfOpenIntervalSet(IEnumerable inervals) + { + throw new NotImplementedException(); + } + + public bool Contains(int value) + { + throw new NotImplementedException(); + } + } + + static void PopulateSourceLocations(Stack qsharpCallStack, System.Diagnostics.StackFrame[] csharpCallStack) + { + // TODO: change logic to check if given location in the known Q# call-stack locations + //List> qsharpSourceLocations = new List>(); + //foreach (System.Diagnostics.StackFrame csStackFrame in csharpCallStack) + //{ + // string fileName = csStackFrame.GetFileName(); + // if (System.IO.Path.GetExtension(fileName) == ".qs") + // { + // qsharpSourceLocations.Add(new Tuple(fileName, csStackFrame.GetFileLineNumber())); + // } + //} + + //string fileName = System.IO.Path.GetFullPath(qsharpSourceLocations[i].Item1); + //int failedLineNumber = qsharpSourceLocations[i].Item2; + //if (fileName == stackFrames[i].sourceFile && + // sourceLocation.StartLine <= failedLineNumber && + // ((failedLineNumber <= sourceLocation.EndLine) || sourceLocation.EndLine == -1)) + //{ + // stackFrames[i].failedLineNumber = failedLineNumber; + //} + + Dictionary> sourceLocations = new Dictionary>(); + foreach (StackFrame currentFrame in qsharpCallStack ) { - ICallable op = UnwrapCallable(stackFrames[i].operation); + ICallable op = UnwrapCallable(currentFrame.operation); 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) { - stackFrames[i].sourceFile = System.IO.Path.GetFullPath(sourceLocation.SourceFile); - stackFrames[i].declarationStartLineNumber = sourceLocation.StartLine; - stackFrames[i].declarationEndLineNumber = sourceLocation.EndLine; - - string fileName = System.IO.Path.GetFullPath(qsharpSourceLocations[i].Item1); - int failedLineNumber = qsharpSourceLocations[i].Item2; - if (fileName == stackFrames[i].sourceFile && - sourceLocation.StartLine <= failedLineNumber && - ((failedLineNumber <= sourceLocation.EndLine) || sourceLocation.EndLine == -1)) + currentFrame.sourceFile = System.IO.Path.GetFullPath(sourceLocation.SourceFile); + currentFrame.declarationStartLineNumber = sourceLocation.StartLine + 1; // note that attribute has base 0 line numbers + currentFrame.declarationEndLineNumber = sourceLocation.EndLine == -1 ? -1 : sourceLocation.EndLine + 1; + + List intervals = sourceLocations.GetValueOrDefault(currentFrame.sourceFile); + HalfOpenInterval currentRange = new HalfOpenInterval( + currentFrame.declarationStartLineNumber, + currentFrame.declarationEndLineNumber == -1 ? int.MaxValue : currentFrame.declarationEndLineNumber); + if (intervals == null) + { + sourceLocations.Add(currentFrame.sourceFile, new List(Enumerable.Repeat(currentRange,1)); + } + else { - stackFrames[i].failedLineNumber = failedLineNumber; + intervals.Add(currentRange); } } } From 4cb1647ae6a80ef45e10678b8bd261d997243694 Mon Sep 17 00:00:00 2001 From: Vadym Date: Tue, 29 Oct 2019 16:20:47 -0700 Subject: [PATCH 06/20] added PDB source extraction supoort --- ...Microsoft.Quantum.Simulation.Common.csproj | 4 + src/Simulation/Common/StackTrace.cs | 165 +++++------------- src/Simulation/Core/TypeExtensions.cs | 10 ++ .../CsharpGeneration/SimulationCode.fs | 6 +- .../Simulators.Tests/Circuits/Fail.qs | 23 ++- .../Simulators.Tests/StackTraceTests.cs | 88 ++++++++-- ...osoft.Quantum.Simulation.Simulators.csproj | 1 + 7 files changed, 154 insertions(+), 143 deletions(-) diff --git a/src/Simulation/Common/Microsoft.Quantum.Simulation.Common.csproj b/src/Simulation/Common/Microsoft.Quantum.Simulation.Common.csproj index 369209ad295..54886046904 100644 --- a/src/Simulation/Common/Microsoft.Quantum.Simulation.Common.csproj +++ b/src/Simulation/Common/Microsoft.Quantum.Simulation.Common.csproj @@ -8,6 +8,10 @@ x64 + + + + diff --git a/src/Simulation/Common/StackTrace.cs b/src/Simulation/Common/StackTrace.cs index 5eead854b85..e1ecf2fa605 100644 --- a/src/Simulation/Common/StackTrace.cs +++ b/src/Simulation/Common/StackTrace.cs @@ -39,6 +39,8 @@ public class StackFrame /// public int declarationEndLineNumber; + private readonly Lazy pdbFileLocation; + public StackFrame(ICallable _operation, IApplyData _argument) { operation = _operation; @@ -47,34 +49,23 @@ public StackFrame(ICallable _operation, IApplyData _argument) failedLineNumber = -1; declarationStartLineNumber = -1; declarationEndLineNumber = -1; + pdbFileLocation = new Lazy(() => PortablePdbSymbolReader.GetPDBLocation(operation)); } public string GetOperationSourceFromPDB() { - string result = null; - try - { - //TODO: - } - catch (Exception) - { - - } - return result; + return PortablePDBEmbeddedFilesCache.GetEmbeddedFileRange( + pdbFileLocation.Value, + sourceFile, + declarationStartLineNumber, + declarationEndLineNumber, + showLineNumbers: true, markedLine: failedLineNumber); } public string GetURLFromPDB() { - string result = null; - try - { - //TODO: - } - catch (Exception) - { - - } - return result; + string result = PortablePDBPathRemappingCache.TryGetFileUrl(pdbFileLocation.Value, sourceFile); + return PortablePdbSymbolReader.TryFormatGitHubUrl(result, failedLineNumber); } public override string ToString() @@ -137,96 +128,13 @@ void OnFail(System.Runtime.ExceptionServices.ExceptionDispatchInfo exceptionInfo } } - public static ICallable UnwrapCallable(ICallable op) - { - ICallable res = op; - while (res as IWrappedOperation != null) - { - res = (res as IWrappedOperation).BaseOperation; - } - return res; - } - - class HalfOpenInterval : IEquatable - { - public readonly int start; - public readonly int end; - - public HalfOpenInterval(int start, int end) - { - if( start > end ) throw new ArgumentException($"Interval start:{start} must be less or equal to end:{end}."); - this.start = start; - this.end = end; - } - - public override bool Equals(object obj) - { - return Equals(obj as HalfOpenInterval); - } - - public bool Equals(HalfOpenInterval other) - { - return other != null && - start == other.start && - end == other.end; - } - - public override int GetHashCode() - { - return HashCode.Combine(start, end); - } - } - - class NonOverlappingHalfOpenIntervalSet - { - class NonOverlappinIntervalsComparator : IComparer - { - public int Compare(HalfOpenInterval x, HalfOpenInterval y) - { - if (x.end <= y.start) return -1; - if (x.start >= y.end) return 1; - if (x.start == y.start && x.end == y.end) return 0; - throw new ArgumentException("Compared intervals must be non-overlapping"); - } - } - public NonOverlappingHalfOpenIntervalSet(IEnumerable inervals) - { - throw new NotImplementedException(); - } - public bool Contains(int value) - { - throw new NotImplementedException(); - } - } - - static void PopulateSourceLocations(Stack qsharpCallStack, System.Diagnostics.StackFrame[] csharpCallStack) + static StackFrame[] PopulateSourceLocations(Stack qsharpCallStack, System.Diagnostics.StackFrame[] csharpCallStack) { - // TODO: change logic to check if given location in the known Q# call-stack locations - //List> qsharpSourceLocations = new List>(); - //foreach (System.Diagnostics.StackFrame csStackFrame in csharpCallStack) - //{ - // string fileName = csStackFrame.GetFileName(); - // if (System.IO.Path.GetExtension(fileName) == ".qs") - // { - // qsharpSourceLocations.Add(new Tuple(fileName, csStackFrame.GetFileLineNumber())); - // } - //} - - //string fileName = System.IO.Path.GetFullPath(qsharpSourceLocations[i].Item1); - //int failedLineNumber = qsharpSourceLocations[i].Item2; - //if (fileName == stackFrames[i].sourceFile && - // sourceLocation.StartLine <= failedLineNumber && - // ((failedLineNumber <= sourceLocation.EndLine) || sourceLocation.EndLine == -1)) - //{ - // stackFrames[i].failedLineNumber = failedLineNumber; - //} - - Dictionary> sourceLocations = new Dictionary>(); - foreach (StackFrame currentFrame in qsharpCallStack ) + foreach (StackFrame currentFrame in qsharpCallStack) { - ICallable op = UnwrapCallable(currentFrame.operation); + ICallable op = currentFrame.operation.UnwrapCallable(); object[] locations = op.GetType().GetCustomAttributes(typeof(SourceLocationAttribute), true); foreach (object location in locations) { @@ -234,32 +142,43 @@ static void PopulateSourceLocations(Stack qsharpCallStack, System.Di if (sourceLocation != null && sourceLocation.SpecializationKind == op.Variant) { currentFrame.sourceFile = System.IO.Path.GetFullPath(sourceLocation.SourceFile); - currentFrame.declarationStartLineNumber = sourceLocation.StartLine + 1; // note that attribute has base 0 line numbers - currentFrame.declarationEndLineNumber = sourceLocation.EndLine == -1 ? -1 : sourceLocation.EndLine + 1; + currentFrame.declarationStartLineNumber = sourceLocation.StartLine; // note that attribute has base 0 line numbers + currentFrame.declarationEndLineNumber = sourceLocation.EndLine; + } + } + } - List intervals = sourceLocations.GetValueOrDefault(currentFrame.sourceFile); - HalfOpenInterval currentRange = new HalfOpenInterval( - currentFrame.declarationStartLineNumber, - currentFrame.declarationEndLineNumber == -1 ? int.MaxValue : currentFrame.declarationEndLineNumber); - if (intervals == null) - { - sourceLocations.Add(currentFrame.sourceFile, new List(Enumerable.Repeat(currentRange,1)); - } - else - { - intervals.Add(currentRange); - } + 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++; } } } + return qsharpStackFrames; } - public Stack CallStack + public StackFrame[] CallStack { get { - PopulateSourceLocations(callStack, frames); - return callStack; + return PopulateSourceLocations(callStack, frames); } } } diff --git a/src/Simulation/Core/TypeExtensions.cs b/src/Simulation/Core/TypeExtensions.cs index 9822b37f373..5bfb38c72eb 100644 --- a/src/Simulation/Core/TypeExtensions.cs +++ b/src/Simulation/Core/TypeExtensions.cs @@ -368,5 +368,15 @@ public static string QSharpType(this Type t) return t.Name; } + + public static ICallable UnwrapCallable(this ICallable op) + { + ICallable res = op; + while (res as IWrappedOperation != null) + { + res = (res as IWrappedOperation).BaseOperation; + } + return res; + } } } diff --git a/src/Simulation/CsharpGeneration/SimulationCode.fs b/src/Simulation/CsharpGeneration/SimulationCode.fs index 5fe363851b7..21a5bb9f0de 100644 --- a/src/Simulation/CsharpGeneration/SimulationCode.fs +++ b/src/Simulation/CsharpGeneration/SimulationCode.fs @@ -347,7 +347,7 @@ module SimulationCode = ``#line`` ln n s | Some n, None -> startLine |> function | Some ln -> - ``#line`` ln n s + ``#line`` (ln + 1) n s | None -> s | _ -> s @@ -1020,12 +1020,12 @@ module SimulationCode = | QsControlledAdjoint -> "ControlledAdjoint" let body = (buildSpecializationBody context sp) let attribute = - let startLine = fst sp.Location.Offset + let startLine = fst sp.Location.Offset + 1 let endLine = match context.declarationPositions.TryGetValue sp.SourceFile with | true, startPositions -> let index = startPositions.IndexOf sp.Location.Offset - if index + 1 >= startPositions.Count then -1 else fst startPositions.[index + 1] + if index + 1 >= startPositions.Count then -1 else fst startPositions.[index + 1] + 1 //TODO: diagnostics. | false, _ -> startLine ``attribute`` None (ident "SourceLocation") [ diff --git a/src/Simulation/Simulators.Tests/Circuits/Fail.qs b/src/Simulation/Simulators.Tests/Circuits/Fail.qs index 834ef2f4af9..4d20fdea39c 100644 --- a/src/Simulation/Simulators.Tests/Circuits/Fail.qs +++ b/src/Simulation/Simulators.Tests/Circuits/Fail.qs @@ -3,8 +3,8 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { - function AlwaysFail() : Unit { - fail "Always fail"; + operation AlwaysFail() : Unit is Adj + Ctl{ + Fail(); } operation AlwaysFail1() : Unit is Adj + Ctl{ @@ -55,4 +55,23 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { operation GenericCtlFail1() : Unit is Adj + Ctl { Controlled GenericFail( new Qubit[0], (5,6)); } + + function Fail() : Unit { + fail "Always fail"; + } + + operation RecursionFail( a : Int) : Unit is Adj { + if ( a >= 1 ) + { + RecursionFail(a-1); + } + else + { + Fail(); + } + } + + operation RecursionFail1() : Unit { + RecursionFail(2); + } } \ No newline at end of file diff --git a/src/Simulation/Simulators.Tests/StackTraceTests.cs b/src/Simulation/Simulators.Tests/StackTraceTests.cs index 851bae4e0e7..3477f66244b 100644 --- a/src/Simulation/Simulators.Tests/StackTraceTests.cs +++ b/src/Simulation/Simulators.Tests/StackTraceTests.cs @@ -11,6 +11,7 @@ using Microsoft.Quantum.Simulation.Simulators.Tests.Circuits; using Microsoft.Quantum.Simulation.Simulators.Exceptions; using Xunit.Abstractions; +using System.Text; namespace Microsoft.Quantum.Simulation.Simulators.Tests { @@ -36,7 +37,7 @@ public void AlwaysFail4Test() } catch (ExecutionFailException) { - StackFrame[] stackFrames = sc.CallStack.ToArray(); + StackFrame[] stackFrames = sc.CallStack; Assert.Equal(5, stackFrames.Length); @@ -59,6 +60,27 @@ public void AlwaysFail4Test() Assert.Equal(stackFrames[0].declarationStartLineNumber, stackFrames[0].failedLineNumber); Assert.Equal(stackFrames[1].declarationStartLineNumber, stackFrames[1].failedLineNumber); Assert.Equal(stackFrames[3].declarationStartLineNumber, stackFrames[3].failedLineNumber); + + for (int i = 0; i < stackFrames.Length; ++i) + { + Assert.StartsWith(@"https://github.com/", stackFrames[i].GetURLFromPDB()); + Assert.EndsWith($"#L{stackFrames[i].failedLineNumber}", stackFrames[i].GetURLFromPDB()); + } + + StringBuilder builder = new StringBuilder(); + builder.Append("13 ".PadLeft(PortablePDBEmbeddedFilesCache.lineNumberPaddingWidth)); + builder.AppendLine(" operation AlwaysFail2() : Unit is Adj + Ctl {"); + builder.Append("14 ".PadLeft(PortablePDBEmbeddedFilesCache.lineNumberPaddingWidth) + PortablePDBEmbeddedFilesCache.lineMarkPrefix); + builder.AppendLine(" Controlled AlwaysFail1(new Qubit[0],());"); + builder.Append("15 ".PadLeft(PortablePDBEmbeddedFilesCache.lineNumberPaddingWidth)); + builder.AppendLine(" }"); + Assert.Equal(builder.ToString(), stackFrames[2].GetOperationSourceFromPDB()); + + for( int i = 0; i < stackFrames.Length; ++i ) + { + output.WriteLine($"operation:{stackFrames[i].operation.FullName}"); + output.WriteLine(stackFrames[i].GetOperationSourceFromPDB()); + } } } @@ -76,7 +98,7 @@ public void GenericFail1Test() } catch (ExecutionFailException) { - StackFrame[] stackFrames = sc.CallStack.ToArray(); + StackFrame[] stackFrames = sc.CallStack; Assert.Equal(3, stackFrames.Length); @@ -103,7 +125,7 @@ public void GenericFail1Test() } catch (ExecutionFailException) { - StackFrame[] stackFrames = sc.CallStack.ToArray(); + StackFrame[] stackFrames = sc.CallStack; Assert.Equal(3, stackFrames.Length); @@ -115,8 +137,8 @@ public void GenericFail1Test() Assert.Equal(OperationFunctor.Adjoint, stackFrames[1].operation.Variant); Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); - Assert.Equal(5, stackFrames[0].failedLineNumber); - Assert.Equal(23, stackFrames[1].failedLineNumber); + Assert.Equal(6, stackFrames[0].failedLineNumber); + Assert.Equal(24, stackFrames[1].failedLineNumber); Assert.Equal(52, stackFrames[2].failedLineNumber); } } @@ -130,7 +152,7 @@ public void GenericFail1Test() } catch (ExecutionFailException) { - StackFrame[] stackFrames = sc.CallStack.ToArray(); + StackFrame[] stackFrames = sc.CallStack; Assert.Equal(3, stackFrames.Length); @@ -142,8 +164,8 @@ public void GenericFail1Test() Assert.Equal(OperationFunctor.Controlled, stackFrames[1].operation.Variant); Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); - Assert.Equal(5, stackFrames[0].failedLineNumber); - Assert.Equal(23, stackFrames[1].failedLineNumber); + Assert.Equal(6, stackFrames[0].failedLineNumber); + Assert.Equal(24, stackFrames[1].failedLineNumber); Assert.Equal(56, stackFrames[2].failedLineNumber); } } @@ -163,7 +185,7 @@ public void PartialFail1Test() } catch (ExecutionFailException) { - StackFrame[] stackFrames = sc.CallStack.ToArray(); + StackFrame[] stackFrames = sc.CallStack; Assert.Equal(3, stackFrames.Length); @@ -190,7 +212,7 @@ public void PartialFail1Test() } catch (ExecutionFailException) { - StackFrame[] stackFrames = sc.CallStack.ToArray(); + StackFrame[] stackFrames = sc.CallStack; Assert.Equal(3, stackFrames.Length); @@ -202,8 +224,8 @@ public void PartialFail1Test() Assert.Equal(OperationFunctor.Adjoint, stackFrames[1].operation.Variant); Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); - Assert.Equal(5, stackFrames[0].failedLineNumber); - Assert.Equal(31, stackFrames[1].failedLineNumber); + Assert.Equal(6, stackFrames[0].failedLineNumber); + Assert.Equal(32, stackFrames[1].failedLineNumber); Assert.Equal(43, stackFrames[2].failedLineNumber); } } @@ -217,7 +239,7 @@ public void PartialFail1Test() } catch (ExecutionFailException) { - StackFrame[] stackFrames = sc.CallStack.ToArray(); + StackFrame[] stackFrames = sc.CallStack; Assert.Equal(3, stackFrames.Length); @@ -229,11 +251,47 @@ public void PartialFail1Test() Assert.Equal(OperationFunctor.Controlled, stackFrames[1].operation.Variant); Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); - Assert.Equal(5, stackFrames[0].failedLineNumber); - Assert.Equal(31, stackFrames[1].failedLineNumber); + Assert.Equal(6, stackFrames[0].failedLineNumber); + Assert.Equal(32, stackFrames[1].failedLineNumber); Assert.Equal(48, stackFrames[2].failedLineNumber); } } } + + [Fact] + public void RecursionFail1Test() + { + ToffoliSimulator sim = new ToffoliSimulator(); + + { + StackTraceCollector sc = new StackTraceCollector(sim); + ICallable op = sim.Get(); + try + { + QVoid res = op.Apply(QVoid.Instance); + } + catch (ExecutionFailException) + { + StackFrame[] stackFrames = sc.CallStack; + + Assert.Equal(4, stackFrames.Length); + + Assert.Equal(namespacePrefix + "RecursionFail", stackFrames[0].operation.FullName); + Assert.Equal(namespacePrefix + "RecursionFail", stackFrames[1].operation.FullName); + Assert.Equal(namespacePrefix + "RecursionFail", stackFrames[2].operation.FullName); + Assert.Equal(namespacePrefix + "RecursionFail1", stackFrames[3].operation.FullName); + + Assert.Equal(OperationFunctor.Body, stackFrames[0].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[1].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[3].operation.Variant); + + Assert.Equal(70, stackFrames[0].failedLineNumber); + Assert.Equal(66, stackFrames[1].failedLineNumber); + Assert.Equal(66, stackFrames[2].failedLineNumber); + Assert.Equal(75, stackFrames[3].failedLineNumber); + } + } + } } } \ No newline at end of file diff --git a/src/Simulation/Simulators.Tests/Tests.Microsoft.Quantum.Simulation.Simulators.csproj b/src/Simulation/Simulators.Tests/Tests.Microsoft.Quantum.Simulation.Simulators.csproj index 64d3e4bb24e..565987988c9 100644 --- a/src/Simulation/Simulators.Tests/Tests.Microsoft.Quantum.Simulation.Simulators.csproj +++ b/src/Simulation/Simulators.Tests/Tests.Microsoft.Quantum.Simulation.Simulators.csproj @@ -1,6 +1,7 @@  + netcoreapp3.0 From 40f7916b636becc7d611b9bae38ecd9920fc132e Mon Sep 17 00:00:00 2001 From: Vadym Date: Tue, 29 Oct 2019 18:33:52 -0700 Subject: [PATCH 07/20] extending tests; fixing url issues --- src/Simulation/Common/SimulatorBase.cs | 41 +++++++-- src/Simulation/Common/StackTrace.cs | 83 +++++++++++++++---- .../Simulators.Tests/StackTraceTests.cs | 17 ++++ 3 files changed, 120 insertions(+), 21 deletions(-) diff --git a/src/Simulation/Common/SimulatorBase.cs b/src/Simulation/Common/SimulatorBase.cs index 3bc17a26e4e..b0dc7dcba3d 100644 --- a/src/Simulation/Common/SimulatorBase.cs +++ b/src/Simulation/Common/SimulatorBase.cs @@ -98,14 +98,34 @@ public override AbstractCallable CreateInstance(Type t) return result; } + public virtual O RunSync(I args) where T : AbstractCallable, ICallable + { + O res = default(O); + var op = Get(); + try + { + res = op.Apply(args); + } + catch (Exception e) // Dumps q# call-stack in case of exception if CallStack tracking was enabled + { + StackFrame[] qsharpStackFrames = this.CallStack; + if (qsharpStackFrames != null) + { + OnLog?.Invoke($"Unhandled Exception: {e.GetType().FullName}: {e.Message}"); + foreach (StackFrame sf in qsharpStackFrames) + { + OnLog?.Invoke(sf.ToStringWithBestSourceLocation()); + } + } + OnLog?.Invoke(""); + throw; + } + return res; + } public virtual Task Run(I args) where T : AbstractCallable, ICallable { - return Task.Run(() => - { - var op = Get(); - return op.Apply(args); - }); + return Task.Run(() => RunSync(args)); } /// @@ -346,5 +366,16 @@ public virtual void Fail(System.Runtime.ExceptionServices.ExceptionDispatchInfo { OnFail?.Invoke(exceptionInfo); } + + #region Stack trace collection support + private StackTraceCollector stackTraceCollector = null; + + public void EnableStackTrace() + { + stackTraceCollector = new StackTraceCollector(this); + } + + public StackFrame[] CallStack => stackTraceCollector?.CallStack; + #endregion } } diff --git a/src/Simulation/Common/StackTrace.cs b/src/Simulation/Common/StackTrace.cs index e1ecf2fa605..0dacf7d98cd 100644 --- a/src/Simulation/Common/StackTrace.cs +++ b/src/Simulation/Common/StackTrace.cs @@ -26,7 +26,7 @@ public class StackFrame /// 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 /// - public int failedLineNumber; + public int failedLineNumber = -2; /// /// One based line number where the declaration starts. @@ -39,46 +39,81 @@ public class StackFrame /// public int declarationEndLineNumber; - private readonly Lazy pdbFileLocation; - public StackFrame(ICallable _operation, IApplyData _argument) { operation = _operation; argument = _argument; - sourceFile = null; - failedLineNumber = -1; - declarationStartLineNumber = -1; - declarationEndLineNumber = -1; - pdbFileLocation = new Lazy(() => PortablePdbSymbolReader.GetPDBLocation(operation)); } + /// + /// Uses PortablePDBs and SourceLink to get the source of failed operation. + /// public string GetOperationSourceFromPDB() { + string pdbFileLocation = PortablePdbSymbolReader.GetPDBLocation(operation); return PortablePDBEmbeddedFilesCache.GetEmbeddedFileRange( - pdbFileLocation.Value, + pdbFileLocation, sourceFile, declarationStartLineNumber, declarationEndLineNumber, showLineNumbers: true, markedLine: failedLineNumber); } + /// + /// Uses PortablePDBs and SourceLink to get URL for file and line number. + /// + /// public string GetURLFromPDB() { - string result = PortablePDBPathRemappingCache.TryGetFileUrl(pdbFileLocation.Value, sourceFile); + string pdbFileLocation = PortablePdbSymbolReader.GetPDBLocation(operation); + Tuple result = PortablePDBPathRemappingCache.TryGetFileUrl(pdbFileLocation, sourceFile); return PortablePdbSymbolReader.TryFormatGitHubUrl(result, failedLineNumber); } public override string ToString() { - string location = GetURLFromPDB() ?? $"{sourceFile}:line {failedLineNumber}"; - return $"in {operation.FullName} on {location}"; + return $"in {operation.FullName} on {sourceFile}:line {failedLineNumber}"; + } + + /// + /// 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 + /// by 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 ToStringWithBestSourceLocation() + { + string message = ToString(); + if (System.IO.File.Exists(sourceFile)) + { + return message; + } + else + { + string url = GetURLFromPDB(); + if (url == null) + { + return message; + } + else + { + return $"in {operation.FullName} on {url}"; + } + } } } + /// + /// Tracks Q# call-stack till the first failure resulting in + /// event invocation. + /// public class StackTraceCollector { private readonly Stack callStack; private System.Diagnostics.StackFrame[] frames = null; + StackFrame[] stackFramesWithLocations = null; bool hasFailed = false; public StackTraceCollector(SimulatorBase sim) @@ -91,7 +126,10 @@ public StackTraceCollector(SimulatorBase sim) void OnOperationStart(ICallable callable, IApplyData arg) { - callStack.Push(new StackFrame(callable, arg)); + if (!hasFailed) + { + callStack.Push(new StackFrame(callable, arg)); + } } void OnOperationEnd(ICallable callable, IApplyData arg) @@ -128,8 +166,6 @@ void OnFail(System.Runtime.ExceptionServices.ExceptionDispatchInfo exceptionInfo } } - - static StackFrame[] PopulateSourceLocations(Stack qsharpCallStack, System.Diagnostics.StackFrame[] csharpCallStack) { foreach (StackFrame currentFrame in qsharpCallStack) @@ -174,11 +210,26 @@ static StackFrame[] PopulateSourceLocations(Stack qsharpCallStack, S return qsharpStackFrames; } + /// + /// If failure has happened returns the call-stack at time of failure. + /// Returns null if the failure has not happened. + /// public StackFrame[] CallStack { get { - return PopulateSourceLocations(callStack, frames); + if (hasFailed) + { + if( stackFramesWithLocations == null ) + { + stackFramesWithLocations = PopulateSourceLocations(callStack, frames); + } + return stackFramesWithLocations; + } + else + { + return null; + } } } } diff --git a/src/Simulation/Simulators.Tests/StackTraceTests.cs b/src/Simulation/Simulators.Tests/StackTraceTests.cs index 3477f66244b..602b9fff854 100644 --- a/src/Simulation/Simulators.Tests/StackTraceTests.cs +++ b/src/Simulation/Simulators.Tests/StackTraceTests.cs @@ -293,5 +293,22 @@ public void RecursionFail1Test() } } } + + [Fact] + public void ErrorLogTest() + { + ToffoliSimulator sim = new ToffoliSimulator(); + sim.EnableStackTrace(); + StringBuilder stringBuilder = new StringBuilder(); + sim.OnLog += (msg) => stringBuilder.AppendLine(msg); + try + { + QVoid res = sim.RunSync(QVoid.Instance); + } + catch (ExecutionFailException) + { + } + output.WriteLine(stringBuilder.ToString()); + } } } \ No newline at end of file From f098a43f1954d63aeeb67f973a276ac8beaf8bbb Mon Sep 17 00:00:00 2001 From: Vadym Date: Tue, 29 Oct 2019 18:53:48 -0700 Subject: [PATCH 08/20] adding portablePdbreader --- src/Simulation/Common/PortablePDBReader.cs | 260 +++++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 src/Simulation/Common/PortablePDBReader.cs diff --git a/src/Simulation/Common/PortablePDBReader.cs b/src/Simulation/Common/PortablePDBReader.cs new file mode 100644 index 00000000000..b7fbf3d2bbb --- /dev/null +++ b/src/Simulation/Common/PortablePDBReader.cs @@ -0,0 +1,260 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Quantum.Simulation.Common +{ + using System.Collections.Generic; + using System.Reflection.Metadata; + using System; + using System.IO; + using System.IO.Compression; + using System.Text; + using Newtonsoft.Json.Linq; + using System.Text.RegularExpressions; + using Microsoft.Quantum.Simulation.Core; + + // Based on https://github.com/microsoft/BPerf/blob/master/WebViewer/Microsoft.BPerf.SymbolicInformation.ProgramDatabase/PortablePdbSymbolReader.cs + class PortablePdbSymbolReader + { + private static readonly Guid SourceLink = new Guid("CC110556-A091-4D38-9FEC-25AB9A351A6A"); + + private static readonly Guid EmbeddedSource = new Guid("0E8A571B-6926-466E-B4AD-8AB04611F5FE"); + + public static Dictionary GetEmbeddedFiles(string pdbFilePath) + { + Dictionary embeddedFiles = new Dictionary(); + + using (FileStream stream = File.OpenRead(pdbFilePath)) + using (MetadataReaderProvider metadataReaderProvider = MetadataReaderProvider.FromPortablePdbStream(stream)) + { + MetadataReader metadataReader = metadataReaderProvider.GetMetadataReader(); + CustomDebugInformationHandleCollection customDebugInformationHandles = metadataReader.CustomDebugInformation; + foreach (var customDebugInformationHandle in customDebugInformationHandles) + { + CustomDebugInformation customDebugInformation = metadataReader.GetCustomDebugInformation(customDebugInformationHandle); + if (metadataReader.GetGuid(customDebugInformation.Kind) == EmbeddedSource) + { + byte[] embeddedSource = metadataReader.GetBlobBytes(customDebugInformation.Value); + Int32 uncompressedSourceFileSize = BitConverter.ToInt32(embeddedSource, 0); + if (uncompressedSourceFileSize != 0) + { + Document document = metadataReader.GetDocument((DocumentHandle)customDebugInformation.Parent); + string sourceFileName = System.IO.Path.GetFullPath(metadataReader.GetString(document.Name)); + + // See https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PortablePdb-Metadata.md#embedded-source-c-and-vb-compilers + // Decompress embedded source + MemoryStream memoryStream = + new MemoryStream(embeddedSource, sizeof(Int32), embeddedSource.Length - sizeof(Int32)); + + using (DeflateStream decompressionStream = new DeflateStream(memoryStream, CompressionMode.Decompress)) + { + MemoryStream decompressed = new MemoryStream(new byte[uncompressedSourceFileSize], true); + decompressionStream.CopyTo(decompressed); + embeddedFiles.Add(sourceFileName, Encoding.UTF8.GetString(decompressed.ToArray())); + } + } + } + } + } + return embeddedFiles; + } + + public static string GetSourceLinkString(string pdbFilePath) + { + using (FileStream stream = File.OpenRead(pdbFilePath)) + { + using (MetadataReaderProvider metadataReaderProvider = MetadataReaderProvider.FromPortablePdbStream(stream)) + { + MetadataReader metadataReader = metadataReaderProvider.GetMetadataReader(); + CustomDebugInformationHandleCollection customDebugInformationHandles = metadataReader.CustomDebugInformation; + foreach (var customDebugInformationHandle in customDebugInformationHandles) + { + CustomDebugInformation customDebugInformation = metadataReader.GetCustomDebugInformation(customDebugInformationHandle); + + if (metadataReader.GetGuid(customDebugInformation.Kind) == SourceLink) + { + return Encoding.UTF8.GetString(metadataReader.GetBlobBytes(customDebugInformation.Value)); + } + } + } + } + return null; + } + + public static Tuple[] ParseSourceLinkString(string SourceLinkString) + { + if (SourceLinkString == null) return null; + List> pairs = new List>(); + JObject jsonResponse = JObject.Parse(SourceLinkString); + JObject document = JObject.Parse(jsonResponse["documents"].ToString()); + foreach (JProperty property in document.Properties()) + { + string fullPath = System.IO.Path.GetFullPath(property.Name.Replace("*", "")); + pairs.Add(new Tuple(fullPath, property.Value.ToObject().Replace("*", ""))); + } + return pairs.ToArray(); + } + + public static string TryFormatGitHubUrl(Tuple rawUrl, int lineNumber) + { + if (rawUrl == null) + return null; + + string result = null; + if (rawUrl.Item1.StartsWith(@"https://raw.githubusercontent.com")) + { + result = Regex.Replace(rawUrl.Item1, "[a-f0-9]+" + Regex.Escape("/") + "$", (Match m) => { return @"blob/" + m.Value; }); + result = result.Replace(@"https://raw.githubusercontent.com", @"https://github.com"); + result += rawUrl.Item2; + result += $"#L{lineNumber}"; + } + else + { + result = rawUrl.Item1 + rawUrl.Item2; + } + return result; + } + + public static string GetPDBLocation(ICallable callable) + { + try + { + string filename = System.IO.Path.ChangeExtension(callable.UnwrapCallable().GetType().Assembly.Location, ".pdb"); + if( File.Exists(filename) ) + { + return filename; + } + else + { + return null; + } + } + catch(NotSupportedException) + { + return null; + } + } + } + + public static class PortablePDBPathRemappingCache + { + [ThreadStatic] + private static Dictionary[]> knownPathRemappings = null; + + public static Tuple[] GetRemappingInfromation(string pdbLocation) + { + if (knownPathRemappings == null) + { + knownPathRemappings = new Dictionary[]>(); + } + + Tuple[] remappings; + if (knownPathRemappings.TryGetValue(pdbLocation, out remappings)) + { + return remappings; + } + else + { + try + { + string sourceLinkString = PortablePdbSymbolReader.GetSourceLinkString(pdbLocation); + remappings = PortablePdbSymbolReader.ParseSourceLinkString(sourceLinkString); + } + finally + { + knownPathRemappings.Add(pdbLocation, remappings); + } + return remappings; + } + } + + /// + /// Tuple of strings such that full URL consists of their concatenation. + /// First part of URL is URL root for all files in PDB and second part is relative path to given file. + /// + public static Tuple TryGetFileUrl(string pdbLocation, string fileName ) + { + if (fileName == null) return null; + + string prefix = null; + string rest = null; + + Tuple[] fileCorrespondence = GetRemappingInfromation(pdbLocation); + if (fileCorrespondence != null) + { + foreach (Tuple replacement in fileCorrespondence) + { + if (fileName.StartsWith(replacement.Item1)) + { + rest = fileName.Replace(replacement.Item1, ""); + prefix = replacement.Item2; + if (prefix.Contains(@"/") && rest.Contains(@"\")) + { + rest = rest.Replace(@"\", @"/"); + } + break; + } + } + } + return new Tuple(prefix,rest); + } + } + + public static class PortablePDBEmbeddedFilesCache + { + public const string lineMarkPrefix = ">>>"; + public const int lineNumberPaddingWidth = 6; + + [ThreadStatic] + private static Dictionary> embeddedFiles = null; + + public static Dictionary GetEmbeddedFiles(string pdbLocation) + { + if (embeddedFiles == null) + { + embeddedFiles = new Dictionary>(); + } + + Dictionary embeddedFilesFromPath = null; + if (embeddedFiles.TryGetValue(pdbLocation, out embeddedFilesFromPath)) + { + return embeddedFilesFromPath; + } + else + { + try + { + embeddedFilesFromPath = PortablePdbSymbolReader.GetEmbeddedFiles(pdbLocation); + } + finally + { + embeddedFiles.Add(pdbLocation, embeddedFilesFromPath); + } + return embeddedFilesFromPath; + } + } + + public static string GetEmbeddedFileRange( string pdbLocation, string fullName, int lineStart, int lineEnd, bool showLineNumbers = false, int markedLine = -1, string markPrefix = lineMarkPrefix) + { + Dictionary sourceToFile = GetEmbeddedFiles(pdbLocation); + if (sourceToFile == null) return null; + string source = sourceToFile.GetValueOrDefault(fullName); + if (source == null) return null; + string[] lines = Regex.Split(source, "\r\n|\r|\n"); + StringBuilder builder = new StringBuilder(); + for( int i = lineStart; i < (lineEnd == -1 ? int.MaxValue : lineEnd); ++i ) + { + if( showLineNumbers ) + { + builder.Append($"{i} ".PadLeft(lineNumberPaddingWidth)); + } + if (i == markedLine) + { + builder.Append(markPrefix); + } + builder.AppendLine(lines[i - 1]); + } + return builder.ToString(); + } + } +} \ No newline at end of file From 28ea1b8993ef121236a657b09d4e78ab5fbf8c79 Mon Sep 17 00:00:00 2001 From: Vadym Kliuchnikov Date: Tue, 29 Oct 2019 21:26:24 -0700 Subject: [PATCH 09/20] fixing code generation tests to have base one line numbers --- .../SimulationCodeTests.fs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Simulation/CsharpGeneration.Tests/SimulationCodeTests.fs b/src/Simulation/CsharpGeneration.Tests/SimulationCodeTests.fs index 6139248ef3d..46a99b56d66 100644 --- a/src/Simulation/CsharpGeneration.Tests/SimulationCodeTests.fs +++ b/src/Simulation/CsharpGeneration.Tests/SimulationCodeTests.fs @@ -2304,10 +2304,10 @@ namespace N1 |> testOneClass randomAbstractOperation """ - [SourceLocation("%%%", OperationFunctor.Body, 107, 112)] - [SourceLocation("%%%", OperationFunctor.Adjoint, 112, 118)] - [SourceLocation("%%%", OperationFunctor.Controlled, 118, 125)] - [SourceLocation("%%%", OperationFunctor.ControlledAdjoint, 125, 131)] + [SourceLocation("%%%", OperationFunctor.Body, 108, 113)] + [SourceLocation("%%%", OperationFunctor.Adjoint, 113, 119)] + [SourceLocation("%%%", OperationFunctor.Controlled, 119, 126)] + [SourceLocation("%%%", OperationFunctor.ControlledAdjoint, 126, 132)] public partial class oneQubitOperation : Unitary, ICallable { public oneQubitOperation(IOperationFactory m) : base(m) @@ -2405,7 +2405,7 @@ namespace N1 |> testOneClass genCtrl3 """ - [SourceLocation("%%%", OperationFunctor.Body, 1265, 1271)] + [SourceLocation("%%%", OperationFunctor.Body, 1266, 1272)] public partial class composeImpl<__A__, __B__> : Operation<(ICallable,ICallable,__B__), QVoid>, ICallable { public composeImpl(IOperationFactory m) : base(m) @@ -2554,7 +2554,7 @@ namespace N1 |> testOneClass emptyFunction """ - [SourceLocation("%%%", OperationFunctor.Body, 32, 39)] + [SourceLocation("%%%", OperationFunctor.Body, 33, 40)] public partial class intFunction : Function, ICallable { public intFunction(IOperationFactory m) : base(m) @@ -2582,7 +2582,7 @@ namespace N1 |> testOneClass intFunction """ - [SourceLocation("%%%", OperationFunctor.Body, 44, 50)] + [SourceLocation("%%%", OperationFunctor.Body, 45, 51)] public partial class powFunction : Function<(Int64,Int64), Int64>, ICallable { public powFunction(IOperationFactory m) : base(m) @@ -2619,7 +2619,7 @@ namespace N1 |> testOneClass powFunction """ - [SourceLocation("%%%", OperationFunctor.Body, 50, 56)] + [SourceLocation("%%%", OperationFunctor.Body, 51, 57)] public partial class bigPowFunction : Function<(System.Numerics.BigInteger,Int64), System.Numerics.BigInteger>, ICallable { public bigPowFunction(IOperationFactory m) : base(m) @@ -3058,7 +3058,7 @@ using Microsoft.Quantum.Simulation.Core; #line hidden namespace Microsoft.Quantum.Tests.Inline { - [SourceLocation("%%%", OperationFunctor.Body, 6, -1)] + [SourceLocation("%%%", OperationFunctor.Body, 7, -1)] public partial class HelloWorld : Operation, ICallable { public HelloWorld(IOperationFactory m) : base(m) @@ -3104,7 +3104,7 @@ using Microsoft.Quantum.Simulation.Core; #line hidden namespace Microsoft.Quantum.Tests.LineNumbers { - [SourceLocation("%%%", OperationFunctor.Body, 8, -1)] + [SourceLocation("%%%", OperationFunctor.Body, 9, -1)] public partial class TestLineInBlocks : Operation, ICallable { public TestLineInBlocks(IOperationFactory m) : base(m) From 1701ba197062c475c6163c24ed41ce289cdeabbd Mon Sep 17 00:00:00 2001 From: Vadym Date: Wed, 30 Oct 2019 11:20:36 -0700 Subject: [PATCH 10/20] incorporating feedback --- src/Simulation/Common/PortablePDBReader.cs | 41 ++++++++++++++++------ 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/Simulation/Common/PortablePDBReader.cs b/src/Simulation/Common/PortablePDBReader.cs index b7fbf3d2bbb..9c7ff0a9266 100644 --- a/src/Simulation/Common/PortablePDBReader.cs +++ b/src/Simulation/Common/PortablePDBReader.cs @@ -236,23 +236,44 @@ public static Dictionary GetEmbeddedFiles(string pdbLocation) public static string GetEmbeddedFileRange( string pdbLocation, string fullName, int lineStart, int lineEnd, bool showLineNumbers = false, int markedLine = -1, string markPrefix = lineMarkPrefix) { - Dictionary sourceToFile = GetEmbeddedFiles(pdbLocation); - if (sourceToFile == null) return null; - string source = sourceToFile.GetValueOrDefault(fullName); + Dictionary fileNameToFileSourceText = GetEmbeddedFiles(pdbLocation); + if (fileNameToFileSourceText == null) return null; + string source = fileNameToFileSourceText.GetValueOrDefault(fullName); if (source == null) return null; - string[] lines = Regex.Split(source, "\r\n|\r|\n"); + StringBuilder builder = new StringBuilder(); - for( int i = lineStart; i < (lineEnd == -1 ? int.MaxValue : lineEnd); ++i ) + using (StringReader reader = new StringReader(source)) { - if( showLineNumbers ) + int lineNumber = 0; + string currentLine = null; + + // first go through text source till we reach lineStart + while ( reader.Peek() != -1 ) { - builder.Append($"{i} ".PadLeft(lineNumberPaddingWidth)); + lineNumber++; + currentLine = reader.ReadLine(); + if (lineNumber == lineStart) + break; } - if (i == markedLine) + + while (reader.Peek() != -1) { - builder.Append(markPrefix); + if (showLineNumbers) + { + builder.Append($"{lineNumber} ".PadLeft(lineNumberPaddingWidth)); + } + if (lineNumber == markedLine) + { + builder.Append(markPrefix); + } + builder.AppendLine(currentLine); + + lineNumber++; + currentLine = reader.ReadLine(); + + if (lineNumber == lineEnd) + break; } - builder.AppendLine(lines[i - 1]); } return builder.ToString(); } From 6408fe2f6632e4e9d7a8c3895e4c8a45e27333be Mon Sep 17 00:00:00 2001 From: Vadym Kliuchnikov Date: Wed, 30 Oct 2019 11:35:57 -0700 Subject: [PATCH 11/20] refactoring --- src/Simulation/Common/PortablePDBReader.cs | 26 ++++++++++------------ 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Simulation/Common/PortablePDBReader.cs b/src/Simulation/Common/PortablePDBReader.cs index 9c7ff0a9266..4f094820a06 100644 --- a/src/Simulation/Common/PortablePDBReader.cs +++ b/src/Simulation/Common/PortablePDBReader.cs @@ -139,17 +139,17 @@ public static string GetPDBLocation(ICallable callable) public static class PortablePDBPathRemappingCache { [ThreadStatic] - private static Dictionary[]> knownPathRemappings = null; + private static Dictionary[]> pdbLocationToPathRemapping = null; public static Tuple[] GetRemappingInfromation(string pdbLocation) { - if (knownPathRemappings == null) + if (pdbLocationToPathRemapping == null) { - knownPathRemappings = new Dictionary[]>(); + pdbLocationToPathRemapping = new Dictionary[]>(); } Tuple[] remappings; - if (knownPathRemappings.TryGetValue(pdbLocation, out remappings)) + if (pdbLocationToPathRemapping.TryGetValue(pdbLocation, out remappings)) { return remappings; } @@ -162,7 +162,7 @@ public static Tuple[] GetRemappingInfromation(string pdbLocation } finally { - knownPathRemappings.Add(pdbLocation, remappings); + pdbLocationToPathRemapping.Add(pdbLocation, remappings); } return remappings; } @@ -206,17 +206,17 @@ public static class PortablePDBEmbeddedFilesCache public const int lineNumberPaddingWidth = 6; [ThreadStatic] - private static Dictionary> embeddedFiles = null; + private static Dictionary> pdbLocationToEmbeddedFiles = null; public static Dictionary GetEmbeddedFiles(string pdbLocation) { - if (embeddedFiles == null) + if (pdbLocationToEmbeddedFiles == null) { - embeddedFiles = new Dictionary>(); + pdbLocationToEmbeddedFiles = new Dictionary>(); } Dictionary embeddedFilesFromPath = null; - if (embeddedFiles.TryGetValue(pdbLocation, out embeddedFilesFromPath)) + if (pdbLocationToEmbeddedFiles.TryGetValue(pdbLocation, out embeddedFilesFromPath)) { return embeddedFilesFromPath; } @@ -228,7 +228,7 @@ public static Dictionary GetEmbeddedFiles(string pdbLocation) } finally { - embeddedFiles.Add(pdbLocation, embeddedFilesFromPath); + pdbLocationToEmbeddedFiles.Add(pdbLocation, embeddedFilesFromPath); } return embeddedFilesFromPath; } @@ -245,19 +245,19 @@ public static string GetEmbeddedFileRange( string pdbLocation, string fullName, using (StringReader reader = new StringReader(source)) { int lineNumber = 0; - string currentLine = null; // first go through text source till we reach lineStart while ( reader.Peek() != -1 ) { lineNumber++; - currentLine = reader.ReadLine(); if (lineNumber == lineStart) break; + reader.ReadLine(); } while (reader.Peek() != -1) { + string currentLine = reader.ReadLine(); if (showLineNumbers) { builder.Append($"{lineNumber} ".PadLeft(lineNumberPaddingWidth)); @@ -269,8 +269,6 @@ public static string GetEmbeddedFileRange( string pdbLocation, string fullName, builder.AppendLine(currentLine); lineNumber++; - currentLine = reader.ReadLine(); - if (lineNumber == lineEnd) break; } From 013bea670378c302dee0be4e2ba73f05eaa662c7 Mon Sep 17 00:00:00 2001 From: Vadym Kliuchnikov Date: Wed, 30 Oct 2019 15:14:30 -0700 Subject: [PATCH 12/20] refactoring --- src/Simulation/Common/StackTrace.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Simulation/Common/StackTrace.cs b/src/Simulation/Common/StackTrace.cs index 0dacf7d98cd..5d01999ca76 100644 --- a/src/Simulation/Common/StackTrace.cs +++ b/src/Simulation/Common/StackTrace.cs @@ -70,9 +70,11 @@ public string GetURLFromPDB() return PortablePdbSymbolReader.TryFormatGitHubUrl(result, failedLineNumber); } + private const string messageFormat = "in {0} on {1}"; + public override string ToString() { - return $"in {operation.FullName} on {sourceFile}:line {failedLineNumber}"; + return string.Format(messageFormat, operation.FullName, $"{sourceFile}:line {failedLineNumber}"); } /// @@ -99,7 +101,7 @@ public virtual string ToStringWithBestSourceLocation() } else { - return $"in {operation.FullName} on {url}"; + return string.Format(messageFormat, operation.FullName, url); } } } @@ -178,7 +180,7 @@ static StackFrame[] PopulateSourceLocations(Stack qsharpCallStack, S if (sourceLocation != null && sourceLocation.SpecializationKind == op.Variant) { currentFrame.sourceFile = System.IO.Path.GetFullPath(sourceLocation.SourceFile); - currentFrame.declarationStartLineNumber = sourceLocation.StartLine; // note that attribute has base 0 line numbers + currentFrame.declarationStartLineNumber = sourceLocation.StartLine; currentFrame.declarationEndLineNumber = sourceLocation.EndLine; } } From 12edebc8c8b7cc353eee44e56824d4e5d92d7823 Mon Sep 17 00:00:00 2001 From: Vadym Kliuchnikov Date: Wed, 30 Oct 2019 15:51:04 -0700 Subject: [PATCH 13/20] refactoring --- src/Simulation/Core/Generics/Adjoint.cs | 4 ++-- src/Simulation/Core/Generics/Controlled.cs | 4 ++-- src/Simulation/Core/Generics/GenericPartial.cs | 4 ++-- src/Simulation/Core/Operations/Adjoint.cs | 4 ++-- src/Simulation/Core/Operations/Controlled.cs | 4 ++-- src/Simulation/Core/Operations/Operation.cs | 2 +- src/Simulation/Core/Operations/OperationPartial.cs | 4 ++-- src/Simulation/Core/TypeExtensions.cs | 4 ++-- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Simulation/Core/Generics/Adjoint.cs b/src/Simulation/Core/Generics/Adjoint.cs index 524df1feda1..45577250ebd 100644 --- a/src/Simulation/Core/Generics/Adjoint.cs +++ b/src/Simulation/Core/Generics/Adjoint.cs @@ -23,7 +23,7 @@ public interface IAdjointable : ICallable /// input Type is not resolved until it gets Applied at runtime. /// [DebuggerTypeProxy(typeof(GenericAdjoint.DebuggerProxy))] - public class GenericAdjoint : GenericCallable, IApplyData, IWrappedOperation + public class GenericAdjoint : GenericCallable, IApplyData, IOperationWrapper { public GenericAdjoint(GenericCallable baseOp) : base(baseOp.Factory, null) { @@ -31,7 +31,7 @@ public GenericAdjoint(GenericCallable baseOp) : base(baseOp.Factory, null) } public GenericCallable BaseOp { get; } - ICallable IWrappedOperation.BaseOperation => BaseOp; + ICallable IOperationWrapper.BaseOperation => BaseOp; IEnumerable IApplyData.Qubits => ((IApplyData)this.BaseOp)?.Qubits; diff --git a/src/Simulation/Core/Generics/Controlled.cs b/src/Simulation/Core/Generics/Controlled.cs index 15f57783dfa..cc9b05fb994 100644 --- a/src/Simulation/Core/Generics/Controlled.cs +++ b/src/Simulation/Core/Generics/Controlled.cs @@ -25,7 +25,7 @@ public partial interface IControllable : ICallable /// input Type is not resolved until it gets Applied at runtime. /// [DebuggerTypeProxy(typeof(GenericControlled.DebuggerProxy))] - public class GenericControlled : GenericCallable, IApplyData, IWrappedOperation + public class GenericControlled : GenericCallable, IApplyData, IOperationWrapper { public GenericControlled(GenericCallable baseOp) : base(baseOp.Factory, null) { @@ -33,7 +33,7 @@ public GenericControlled(GenericCallable baseOp) : base(baseOp.Factory, null) } public GenericCallable BaseOp { get; } - ICallable IWrappedOperation.BaseOperation => BaseOp; + ICallable IOperationWrapper.BaseOperation => BaseOp; IEnumerable IApplyData.Qubits => ((IApplyData)this.BaseOp)?.Qubits; diff --git a/src/Simulation/Core/Generics/GenericPartial.cs b/src/Simulation/Core/Generics/GenericPartial.cs index 3d8408d8ceb..8db1263639e 100644 --- a/src/Simulation/Core/Generics/GenericPartial.cs +++ b/src/Simulation/Core/Generics/GenericPartial.cs @@ -14,7 +14,7 @@ namespace Microsoft.Quantum.Simulation.Core /// input Type is not resolved until it gets Applied at runtime. /// [DebuggerTypeProxy(typeof(GenericPartial.DebuggerProxy))] - public class GenericPartial : GenericCallable, IApplyData, IWrappedOperation + public class GenericPartial : GenericCallable, IApplyData, IOperationWrapper { private Lazy __qubits = null; @@ -29,7 +29,7 @@ public GenericPartial(GenericCallable baseOp, object partialValues) : base(baseO } public GenericCallable BaseOp { get; } - ICallable IWrappedOperation.BaseOperation => BaseOp; + ICallable IOperationWrapper.BaseOperation => BaseOp; public override string Name => this.BaseOp.Name; public override string FullName => this.BaseOp.FullName; diff --git a/src/Simulation/Core/Operations/Adjoint.cs b/src/Simulation/Core/Operations/Adjoint.cs index 20bb62fc5fc..6db9f4a9f81 100644 --- a/src/Simulation/Core/Operations/Adjoint.cs +++ b/src/Simulation/Core/Operations/Adjoint.cs @@ -39,7 +39,7 @@ public Adjointable(IOperationFactory m) : base(m) /// Class used to represents an operation that has been adjointed. /// [DebuggerTypeProxy(typeof(AdjointedOperation<,>.DebuggerProxy))] - public class AdjointedOperation : Unitary, IApplyData, ICallable, IWrappedOperation + public class AdjointedOperation : Unitary, IApplyData, ICallable, IOperationWrapper { public AdjointedOperation(Operation op) : base(op.Factory) { @@ -50,7 +50,7 @@ public AdjointedOperation(Operation op) : base(op.Factory) } public Operation BaseOp { get; } - ICallable IWrappedOperation.BaseOperation => BaseOp; + ICallable IOperationWrapper.BaseOperation => BaseOp; public override void Init() { } diff --git a/src/Simulation/Core/Operations/Controlled.cs b/src/Simulation/Core/Operations/Controlled.cs index 1f7be787f66..8df3a8eb579 100644 --- a/src/Simulation/Core/Operations/Controlled.cs +++ b/src/Simulation/Core/Operations/Controlled.cs @@ -38,7 +38,7 @@ public Controllable(IOperationFactory m) : base(m) { } /// This class is used to represents an operation that has been controlled. /// [DebuggerTypeProxy(typeof(ControlledOperation<,>.DebuggerProxy))] - public class ControlledOperation : Unitary<(IQArray, I)>, IApplyData, ICallable, IWrappedOperation + public class ControlledOperation : Unitary<(IQArray, I)>, IApplyData, ICallable, IOperationWrapper { public class In : IApplyData { @@ -66,7 +66,7 @@ public ControlledOperation(Operation op) : base(op.Factory) } public Operation BaseOp { get; } - ICallable IWrappedOperation.BaseOperation => BaseOp; + ICallable IOperationWrapper.BaseOperation => BaseOp; public override void Init() { } diff --git a/src/Simulation/Core/Operations/Operation.cs b/src/Simulation/Core/Operations/Operation.cs index c654dc6b299..e81248f2528 100644 --- a/src/Simulation/Core/Operations/Operation.cs +++ b/src/Simulation/Core/Operations/Operation.cs @@ -22,7 +22,7 @@ public partial interface ICallable : ICallable /// , , /// , /// - public interface IWrappedOperation + public interface IOperationWrapper { ICallable BaseOperation { get; } } diff --git a/src/Simulation/Core/Operations/OperationPartial.cs b/src/Simulation/Core/Operations/OperationPartial.cs index 46b2407b616..12e03f01913 100644 --- a/src/Simulation/Core/Operations/OperationPartial.cs +++ b/src/Simulation/Core/Operations/OperationPartial.cs @@ -17,7 +17,7 @@ namespace Microsoft.Quantum.Simulation.Core /// Optionally it can receive a Mapper to do the same. /// [DebuggerTypeProxy(typeof(OperationPartial<,,>.DebuggerProxy))] - public class OperationPartial : Operation, IUnitary

, IWrappedOperation + public class OperationPartial : Operation, IUnitary

, IOperationWrapper { private Lazy __qubits = null; @@ -59,7 +59,7 @@ public OperationPartial(Operation op, object partialTuple) : base(op.Facto public override void Init() { } public Operation BaseOp { get; } - ICallable IWrappedOperation.BaseOperation => BaseOp; + ICallable IOperationWrapper.BaseOperation => BaseOp; public Func Mapper { get; } diff --git a/src/Simulation/Core/TypeExtensions.cs b/src/Simulation/Core/TypeExtensions.cs index 5bfb38c72eb..94bc5ebabf6 100644 --- a/src/Simulation/Core/TypeExtensions.cs +++ b/src/Simulation/Core/TypeExtensions.cs @@ -372,9 +372,9 @@ public static string QSharpType(this Type t) public static ICallable UnwrapCallable(this ICallable op) { ICallable res = op; - while (res as IWrappedOperation != null) + while (res as IOperationWrapper != null) { - res = (res as IWrappedOperation).BaseOperation; + res = (res as IOperationWrapper).BaseOperation; } return res; } From ac0bfa2fed824e7db635056c64990476d5781e2f Mon Sep 17 00:00:00 2001 From: Vadym Kliuchnikov Date: Wed, 20 Nov 2019 14:45:27 -0800 Subject: [PATCH 14/20] addressing review; part1 --- src/Simulation/Common/PortablePDBReader.cs | 34 ++++++++++++++----- src/Simulation/Common/SimulatorBase.cs | 4 +-- .../Simulators.Tests/StackTraceTests.cs | 2 +- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/Simulation/Common/PortablePDBReader.cs b/src/Simulation/Common/PortablePDBReader.cs index 4f094820a06..9b1b601697a 100644 --- a/src/Simulation/Common/PortablePDBReader.cs +++ b/src/Simulation/Common/PortablePDBReader.cs @@ -13,13 +13,26 @@ namespace Microsoft.Quantum.Simulation.Common using System.Text.RegularExpressions; using Microsoft.Quantum.Simulation.Core; - // Based on https://github.com/microsoft/BPerf/blob/master/WebViewer/Microsoft.BPerf.SymbolicInformation.ProgramDatabase/PortablePdbSymbolReader.cs + ///

+ /// Utility class for extracting source file text and source location from PortablePDB meta-data. + /// + /// + /// Based on https://github.com/microsoft/BPerf/blob/master/WebViewer/Microsoft.BPerf.SymbolicInformation.ProgramDatabase/PortablePdbSymbolReader.cs + /// class PortablePdbSymbolReader { + /// SourceLink GUID is a part of PortablePDB meta-data specification https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PortablePdb-Metadata.md#SourceLink private static readonly Guid SourceLink = new Guid("CC110556-A091-4D38-9FEC-25AB9A351A6A"); + /// EmbeddedSource GUID is a part of PortablePDB meta-data specification https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PortablePdb-Metadata.md#embedded-source-c-and-vb-compilers private static readonly Guid EmbeddedSource = new Guid("0E8A571B-6926-466E-B4AD-8AB04611F5FE"); + /// + /// Unpacks all files stored in a PortablePDB meta-data. The key in the dictionary is the location of a source file + /// on the build machine. The value is the content of the source file itself. + /// + /// Path to PortablePDB file to load source files from. + /// public static Dictionary GetEmbeddedFiles(string pdbFilePath) { Dictionary embeddedFiles = new Dictionary(); @@ -59,6 +72,11 @@ public static Dictionary GetEmbeddedFiles(string pdbFilePath) return embeddedFiles; } + /// + /// Returns SourceLink information, that is JSON string with schema described at https://github.com/dotnet/designs/blob/master/accepted/diagnostics/source-link.md#source-link-json-schema + /// stored in PortablePDB. + /// + /// Path to PortablePDB file public static string GetSourceLinkString(string pdbFilePath) { using (FileStream stream = File.OpenRead(pdbFilePath)) @@ -95,7 +113,7 @@ public static Tuple[] ParseSourceLinkString(string SourceLinkStr return pairs.ToArray(); } - public static string TryFormatGitHubUrl(Tuple rawUrl, int lineNumber) + public static string TryFormatGitHubUrl(Tuple rawUrl, int lineNumber) { if (rawUrl == null) return null; @@ -120,7 +138,7 @@ public static string GetPDBLocation(ICallable callable) try { string filename = System.IO.Path.ChangeExtension(callable.UnwrapCallable().GetType().Assembly.Location, ".pdb"); - if( File.Exists(filename) ) + if (File.Exists(filename)) { return filename; } @@ -129,7 +147,7 @@ public static string GetPDBLocation(ICallable callable) return null; } } - catch(NotSupportedException) + catch (NotSupportedException) { return null; } @@ -172,7 +190,7 @@ public static Tuple[] GetRemappingInfromation(string pdbLocation /// Tuple of strings such that full URL consists of their concatenation. /// First part of URL is URL root for all files in PDB and second part is relative path to given file. /// - public static Tuple TryGetFileUrl(string pdbLocation, string fileName ) + public static Tuple TryGetFileUrl(string pdbLocation, string fileName) { if (fileName == null) return null; @@ -196,7 +214,7 @@ public static Tuple TryGetFileUrl(string pdbLocation, string file } } } - return new Tuple(prefix,rest); + return new Tuple(prefix, rest); } } @@ -234,7 +252,7 @@ public static Dictionary GetEmbeddedFiles(string pdbLocation) } } - public static string GetEmbeddedFileRange( string pdbLocation, string fullName, int lineStart, int lineEnd, bool showLineNumbers = false, int markedLine = -1, string markPrefix = lineMarkPrefix) + public static string GetEmbeddedFileRange(string pdbLocation, string fullName, int lineStart, int lineEnd, bool showLineNumbers = false, int markedLine = -1, string markPrefix = lineMarkPrefix) { Dictionary fileNameToFileSourceText = GetEmbeddedFiles(pdbLocation); if (fileNameToFileSourceText == null) return null; @@ -247,7 +265,7 @@ public static string GetEmbeddedFileRange( string pdbLocation, string fullName, int lineNumber = 0; // first go through text source till we reach lineStart - while ( reader.Peek() != -1 ) + while (reader.Peek() != -1) { lineNumber++; if (lineNumber == lineStart) diff --git a/src/Simulation/Common/SimulatorBase.cs b/src/Simulation/Common/SimulatorBase.cs index b0dc7dcba3d..7a585bb274c 100644 --- a/src/Simulation/Common/SimulatorBase.cs +++ b/src/Simulation/Common/SimulatorBase.cs @@ -98,7 +98,7 @@ public override AbstractCallable CreateInstance(Type t) return result; } - public virtual O RunSync(I args) where T : AbstractCallable, ICallable + public virtual O Execute(I args) where T : AbstractCallable, ICallable { O res = default(O); var op = Get(); @@ -125,7 +125,7 @@ public virtual O RunSync(I args) where T : AbstractCallable, ICallable public virtual Task Run(I args) where T : AbstractCallable, ICallable { - return Task.Run(() => RunSync(args)); + return Task.Run(() => Execute(args)); } /// diff --git a/src/Simulation/Simulators.Tests/StackTraceTests.cs b/src/Simulation/Simulators.Tests/StackTraceTests.cs index 602b9fff854..3e4ac6c005a 100644 --- a/src/Simulation/Simulators.Tests/StackTraceTests.cs +++ b/src/Simulation/Simulators.Tests/StackTraceTests.cs @@ -303,7 +303,7 @@ public void ErrorLogTest() sim.OnLog += (msg) => stringBuilder.AppendLine(msg); try { - QVoid res = sim.RunSync(QVoid.Instance); + QVoid res = sim.Execute(QVoid.Instance); } catch (ExecutionFailException) { From 96839ddca0c21aa32bf9c91f339fa1d66ed68148 Mon Sep 17 00:00:00 2001 From: Vadym Kliuchnikov Date: Wed, 20 Nov 2019 18:48:21 -0800 Subject: [PATCH 15/20] addressing reviewer feedback; part 2 --- src/Simulation/Common/PortablePDBReader.cs | 243 ++++++++++++------ src/Simulation/Common/StackTrace.cs | 137 +++++----- .../Simulators.Tests/StackTraceTests.cs | 174 +++++++------ 3 files changed, 335 insertions(+), 219 deletions(-) diff --git a/src/Simulation/Common/PortablePDBReader.cs b/src/Simulation/Common/PortablePDBReader.cs index 9b1b601697a..4ac73c1fccd 100644 --- a/src/Simulation/Common/PortablePDBReader.cs +++ b/src/Simulation/Common/PortablePDBReader.cs @@ -12,6 +12,77 @@ namespace Microsoft.Quantum.Simulation.Common using Newtonsoft.Json.Linq; using System.Text.RegularExpressions; using Microsoft.Quantum.Simulation.Core; + using System.Linq; + + public class CompressedSourceFile + { + private readonly byte[] compressedSource = null; + private string decompressedSource = null; + + public CompressedSourceFile(byte[] compressedSource) + { + this.compressedSource = compressedSource; + } + + public override string ToString() + { + if (decompressedSource == null) + { + using MemoryStream memoryStream = new MemoryStream(compressedSource, sizeof(Int32), compressedSource.Length - sizeof(Int32)); + using DeflateStream decompressionStream = new DeflateStream(memoryStream, CompressionMode.Decompress); + Int32 uncompressedSourceFileSize = BitConverter.ToInt32(compressedSource, 0); + MemoryStream decompressed = new MemoryStream(new byte[uncompressedSourceFileSize], true); + decompressionStream.CopyTo(decompressed); + decompressedSource = Encoding.UTF8.GetString(decompressed.ToArray()); + } + + return decompressedSource; + } + } + + /// + /// Class for source link information corresponding to SourceLink schema + /// https://github.com/dotnet/designs/blob/master/accepted/diagnostics/source-link.md#source-link-json-schema + /// + [Serializable] + public class SourceLinkPathRemapping + { + public Dictionary documents; + + /// + /// Collection of patterns present within documents + /// + /// + /// For example, documents can contain patterns with *, like shown below + /// + /// { "documents": { "C:\\src\\CodeFormatter\\*": "https://raw.githubusercontent.com/dotnet/codeformatter/bcc51178e1a82fb2edaf47285f6e577989a7333f/*" },} + /// + /// + [Newtonsoft.Json.JsonIgnore] + private ValueTuple[] patterns; + + // Collection of patterns present within documents + [Newtonsoft.Json.JsonIgnore] + public ValueTuple[] Patterns + { + get + { + if (this.patterns == null) + { + List> patterns = new List>(); + foreach (KeyValuePair keyValuePair in documents) + { + if (keyValuePair.Key.EndsWith("*")) + { + patterns.Add(new ValueTuple(keyValuePair.Key.Replace("*", ""), keyValuePair.Value.Replace("*", ""))); + } + } + this.patterns = patterns.ToArray(); + } + return this.patterns; + } + } + } /// /// Utility class for extracting source file text and source location from PortablePDB meta-data. @@ -19,7 +90,7 @@ namespace Microsoft.Quantum.Simulation.Common /// /// Based on https://github.com/microsoft/BPerf/blob/master/WebViewer/Microsoft.BPerf.SymbolicInformation.ProgramDatabase/PortablePdbSymbolReader.cs /// - class PortablePdbSymbolReader + public class PortablePdbSymbolReader { /// SourceLink GUID is a part of PortablePDB meta-data specification https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PortablePdb-Metadata.md#SourceLink private static readonly Guid SourceLink = new Guid("CC110556-A091-4D38-9FEC-25AB9A351A6A"); @@ -30,12 +101,12 @@ class PortablePdbSymbolReader /// /// Unpacks all files stored in a PortablePDB meta-data. The key in the dictionary is the location of a source file /// on the build machine. The value is the content of the source file itself. + /// The function will throw an exception if PortablePDB file is not found or anything else went wrong. /// /// Path to PortablePDB file to load source files from. - /// - public static Dictionary GetEmbeddedFiles(string pdbFilePath) + public static Dictionary GetEmbeddedFiles(string pdbFilePath) { - Dictionary embeddedFiles = new Dictionary(); + Dictionary embeddedFiles = new Dictionary(); using (FileStream stream = File.OpenRead(pdbFilePath)) using (MetadataReaderProvider metadataReaderProvider = MetadataReaderProvider.FromPortablePdbStream(stream)) @@ -53,18 +124,7 @@ public static Dictionary GetEmbeddedFiles(string pdbFilePath) { Document document = metadataReader.GetDocument((DocumentHandle)customDebugInformation.Parent); string sourceFileName = System.IO.Path.GetFullPath(metadataReader.GetString(document.Name)); - - // See https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PortablePdb-Metadata.md#embedded-source-c-and-vb-compilers - // Decompress embedded source - MemoryStream memoryStream = - new MemoryStream(embeddedSource, sizeof(Int32), embeddedSource.Length - sizeof(Int32)); - - using (DeflateStream decompressionStream = new DeflateStream(memoryStream, CompressionMode.Decompress)) - { - MemoryStream decompressed = new MemoryStream(new byte[uncompressedSourceFileSize], true); - decompressionStream.CopyTo(decompressed); - embeddedFiles.Add(sourceFileName, Encoding.UTF8.GetString(decompressed.ToArray())); - } + embeddedFiles.Add(sourceFileName, new CompressedSourceFile(embeddedSource)); } } } @@ -75,9 +135,10 @@ public static Dictionary GetEmbeddedFiles(string pdbFilePath) /// /// Returns SourceLink information, that is JSON string with schema described at https://github.com/dotnet/designs/blob/master/accepted/diagnostics/source-link.md#source-link-json-schema /// stored in PortablePDB. + /// The function will throw an exception if PortablePDB file is not found or anything else went wrong. /// /// Path to PortablePDB file - public static string GetSourceLinkString(string pdbFilePath) + public static SourceLinkPathRemapping GetSourceLinkString(string pdbFilePath) { using (FileStream stream = File.OpenRead(pdbFilePath)) { @@ -91,7 +152,8 @@ public static string GetSourceLinkString(string pdbFilePath) if (metadataReader.GetGuid(customDebugInformation.Kind) == SourceLink) { - return Encoding.UTF8.GetString(metadataReader.GetBlobBytes(customDebugInformation.Value)); + string jsonString = Encoding.UTF8.GetString(metadataReader.GetBlobBytes(customDebugInformation.Value)); + return Newtonsoft.Json.JsonConvert.DeserializeObject(jsonString); } } } @@ -99,40 +161,54 @@ public static string GetSourceLinkString(string pdbFilePath) return null; } - public static Tuple[] ParseSourceLinkString(string SourceLinkString) - { - if (SourceLinkString == null) return null; - List> pairs = new List>(); - JObject jsonResponse = JObject.Parse(SourceLinkString); - JObject document = JObject.Parse(jsonResponse["documents"].ToString()); - foreach (JProperty property in document.Properties()) - { - string fullPath = System.IO.Path.GetFullPath(property.Name.Replace("*", "")); - pairs.Add(new Tuple(fullPath, property.Value.ToObject().Replace("*", ""))); - } - return pairs.ToArray(); - } - - public static string TryFormatGitHubUrl(Tuple rawUrl, int lineNumber) + /// + /// Includes line number into GitHub URL with source location + /// + /// Tuple of baseURL describing the repository and commit and relative path to the file + /// Line number to include into URL + /// GitHub URL for the file with the line number + public static string TryFormatGitHubUrl(string rawUrl, int lineNumber) { if (rawUrl == null) return null; - string result = null; - if (rawUrl.Item1.StartsWith(@"https://raw.githubusercontent.com")) + string result = rawUrl; + if (rawUrl.StartsWith(@"https://raw.githubusercontent.com")) { - result = Regex.Replace(rawUrl.Item1, "[a-f0-9]+" + Regex.Escape("/") + "$", (Match m) => { return @"blob/" + m.Value; }); - result = result.Replace(@"https://raw.githubusercontent.com", @"https://github.com"); - result += rawUrl.Item2; - result += $"#L{lineNumber}"; - } - else - { - result = rawUrl.Item1 + rawUrl.Item2; + // Our goal is to replace raw GitHub URL, for example something like: + // https://raw.githubusercontent.com/microsoft/qsharp-runtime/af6262c05522d645d0a0952272443e84eeab677a/src/Xunit/TestCaseDiscoverer.cs + // By a permanent link GitHub URL that includes line number + // https://github.com/microsoft/qsharp-runtime/blob/af6262c05522d645d0a0952272443e84eeab677a/src/Xunit/TestCaseDiscoverer.cs#L13 + // To make sure that when a user clicks the link to GitHub the line number is highlighted + + MatchCollection sha1StringMatches = Regex.Matches(rawUrl, Regex.Escape("/") + "[a-f0-9]{40}" + Regex.Escape("/")); // SHA1 part of the URL is 40 symbols long + Match sha1StringMatch = null; + + if (sha1StringMatches.Count == 1) + { + sha1StringMatch = sha1StringMatches[0]; + } + else // there are several sub-strings of URL that can potentially be a sha1 hash, we fall-back to original URL in this case + { + return rawUrl; + } + + if (sha1StringMatch.Success) + { + int startPosition = sha1StringMatch.Index; + string sha1String = sha1StringMatch.Value; //should be "/af6262c05522d645d0a0952272443e84eeab677a/" + string urlAndRepositoryPath = rawUrl.Substring(0, startPosition); // should be "https://raw.githubusercontent.com/microsoft/qsharp-runtime" + string relativePath = rawUrl.Substring(startPosition + sha1String.Length); // should be "src/Xunit/TestCaseDiscoverer.cs" + return $"{urlAndRepositoryPath.Replace(@"https://raw.githubusercontent.com", @"https://github.com")}{"/blob"}{sha1String}{relativePath}#L{lineNumber}"; + } } return result; } + /// + /// Returns location of PortablePDB file with the source information for a given callable. + /// Returns null if PortablePDB cannot be found. + /// public static string GetPDBLocation(ICallable callable) { try @@ -154,19 +230,26 @@ public static string GetPDBLocation(ICallable callable) } } + /// + /// Caches path remapping from build machine to URL per location of PDB file. + /// public static class PortablePDBPathRemappingCache { + /// + /// Key is the location of a PortablePDB file on a current machine + /// + // ThreadStaticAttribute makes sure that the cache is thread safe [ThreadStatic] - private static Dictionary[]> pdbLocationToPathRemapping = null; + private static Dictionary pdbLocationToPathRemapping = null; - public static Tuple[] GetRemappingInfromation(string pdbLocation) + public static SourceLinkPathRemapping GetRemappingInfromation(string pdbLocation) { if (pdbLocationToPathRemapping == null) { - pdbLocationToPathRemapping = new Dictionary[]>(); + pdbLocationToPathRemapping = new Dictionary(); } - Tuple[] remappings; + SourceLinkPathRemapping remappings; if (pdbLocationToPathRemapping.TryGetValue(pdbLocation, out remappings)) { return remappings; @@ -175,8 +258,7 @@ public static Tuple[] GetRemappingInfromation(string pdbLocation { try { - string sourceLinkString = PortablePdbSymbolReader.GetSourceLinkString(pdbLocation); - remappings = PortablePdbSymbolReader.ParseSourceLinkString(sourceLinkString); + remappings = PortablePdbSymbolReader.GetSourceLinkString(pdbLocation); } finally { @@ -187,53 +269,70 @@ public static Tuple[] GetRemappingInfromation(string pdbLocation } /// - /// Tuple of strings such that full URL consists of their concatenation. - /// First part of URL is URL root for all files in PDB and second part is relative path to given file. + /// Finds URL for given path on a build machine. /// - public static Tuple TryGetFileUrl(string pdbLocation, string fileName) + public static string TryGetFileUrl(string pdbLocation, string fileName) { if (fileName == null) return null; - string prefix = null; - string rest = null; - - Tuple[] fileCorrespondence = GetRemappingInfromation(pdbLocation); - if (fileCorrespondence != null) + SourceLinkPathRemapping remapping = GetRemappingInfromation(pdbLocation); + if (remapping != null) { - foreach (Tuple replacement in fileCorrespondence) + if (remapping.documents.ContainsKey(fileName)) { - if (fileName.StartsWith(replacement.Item1)) + return remapping.documents[fileName]; + } + + if (remapping.Patterns != null) + { + foreach (ValueTuple replacement in remapping.Patterns) { - rest = fileName.Replace(replacement.Item1, ""); - prefix = replacement.Item2; - if (prefix.Contains(@"/") && rest.Contains(@"\")) + if (fileName.StartsWith(replacement.Item1)) { - rest = rest.Replace(@"\", @"/"); + string rest = fileName.Replace(replacement.Item1, ""); + string prefix = replacement.Item2; + // Replace Windows-style separators by Web/Linux style + if (prefix.Contains(@"/") && rest.Contains(@"\")) + { + rest = rest.Replace(@"\", @"/"); + } + return prefix + rest; } - break; } } } - return new Tuple(prefix, rest); + return null; } } + /// + /// Caches sources of source files per location of PDB file indexed by source file path + /// public static class PortablePDBEmbeddedFilesCache { public const string lineMarkPrefix = ">>>"; public const int lineNumberPaddingWidth = 6; + /// + /// Key is the location of a PortablePDB file on a current machine. + /// Value is the dictionary returned by + /// called with a given Key. + /// + // ThreadStaticAttribute makes sure that the cache is thread safe [ThreadStatic] - private static Dictionary> pdbLocationToEmbeddedFiles = null; + private static Dictionary> pdbLocationToEmbeddedFiles = null; - public static Dictionary GetEmbeddedFiles(string pdbLocation) + /// + /// Returns cached result of calling + /// + public static Dictionary GetEmbeddedFiles(string pdbLocation) { if (pdbLocationToEmbeddedFiles == null) { - pdbLocationToEmbeddedFiles = new Dictionary>(); + pdbLocationToEmbeddedFiles = new Dictionary>(); } - Dictionary embeddedFilesFromPath = null; + Dictionary embeddedFilesFromPath = null; if (pdbLocationToEmbeddedFiles.TryGetValue(pdbLocation, out embeddedFilesFromPath)) { return embeddedFilesFromPath; @@ -254,13 +353,13 @@ public static Dictionary GetEmbeddedFiles(string pdbLocation) public static string GetEmbeddedFileRange(string pdbLocation, string fullName, int lineStart, int lineEnd, bool showLineNumbers = false, int markedLine = -1, string markPrefix = lineMarkPrefix) { - Dictionary fileNameToFileSourceText = GetEmbeddedFiles(pdbLocation); + Dictionary fileNameToFileSourceText = GetEmbeddedFiles(pdbLocation); if (fileNameToFileSourceText == null) return null; - string source = fileNameToFileSourceText.GetValueOrDefault(fullName); + CompressedSourceFile source = fileNameToFileSourceText.GetValueOrDefault(fullName); if (source == null) return null; StringBuilder builder = new StringBuilder(); - using (StringReader reader = new StringReader(source)) + using (StringReader reader = new StringReader(source.ToString())) { int lineNumber = 0; diff --git a/src/Simulation/Common/StackTrace.cs b/src/Simulation/Common/StackTrace.cs index 5d01999ca76..ed9ab930fd5 100644 --- a/src/Simulation/Common/StackTrace.cs +++ b/src/Simulation/Common/StackTrace.cs @@ -14,35 +14,42 @@ namespace Microsoft.Quantum.Simulation.Common [Serializable] public class StackFrame { - public readonly ICallable operation; - public readonly IApplyData argument; + /// + /// Callable corresponding to the stack frame + /// + public ICallable Callable { get; private set; } + + /// + /// Arguments passed to the callable in the stack frame + /// + public IApplyData Argument { get; private set; } /// /// The path to the source where operation is defined /// - public string sourceFile; + public string SourceFile { get; private set; } /// /// 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 /// - public int failedLineNumber = -2; + public int FailedLineNumber { get; private set; } /// /// One based line number where the declaration starts. /// - public int declarationStartLineNumber; + public int DeclarationStartLineNumber { get; private set; } /// /// 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. /// - public int declarationEndLineNumber; + public int DeclarationEndLineNumber { get; private set; } - public StackFrame(ICallable _operation, IApplyData _argument) + public StackFrame(ICallable callable, IApplyData argument) { - operation = _operation; - argument = _argument; + Callable = callable; + Argument = argument; } /// @@ -50,13 +57,13 @@ public StackFrame(ICallable _operation, IApplyData _argument) /// public string GetOperationSourceFromPDB() { - string pdbFileLocation = PortablePdbSymbolReader.GetPDBLocation(operation); + string pdbFileLocation = PortablePdbSymbolReader.GetPDBLocation(Callable); return PortablePDBEmbeddedFilesCache.GetEmbeddedFileRange( pdbFileLocation, - sourceFile, - declarationStartLineNumber, - declarationEndLineNumber, - showLineNumbers: true, markedLine: failedLineNumber); + SourceFile, + DeclarationStartLineNumber, + DeclarationEndLineNumber, + showLineNumbers: true, markedLine: FailedLineNumber); } /// @@ -65,16 +72,16 @@ public string GetOperationSourceFromPDB() /// public string GetURLFromPDB() { - string pdbFileLocation = PortablePdbSymbolReader.GetPDBLocation(operation); - Tuple result = PortablePDBPathRemappingCache.TryGetFileUrl(pdbFileLocation, sourceFile); - return PortablePdbSymbolReader.TryFormatGitHubUrl(result, failedLineNumber); + string pdbFileLocation = PortablePdbSymbolReader.GetPDBLocation(Callable); + string result = PortablePDBPathRemappingCache.TryGetFileUrl(pdbFileLocation, SourceFile); + return PortablePdbSymbolReader.TryFormatGitHubUrl(result, FailedLineNumber); } private const string messageFormat = "in {0} on {1}"; public override string ToString() { - return string.Format(messageFormat, operation.FullName, $"{sourceFile}:line {failedLineNumber}"); + return string.Format(messageFormat, Callable.FullName, $"{SourceFile}:line {FailedLineNumber}"); } /// @@ -88,7 +95,7 @@ public override string ToString() public virtual string ToStringWithBestSourceLocation() { string message = ToString(); - if (System.IO.File.Exists(sourceFile)) + if (System.IO.File.Exists(SourceFile)) { return message; } @@ -101,10 +108,54 @@ public virtual string ToStringWithBestSourceLocation() } else { - return string.Format(messageFormat, operation.FullName, url); + return string.Format(messageFormat, Callable.FullName, url); } } } + + public static StackFrame[] PopulateSourceLocations(Stack 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++; + } + } + } + return qsharpStackFrames; + } } /// @@ -168,49 +219,7 @@ void OnFail(System.Runtime.ExceptionServices.ExceptionDispatchInfo exceptionInfo } } - static StackFrame[] PopulateSourceLocations(Stack qsharpCallStack, System.Diagnostics.StackFrame[] csharpCallStack) - { - foreach (StackFrame currentFrame in qsharpCallStack) - { - ICallable op = currentFrame.operation.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++; - } - } - } - return qsharpStackFrames; - } + /// /// If failure has happened returns the call-stack at time of failure. @@ -224,7 +233,7 @@ public StackFrame[] CallStack { if( stackFramesWithLocations == null ) { - stackFramesWithLocations = PopulateSourceLocations(callStack, frames); + stackFramesWithLocations = StackFrame.PopulateSourceLocations(callStack, frames); } return stackFramesWithLocations; } diff --git a/src/Simulation/Simulators.Tests/StackTraceTests.cs b/src/Simulation/Simulators.Tests/StackTraceTests.cs index 3e4ac6c005a..2917aec09cd 100644 --- a/src/Simulation/Simulators.Tests/StackTraceTests.cs +++ b/src/Simulation/Simulators.Tests/StackTraceTests.cs @@ -41,30 +41,30 @@ public void AlwaysFail4Test() Assert.Equal(5, stackFrames.Length); - Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].operation.FullName); - Assert.Equal(namespacePrefix + "AlwaysFail1", stackFrames[1].operation.FullName); - Assert.Equal(namespacePrefix + "AlwaysFail2", stackFrames[2].operation.FullName); - Assert.Equal(namespacePrefix + "AlwaysFail3", stackFrames[3].operation.FullName); - Assert.Equal(namespacePrefix + "AlwaysFail4", stackFrames[4].operation.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].Callable.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail1", stackFrames[1].Callable.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail2", stackFrames[2].Callable.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail3", stackFrames[3].Callable.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail4", stackFrames[4].Callable.FullName); - Assert.Equal(OperationFunctor.Controlled, stackFrames[0].operation.Variant); - Assert.Equal(OperationFunctor.Controlled, stackFrames[1].operation.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); - Assert.Equal(OperationFunctor.Adjoint, stackFrames[3].operation.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[4].operation.Variant); + Assert.Equal(OperationFunctor.Controlled, stackFrames[0].Callable.Variant); + Assert.Equal(OperationFunctor.Controlled, stackFrames[1].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].Callable.Variant); + Assert.Equal(OperationFunctor.Adjoint, stackFrames[3].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[4].Callable.Variant); - Assert.Equal(14, stackFrames[2].failedLineNumber); - Assert.Equal(21, stackFrames[4].failedLineNumber); + Assert.Equal(14, stackFrames[2].FailedLineNumber); + Assert.Equal(21, stackFrames[4].FailedLineNumber); // For Adjoint and Controlled we expect failedLineNumber to be equal to declarationStartLineNumber - Assert.Equal(stackFrames[0].declarationStartLineNumber, stackFrames[0].failedLineNumber); - Assert.Equal(stackFrames[1].declarationStartLineNumber, stackFrames[1].failedLineNumber); - Assert.Equal(stackFrames[3].declarationStartLineNumber, stackFrames[3].failedLineNumber); + Assert.Equal(stackFrames[0].DeclarationStartLineNumber, stackFrames[0].FailedLineNumber); + Assert.Equal(stackFrames[1].DeclarationStartLineNumber, stackFrames[1].FailedLineNumber); + Assert.Equal(stackFrames[3].DeclarationStartLineNumber, stackFrames[3].FailedLineNumber); for (int i = 0; i < stackFrames.Length; ++i) { Assert.StartsWith(@"https://github.com/", stackFrames[i].GetURLFromPDB()); - Assert.EndsWith($"#L{stackFrames[i].failedLineNumber}", stackFrames[i].GetURLFromPDB()); + Assert.EndsWith($"#L{stackFrames[i].FailedLineNumber}", stackFrames[i].GetURLFromPDB()); } StringBuilder builder = new StringBuilder(); @@ -78,7 +78,7 @@ public void AlwaysFail4Test() for( int i = 0; i < stackFrames.Length; ++i ) { - output.WriteLine($"operation:{stackFrames[i].operation.FullName}"); + output.WriteLine($"operation:{stackFrames[i].Callable.FullName}"); output.WriteLine(stackFrames[i].GetOperationSourceFromPDB()); } } @@ -102,17 +102,17 @@ public void GenericFail1Test() Assert.Equal(3, stackFrames.Length); - Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].operation.FullName); - Assert.Equal(namespacePrefix + "GenericFail", stackFrames[1].operation.FullName); - Assert.Equal(namespacePrefix + "GenericFail1", stackFrames[2].operation.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].Callable.FullName); + Assert.Equal(namespacePrefix + "GenericFail", stackFrames[1].Callable.FullName); + Assert.Equal(namespacePrefix + "GenericFail1", stackFrames[2].Callable.FullName); - Assert.Equal(OperationFunctor.Body, stackFrames[0].operation.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[1].operation.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[0].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[1].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].Callable.Variant); - Assert.Equal(7, stackFrames[0].failedLineNumber); - Assert.Equal(25, stackFrames[1].failedLineNumber); - Assert.Equal(29, stackFrames[2].failedLineNumber); + Assert.Equal(7, stackFrames[0].FailedLineNumber); + Assert.Equal(25, stackFrames[1].FailedLineNumber); + Assert.Equal(29, stackFrames[2].FailedLineNumber); } } @@ -129,17 +129,17 @@ public void GenericFail1Test() Assert.Equal(3, stackFrames.Length); - Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].operation.FullName); - Assert.Equal(namespacePrefix + "GenericFail", stackFrames[1].operation.FullName); - Assert.Equal(namespacePrefix + "GenericAdjFail1", stackFrames[2].operation.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].Callable.FullName); + Assert.Equal(namespacePrefix + "GenericFail", stackFrames[1].Callable.FullName); + Assert.Equal(namespacePrefix + "GenericAdjFail1", stackFrames[2].Callable.FullName); - Assert.Equal(OperationFunctor.Adjoint, stackFrames[0].operation.Variant); - Assert.Equal(OperationFunctor.Adjoint, stackFrames[1].operation.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); + Assert.Equal(OperationFunctor.Adjoint, stackFrames[0].Callable.Variant); + Assert.Equal(OperationFunctor.Adjoint, stackFrames[1].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].Callable.Variant); - Assert.Equal(6, stackFrames[0].failedLineNumber); - Assert.Equal(24, stackFrames[1].failedLineNumber); - Assert.Equal(52, stackFrames[2].failedLineNumber); + Assert.Equal(6, stackFrames[0].FailedLineNumber); + Assert.Equal(24, stackFrames[1].FailedLineNumber); + Assert.Equal(52, stackFrames[2].FailedLineNumber); } } @@ -156,17 +156,17 @@ public void GenericFail1Test() Assert.Equal(3, stackFrames.Length); - Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].operation.FullName); - Assert.Equal(namespacePrefix + "GenericFail", stackFrames[1].operation.FullName); - Assert.Equal(namespacePrefix + "GenericCtlFail1", stackFrames[2].operation.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].Callable.FullName); + Assert.Equal(namespacePrefix + "GenericFail", stackFrames[1].Callable.FullName); + Assert.Equal(namespacePrefix + "GenericCtlFail1", stackFrames[2].Callable.FullName); - Assert.Equal(OperationFunctor.Controlled, stackFrames[0].operation.Variant); - Assert.Equal(OperationFunctor.Controlled, stackFrames[1].operation.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); + Assert.Equal(OperationFunctor.Controlled, stackFrames[0].Callable.Variant); + Assert.Equal(OperationFunctor.Controlled, stackFrames[1].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].Callable.Variant); - Assert.Equal(6, stackFrames[0].failedLineNumber); - Assert.Equal(24, stackFrames[1].failedLineNumber); - Assert.Equal(56, stackFrames[2].failedLineNumber); + Assert.Equal(6, stackFrames[0].FailedLineNumber); + Assert.Equal(24, stackFrames[1].FailedLineNumber); + Assert.Equal(56, stackFrames[2].FailedLineNumber); } } } @@ -189,17 +189,17 @@ public void PartialFail1Test() Assert.Equal(3, stackFrames.Length); - Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].operation.FullName); - Assert.Equal(namespacePrefix + "PartialFail", stackFrames[1].operation.FullName); - Assert.Equal(namespacePrefix + "PartialFail1", stackFrames[2].operation.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].Callable.FullName); + Assert.Equal(namespacePrefix + "PartialFail", stackFrames[1].Callable.FullName); + Assert.Equal(namespacePrefix + "PartialFail1", stackFrames[2].Callable.FullName); - Assert.Equal(OperationFunctor.Body, stackFrames[0].operation.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[1].operation.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[0].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[1].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].Callable.Variant); - Assert.Equal(7, stackFrames[0].failedLineNumber); - Assert.Equal(33, stackFrames[1].failedLineNumber); - Assert.Equal(38, stackFrames[2].failedLineNumber); + Assert.Equal(7, stackFrames[0].FailedLineNumber); + Assert.Equal(33, stackFrames[1].FailedLineNumber); + Assert.Equal(38, stackFrames[2].FailedLineNumber); } } @@ -216,17 +216,17 @@ public void PartialFail1Test() Assert.Equal(3, stackFrames.Length); - Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].operation.FullName); - Assert.Equal(namespacePrefix + "PartialFail", stackFrames[1].operation.FullName); - Assert.Equal(namespacePrefix + "PartialAdjFail1", stackFrames[2].operation.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].Callable.FullName); + Assert.Equal(namespacePrefix + "PartialFail", stackFrames[1].Callable.FullName); + Assert.Equal(namespacePrefix + "PartialAdjFail1", stackFrames[2].Callable.FullName); - Assert.Equal(OperationFunctor.Adjoint, stackFrames[0].operation.Variant); - Assert.Equal(OperationFunctor.Adjoint, stackFrames[1].operation.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); + Assert.Equal(OperationFunctor.Adjoint, stackFrames[0].Callable.Variant); + Assert.Equal(OperationFunctor.Adjoint, stackFrames[1].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].Callable.Variant); - Assert.Equal(6, stackFrames[0].failedLineNumber); - Assert.Equal(32, stackFrames[1].failedLineNumber); - Assert.Equal(43, stackFrames[2].failedLineNumber); + Assert.Equal(6, stackFrames[0].FailedLineNumber); + Assert.Equal(32, stackFrames[1].FailedLineNumber); + Assert.Equal(43, stackFrames[2].FailedLineNumber); } } @@ -243,17 +243,17 @@ public void PartialFail1Test() Assert.Equal(3, stackFrames.Length); - Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].operation.FullName); - Assert.Equal(namespacePrefix + "PartialFail", stackFrames[1].operation.FullName); - Assert.Equal(namespacePrefix + "PartialCtlFail1", stackFrames[2].operation.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].Callable.FullName); + Assert.Equal(namespacePrefix + "PartialFail", stackFrames[1].Callable.FullName); + Assert.Equal(namespacePrefix + "PartialCtlFail1", stackFrames[2].Callable.FullName); - Assert.Equal(OperationFunctor.Controlled, stackFrames[0].operation.Variant); - Assert.Equal(OperationFunctor.Controlled, stackFrames[1].operation.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); + Assert.Equal(OperationFunctor.Controlled, stackFrames[0].Callable.Variant); + Assert.Equal(OperationFunctor.Controlled, stackFrames[1].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].Callable.Variant); - Assert.Equal(6, stackFrames[0].failedLineNumber); - Assert.Equal(32, stackFrames[1].failedLineNumber); - Assert.Equal(48, stackFrames[2].failedLineNumber); + Assert.Equal(6, stackFrames[0].FailedLineNumber); + Assert.Equal(32, stackFrames[1].FailedLineNumber); + Assert.Equal(48, stackFrames[2].FailedLineNumber); } } } @@ -276,24 +276,32 @@ public void RecursionFail1Test() Assert.Equal(4, stackFrames.Length); - Assert.Equal(namespacePrefix + "RecursionFail", stackFrames[0].operation.FullName); - Assert.Equal(namespacePrefix + "RecursionFail", stackFrames[1].operation.FullName); - Assert.Equal(namespacePrefix + "RecursionFail", stackFrames[2].operation.FullName); - Assert.Equal(namespacePrefix + "RecursionFail1", stackFrames[3].operation.FullName); + Assert.Equal(namespacePrefix + "RecursionFail", stackFrames[0].Callable.FullName); + Assert.Equal(namespacePrefix + "RecursionFail", stackFrames[1].Callable.FullName); + Assert.Equal(namespacePrefix + "RecursionFail", stackFrames[2].Callable.FullName); + Assert.Equal(namespacePrefix + "RecursionFail1", stackFrames[3].Callable.FullName); - Assert.Equal(OperationFunctor.Body, stackFrames[0].operation.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[1].operation.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[2].operation.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[3].operation.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[0].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[1].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[3].Callable.Variant); - Assert.Equal(70, stackFrames[0].failedLineNumber); - Assert.Equal(66, stackFrames[1].failedLineNumber); - Assert.Equal(66, stackFrames[2].failedLineNumber); - Assert.Equal(75, stackFrames[3].failedLineNumber); + Assert.Equal(70, stackFrames[0].FailedLineNumber); + Assert.Equal(66, stackFrames[1].FailedLineNumber); + Assert.Equal(66, stackFrames[2].FailedLineNumber); + Assert.Equal(75, stackFrames[3].FailedLineNumber); } } } + [Fact] + public void UrlMappingTest() + { + const string rawUrl = @"https://raw.githubusercontent.com/microsoft/qsharp-runtime/af6262c05522d645d0a0952272443e84eeab677a/src/Xunit/TestCaseDiscoverer.cs"; + const string expectedURL = @"https://github.com/microsoft/qsharp-runtime/blob/af6262c05522d645d0a0952272443e84eeab677a/src/Xunit/TestCaseDiscoverer.cs#L13"; + Assert.Equal(expectedURL, PortablePdbSymbolReader.TryFormatGitHubUrl(rawUrl, 13)); + } + [Fact] public void ErrorLogTest() { From 99603145d8516b4cc5740d16d18a4bbd3a97d935 Mon Sep 17 00:00:00 2001 From: Vadym Kliuchnikov Date: Wed, 20 Nov 2019 19:03:48 -0800 Subject: [PATCH 16/20] addressing reviewer comments; part3 --- src/Simulation/Common/StackTrace.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Simulation/Common/StackTrace.cs b/src/Simulation/Common/StackTrace.cs index ed9ab930fd5..74999081317 100644 --- a/src/Simulation/Common/StackTrace.cs +++ b/src/Simulation/Common/StackTrace.cs @@ -11,6 +11,11 @@ namespace Microsoft.Quantum.Simulation.Common { + /// + /// 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 + /// method. + /// [Serializable] public class StackFrame { @@ -113,6 +118,9 @@ public virtual string ToStringWithBestSourceLocation() } } + /// + /// Finds correspondence between Q# and C# stack frames and populates Q# stack frame information from C# stack frames + /// public static StackFrame[] PopulateSourceLocations(Stack qsharpCallStack, System.Diagnostics.StackFrame[] csharpCallStack) { foreach (StackFrame currentFrame in qsharpCallStack) From d77eac42cd60a89e71a2f85e1788e298a8573204 Mon Sep 17 00:00:00 2001 From: Andres Paz Date: Sat, 23 Nov 2019 01:28:05 -0800 Subject: [PATCH 17/20] Always enable stack trace. Some extra tests. Minor clean up. --- src/Simulation/Common/SimulatorBase.cs | 50 +++-- src/Simulation/Common/StackTrace.cs | 51 ++++- .../Simulators.Tests/Circuits/Fail.qs | 13 ++ .../Simulators.Tests/StackTraceTests.cs | 189 ++++++++++-------- 4 files changed, 198 insertions(+), 105 deletions(-) diff --git a/src/Simulation/Common/SimulatorBase.cs b/src/Simulation/Common/SimulatorBase.cs index 7a585bb274c..66999f33bac 100644 --- a/src/Simulation/Common/SimulatorBase.cs +++ b/src/Simulation/Common/SimulatorBase.cs @@ -32,6 +32,18 @@ public abstract class SimulatorBase : AbstractFactory, IOperat public abstract string Name { get; } + + /// + /// If the execution finishes in failure, this method returns the call-stack of the Q# operations + /// executed up to the point when the failure happened. + /// If the execution was successful, this method returns null. + /// + /// + /// 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 SimulatorBase(IQubitManager qubitManager = null) { this.QubitManager = qubitManager; @@ -100,27 +112,34 @@ public override AbstractCallable CreateInstance(Type t) public virtual O Execute(I args) where T : AbstractCallable, ICallable { - O res = default(O); + StackTraceCollector stackTraceCollector = new StackTraceCollector(this); var op = Get(); + try { - res = op.Apply(args); + var result = op.Apply(args); + this.CallStack = null; + return result; } catch (Exception e) // Dumps q# call-stack in case of exception if CallStack tracking was enabled { - StackFrame[] qsharpStackFrames = this.CallStack; - if (qsharpStackFrames != null) + this.CallStack = stackTraceCollector.CallStack; + OnLog?.Invoke($"Unhandled exception. {e.GetType().FullName}: {e.Message}"); + bool first = true; + foreach (StackFrame sf in this.CallStack) { - OnLog?.Invoke($"Unhandled Exception: {e.GetType().FullName}: {e.Message}"); - foreach (StackFrame sf in qsharpStackFrames) - { - OnLog?.Invoke(sf.ToStringWithBestSourceLocation()); - } + var msg = (first ? " ---> " : " at ") + sf.ToStringWithBestSourceLocation(); + OnLog?.Invoke(msg); + first = false; } OnLog?.Invoke(""); + throw; } - return res; + finally + { + stackTraceCollector.Dispose(); + } } public virtual Task Run(I args) where T : AbstractCallable, ICallable @@ -366,16 +385,5 @@ public virtual void Fail(System.Runtime.ExceptionServices.ExceptionDispatchInfo { OnFail?.Invoke(exceptionInfo); } - - #region Stack trace collection support - private StackTraceCollector stackTraceCollector = null; - - public void EnableStackTrace() - { - stackTraceCollector = new StackTraceCollector(this); - } - - public StackFrame[] CallStack => stackTraceCollector?.CallStack; - #endregion } } diff --git a/src/Simulation/Common/StackTrace.cs b/src/Simulation/Common/StackTrace.cs index 74999081317..f3da295add4 100644 --- a/src/Simulation/Common/StackTrace.cs +++ b/src/Simulation/Common/StackTrace.cs @@ -82,7 +82,7 @@ public string GetURLFromPDB() return PortablePdbSymbolReader.TryFormatGitHubUrl(result, FailedLineNumber); } - private const string messageFormat = "in {0} on {1}"; + private const string messageFormat = "{0} on {1}"; public override string ToString() { @@ -167,22 +167,41 @@ public static StackFrame[] PopulateSourceLocations(Stack qsharpCallS } /// - /// Tracks Q# call-stack till the first failure resulting in + /// Tracks Q# operations call-stack till the first failure resulting in /// event invocation. /// - public class StackTraceCollector + /// + /// 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 class StackTraceCollector : IDisposable { private readonly Stack callStack; + private readonly SimulatorBase sim; private System.Diagnostics.StackFrame[] frames = null; StackFrame[] stackFramesWithLocations = null; bool hasFailed = false; public StackTraceCollector(SimulatorBase sim) + { + callStack = new Stack(); + this.sim = sim; + + this.Start(); + } + + private void Start() { sim.OnOperationStart += this.OnOperationStart; sim.OnOperationEnd += this.OnOperationEnd; sim.OnFail += this.OnFail; - callStack = new Stack(); + } + + private void Stop() + { + sim.OnOperationStart -= this.OnOperationStart; + sim.OnOperationEnd -= this.OnOperationEnd; + sim.OnFail -= this.OnFail; } void OnOperationStart(ICallable callable, IApplyData arg) @@ -251,5 +270,29 @@ public StackFrame[] CallStack } } } + + #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 } } diff --git a/src/Simulation/Simulators.Tests/Circuits/Fail.qs b/src/Simulation/Simulators.Tests/Circuits/Fail.qs index 4d20fdea39c..ce8e75ca30c 100644 --- a/src/Simulation/Simulators.Tests/Circuits/Fail.qs +++ b/src/Simulation/Simulators.Tests/Circuits/Fail.qs @@ -74,4 +74,17 @@ namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { operation RecursionFail1() : Unit { RecursionFail(2); } + + operation DivideBy0() : Int { + let z = 0; + return 3 / z; + } + + operation AllGood() : Unit { + Microsoft.Quantum.Intrinsic.Message("All good!"); + } + + operation AllGood1() : Unit { + AllGood(); + } } \ No newline at end of file diff --git a/src/Simulation/Simulators.Tests/StackTraceTests.cs b/src/Simulation/Simulators.Tests/StackTraceTests.cs index 2917aec09cd..e7d28b2d49b 100644 --- a/src/Simulation/Simulators.Tests/StackTraceTests.cs +++ b/src/Simulation/Simulators.Tests/StackTraceTests.cs @@ -12,6 +12,7 @@ using Microsoft.Quantum.Simulation.Simulators.Exceptions; using Xunit.Abstractions; using System.Text; +using System.Collections.Generic; namespace Microsoft.Quantum.Simulation.Simulators.Tests { @@ -28,58 +29,60 @@ public StackTraceTests(ITestOutputHelper output) [Fact] public void AlwaysFail4Test() { - ToffoliSimulator sim = new ToffoliSimulator(); - StackTraceCollector sc = new StackTraceCollector(sim); - ICallable op = sim.Get(); - try - { - QVoid res = op.Apply(QVoid.Instance); - } - catch (ExecutionFailException) + using (var sim = new QuantumSimulator()) { - StackFrame[] stackFrames = sc.CallStack; - - Assert.Equal(5, stackFrames.Length); - - Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].Callable.FullName); - Assert.Equal(namespacePrefix + "AlwaysFail1", stackFrames[1].Callable.FullName); - Assert.Equal(namespacePrefix + "AlwaysFail2", stackFrames[2].Callable.FullName); - Assert.Equal(namespacePrefix + "AlwaysFail3", stackFrames[3].Callable.FullName); - Assert.Equal(namespacePrefix + "AlwaysFail4", stackFrames[4].Callable.FullName); - - Assert.Equal(OperationFunctor.Controlled, stackFrames[0].Callable.Variant); - Assert.Equal(OperationFunctor.Controlled, stackFrames[1].Callable.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[2].Callable.Variant); - Assert.Equal(OperationFunctor.Adjoint, stackFrames[3].Callable.Variant); - Assert.Equal(OperationFunctor.Body, stackFrames[4].Callable.Variant); - - Assert.Equal(14, stackFrames[2].FailedLineNumber); - Assert.Equal(21, stackFrames[4].FailedLineNumber); - - // For Adjoint and Controlled we expect failedLineNumber to be equal to declarationStartLineNumber - Assert.Equal(stackFrames[0].DeclarationStartLineNumber, stackFrames[0].FailedLineNumber); - Assert.Equal(stackFrames[1].DeclarationStartLineNumber, stackFrames[1].FailedLineNumber); - Assert.Equal(stackFrames[3].DeclarationStartLineNumber, stackFrames[3].FailedLineNumber); - - for (int i = 0; i < stackFrames.Length; ++i) + try { - Assert.StartsWith(@"https://github.com/", stackFrames[i].GetURLFromPDB()); - Assert.EndsWith($"#L{stackFrames[i].FailedLineNumber}", stackFrames[i].GetURLFromPDB()); + QVoid res = AlwaysFail4.Run(sim).Result; } + catch (AggregateException ex) + { + Assert.True(ex.InnerException is ExecutionFailException); - StringBuilder builder = new StringBuilder(); - builder.Append("13 ".PadLeft(PortablePDBEmbeddedFilesCache.lineNumberPaddingWidth)); - builder.AppendLine(" operation AlwaysFail2() : Unit is Adj + Ctl {"); - builder.Append("14 ".PadLeft(PortablePDBEmbeddedFilesCache.lineNumberPaddingWidth) + PortablePDBEmbeddedFilesCache.lineMarkPrefix); - builder.AppendLine(" Controlled AlwaysFail1(new Qubit[0],());"); - builder.Append("15 ".PadLeft(PortablePDBEmbeddedFilesCache.lineNumberPaddingWidth)); - builder.AppendLine(" }"); - Assert.Equal(builder.ToString(), stackFrames[2].GetOperationSourceFromPDB()); + StackFrame[] stackFrames = sim.CallStack; - for( int i = 0; i < stackFrames.Length; ++i ) - { - output.WriteLine($"operation:{stackFrames[i].Callable.FullName}"); - output.WriteLine(stackFrames[i].GetOperationSourceFromPDB()); + Assert.Equal(5, stackFrames.Length); + + Assert.Equal(namespacePrefix + "AlwaysFail", stackFrames[0].Callable.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail1", stackFrames[1].Callable.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail2", stackFrames[2].Callable.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail3", stackFrames[3].Callable.FullName); + Assert.Equal(namespacePrefix + "AlwaysFail4", stackFrames[4].Callable.FullName); + + Assert.Equal(OperationFunctor.Controlled, stackFrames[0].Callable.Variant); + Assert.Equal(OperationFunctor.Controlled, stackFrames[1].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[2].Callable.Variant); + Assert.Equal(OperationFunctor.Adjoint, stackFrames[3].Callable.Variant); + Assert.Equal(OperationFunctor.Body, stackFrames[4].Callable.Variant); + + Assert.Equal(14, stackFrames[2].FailedLineNumber); + Assert.Equal(21, stackFrames[4].FailedLineNumber); + + // For Adjoint and Controlled we expect failedLineNumber to be equal to declarationStartLineNumber + Assert.Equal(stackFrames[0].DeclarationStartLineNumber, stackFrames[0].FailedLineNumber); + Assert.Equal(stackFrames[1].DeclarationStartLineNumber, stackFrames[1].FailedLineNumber); + Assert.Equal(stackFrames[3].DeclarationStartLineNumber, stackFrames[3].FailedLineNumber); + + for (int i = 0; i < stackFrames.Length; ++i) + { + Assert.StartsWith(@"https://github.com/", stackFrames[i].GetURLFromPDB()); + Assert.EndsWith($"#L{stackFrames[i].FailedLineNumber}", stackFrames[i].GetURLFromPDB()); + } + + StringBuilder builder = new StringBuilder(); + builder.Append("13 ".PadLeft(PortablePDBEmbeddedFilesCache.lineNumberPaddingWidth)); + builder.AppendLine(" operation AlwaysFail2() : Unit is Adj + Ctl {"); + builder.Append("14 ".PadLeft(PortablePDBEmbeddedFilesCache.lineNumberPaddingWidth) + PortablePDBEmbeddedFilesCache.lineMarkPrefix); + builder.AppendLine(" Controlled AlwaysFail1(new Qubit[0],());"); + builder.Append("15 ".PadLeft(PortablePDBEmbeddedFilesCache.lineNumberPaddingWidth)); + builder.AppendLine(" }"); + Assert.Equal(builder.ToString(), stackFrames[2].GetOperationSourceFromPDB()); + + for (int i = 0; i < stackFrames.Length; ++i) + { + output.WriteLine($"operation:{stackFrames[i].Callable.FullName}"); + output.WriteLine(stackFrames[i].GetOperationSourceFromPDB()); + } } } } @@ -87,18 +90,16 @@ public void AlwaysFail4Test() [Fact] public void GenericFail1Test() { - ToffoliSimulator sim = new ToffoliSimulator(); + ResourcesEstimator sim = new ResourcesEstimator(); { - StackTraceCollector sc = new StackTraceCollector(sim); - ICallable op = sim.Get(); try { - QVoid res = op.Apply(QVoid.Instance); + QVoid res = sim.Execute(QVoid.Instance); } catch (ExecutionFailException) { - StackFrame[] stackFrames = sc.CallStack; + StackFrame[] stackFrames = sim.CallStack; Assert.Equal(3, stackFrames.Length); @@ -117,15 +118,13 @@ public void GenericFail1Test() } { - StackTraceCollector sc = new StackTraceCollector(sim); - ICallable op = sim.Get(); try { - QVoid res = op.Apply(QVoid.Instance); + QVoid res = sim.Execute(QVoid.Instance); } catch (ExecutionFailException) { - StackFrame[] stackFrames = sc.CallStack; + StackFrame[] stackFrames = sim.CallStack; Assert.Equal(3, stackFrames.Length); @@ -144,15 +143,13 @@ public void GenericFail1Test() } { - StackTraceCollector sc = new StackTraceCollector(sim); - ICallable op = sim.Get(); try { - QVoid res = op.Apply(QVoid.Instance); + QVoid res = sim.Execute(QVoid.Instance); } catch (ExecutionFailException) { - StackFrame[] stackFrames = sc.CallStack; + StackFrame[] stackFrames = sim.CallStack; Assert.Equal(3, stackFrames.Length); @@ -170,22 +167,21 @@ public void GenericFail1Test() } } } - + [Fact] public void PartialFail1Test() { ToffoliSimulator sim = new ToffoliSimulator(); { - StackTraceCollector sc = new StackTraceCollector(sim); - ICallable op = sim.Get(); try { - QVoid res = op.Apply(QVoid.Instance); + QVoid res = PartialFail1.Run(sim).Result; } - catch (ExecutionFailException) + catch (AggregateException ex) { - StackFrame[] stackFrames = sc.CallStack; + Assert.True(ex.InnerException is ExecutionFailException); + StackFrame[] stackFrames = sim.CallStack; Assert.Equal(3, stackFrames.Length); @@ -204,15 +200,14 @@ public void PartialFail1Test() } { - StackTraceCollector sc = new StackTraceCollector(sim); - ICallable op = sim.Get(); try { - QVoid res = op.Apply(QVoid.Instance); + QVoid res = PartialAdjFail1.Run(sim).Result; } - catch (ExecutionFailException) + catch (AggregateException ex) { - StackFrame[] stackFrames = sc.CallStack; + Assert.True(ex.InnerException is ExecutionFailException); + StackFrame[] stackFrames = sim.CallStack; Assert.Equal(3, stackFrames.Length); @@ -231,15 +226,14 @@ public void PartialFail1Test() } { - StackTraceCollector sc = new StackTraceCollector(sim); - ICallable op = sim.Get(); try { - QVoid res = op.Apply(QVoid.Instance); + QVoid res = PartialCtlFail1.Run(sim).Result; } - catch (ExecutionFailException) + catch (AggregateException ex) { - StackFrame[] stackFrames = sc.CallStack; + Assert.True(ex.InnerException is ExecutionFailException); + StackFrame[] stackFrames = sim.CallStack; Assert.Equal(3, stackFrames.Length); @@ -294,6 +288,34 @@ public void RecursionFail1Test() } } + [Fact] + public void DivideByZeroTest() + { + ToffoliSimulator sim = new ToffoliSimulator(); + + try + { + sim.Execute(QVoid.Instance); + } + catch (Exception) + { + StackFrame[] stackFrames = sim.CallStack; + + Assert.Single(stackFrames); + Assert.Equal(namespacePrefix + "DivideBy0", stackFrames[0].Callable.FullName); + } + } + + [Fact] + public void AllGoodTest() + { + ToffoliSimulator sim = new ToffoliSimulator(); + + QVoid res = sim.Execute(QVoid.Instance); + StackFrame[] stackFrames = sim.CallStack; + Assert.Null(stackFrames); + } + [Fact] public void UrlMappingTest() { @@ -306,17 +328,24 @@ public void UrlMappingTest() public void ErrorLogTest() { ToffoliSimulator sim = new ToffoliSimulator(); - sim.EnableStackTrace(); - StringBuilder stringBuilder = new StringBuilder(); - sim.OnLog += (msg) => stringBuilder.AppendLine(msg); + + var logs = new List(); + sim.OnLog += (msg) => logs.Add(msg); try { QVoid res = sim.Execute(QVoid.Instance); } catch (ExecutionFailException) { + Assert.Equal(7, logs.Count); + Assert.StartsWith("Unhandled exception. Microsoft.Quantum.Simulation.Core.ExecutionFailException: Always fail", logs[0]); + Assert.StartsWith(" ---> Microsoft.Quantum.Simulation.Simulators.Tests.Circuits.AlwaysFail", logs[1]); + Assert.StartsWith(" at Microsoft.Quantum.Simulation.Simulators.Tests.Circuits.AlwaysFail1 on", logs[2]); + Assert.StartsWith(" at Microsoft.Quantum.Simulation.Simulators.Tests.Circuits.AlwaysFail2 on", logs[3]); + Assert.StartsWith(" at Microsoft.Quantum.Simulation.Simulators.Tests.Circuits.AlwaysFail3 on", logs[4]); + Assert.StartsWith(" at Microsoft.Quantum.Simulation.Simulators.Tests.Circuits.AlwaysFail4 on", logs[5]); + Assert.Equal("", logs[6]); } - output.WriteLine(stringBuilder.ToString()); } } } \ No newline at end of file From cad26d9a27ed15a1ca024319409cc722a2c720d3 Mon Sep 17 00:00:00 2001 From: Vadym Kliuchnikov Date: Sat, 23 Nov 2019 22:08:03 -0800 Subject: [PATCH 18/20] fixing out of bound issue when Q# and C# stack traces are merged --- src/Simulation/Common/StackTrace.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Simulation/Common/StackTrace.cs b/src/Simulation/Common/StackTrace.cs index f3da295add4..c667448b747 100644 --- a/src/Simulation/Common/StackTrace.cs +++ b/src/Simulation/Common/StackTrace.cs @@ -159,6 +159,7 @@ public static StackFrame[] PopulateSourceLocations(Stack qsharpCallS { currentQsharpStackFrame.FailedLineNumber = failedLineNumber; qsharpStackFrameId++; + if (qsharpStackFrameId == qsharpStackFrames.Length) break; } } } From f722105cc2a755fc7fcd07208b3e07424f050870 Mon Sep 17 00:00:00 2001 From: Andres Paz Date: Sun, 24 Nov 2019 21:42:41 -0800 Subject: [PATCH 19/20] Update src/Simulation/Common/PortablePDBReader.cs --- src/Simulation/Common/PortablePDBReader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Simulation/Common/PortablePDBReader.cs b/src/Simulation/Common/PortablePDBReader.cs index 4ac73c1fccd..15aed0332d0 100644 --- a/src/Simulation/Common/PortablePDBReader.cs +++ b/src/Simulation/Common/PortablePDBReader.cs @@ -133,7 +133,7 @@ public static Dictionary GetEmbeddedFiles(string p } /// - /// Returns SourceLink information, that is JSON string with schema described at https://github.com/dotnet/designs/blob/master/accepted/diagnostics/source-link.md#source-link-json-schema + /// Returns SourceLink information, that is the source link information as described at https://github.com/dotnet/designs/blob/master/accepted/diagnostics/source-link.md#source-link-json-schema /// stored in PortablePDB. /// The function will throw an exception if PortablePDB file is not found or anything else went wrong. /// @@ -393,4 +393,4 @@ public static string GetEmbeddedFileRange(string pdbLocation, string fullName, i return builder.ToString(); } } -} \ No newline at end of file +} From e01d0d8a7eab762348795c50c8a123924723c519 Mon Sep 17 00:00:00 2001 From: Andres Paz Date: Sun, 24 Nov 2019 21:50:27 -0800 Subject: [PATCH 20/20] Minor renames. --- src/Simulation/Common/PortablePDBReader.cs | 46 +++++++++++----------- src/Simulation/Common/StackTrace.cs | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Simulation/Common/PortablePDBReader.cs b/src/Simulation/Common/PortablePDBReader.cs index 15aed0332d0..6db1b53b108 100644 --- a/src/Simulation/Common/PortablePDBReader.cs +++ b/src/Simulation/Common/PortablePDBReader.cs @@ -45,7 +45,7 @@ public override string ToString() /// https://github.com/dotnet/designs/blob/master/accepted/diagnostics/source-link.md#source-link-json-schema /// [Serializable] - public class SourceLinkPathRemapping + public class SourceLinkInfo { public Dictionary documents; @@ -138,7 +138,7 @@ public static Dictionary GetEmbeddedFiles(string p /// The function will throw an exception if PortablePDB file is not found or anything else went wrong. /// /// Path to PortablePDB file - public static SourceLinkPathRemapping GetSourceLinkString(string pdbFilePath) + public static SourceLinkInfo GetSourceLinkInfo(string pdbFilePath) { using (FileStream stream = File.OpenRead(pdbFilePath)) { @@ -153,7 +153,7 @@ public static SourceLinkPathRemapping GetSourceLinkString(string pdbFilePath) if (metadataReader.GetGuid(customDebugInformation.Kind) == SourceLink) { string jsonString = Encoding.UTF8.GetString(metadataReader.GetBlobBytes(customDebugInformation.Value)); - return Newtonsoft.Json.JsonConvert.DeserializeObject(jsonString); + return Newtonsoft.Json.JsonConvert.DeserializeObject(jsonString); } } } @@ -233,24 +233,24 @@ public static string GetPDBLocation(ICallable callable) /// /// Caches path remapping from build machine to URL per location of PDB file. /// - public static class PortablePDBPathRemappingCache + public static class PortablePDBSourceLinkInfoCache { /// /// Key is the location of a PortablePDB file on a current machine /// // ThreadStaticAttribute makes sure that the cache is thread safe [ThreadStatic] - private static Dictionary pdbLocationToPathRemapping = null; + private static Dictionary _cache = null; - public static SourceLinkPathRemapping GetRemappingInfromation(string pdbLocation) + public static SourceLinkInfo GetSourceLinkInfo(string pdbLocation) { - if (pdbLocationToPathRemapping == null) + if (_cache == null) { - pdbLocationToPathRemapping = new Dictionary(); + _cache = new Dictionary(); } - SourceLinkPathRemapping remappings; - if (pdbLocationToPathRemapping.TryGetValue(pdbLocation, out remappings)) + SourceLinkInfo remappings; + if (_cache.TryGetValue(pdbLocation, out remappings)) { return remappings; } @@ -258,11 +258,11 @@ public static SourceLinkPathRemapping GetRemappingInfromation(string pdbLocation { try { - remappings = PortablePdbSymbolReader.GetSourceLinkString(pdbLocation); + remappings = PortablePdbSymbolReader.GetSourceLinkInfo(pdbLocation); } finally { - pdbLocationToPathRemapping.Add(pdbLocation, remappings); + _cache.Add(pdbLocation, remappings); } return remappings; } @@ -275,17 +275,17 @@ public static string TryGetFileUrl(string pdbLocation, string fileName) { if (fileName == null) return null; - SourceLinkPathRemapping remapping = GetRemappingInfromation(pdbLocation); - if (remapping != null) + SourceLinkInfo sourceLinks = GetSourceLinkInfo(pdbLocation); + if (sourceLinks != null) { - if (remapping.documents.ContainsKey(fileName)) + if (sourceLinks.documents.ContainsKey(fileName)) { - return remapping.documents[fileName]; + return sourceLinks.documents[fileName]; } - if (remapping.Patterns != null) + if (sourceLinks.Patterns != null) { - foreach (ValueTuple replacement in remapping.Patterns) + foreach (ValueTuple replacement in sourceLinks.Patterns) { if (fileName.StartsWith(replacement.Item1)) { @@ -320,20 +320,20 @@ public static class PortablePDBEmbeddedFilesCache /// // ThreadStaticAttribute makes sure that the cache is thread safe [ThreadStatic] - private static Dictionary> pdbLocationToEmbeddedFiles = null; + private static Dictionary> _cache = null; /// /// Returns cached result of calling /// public static Dictionary GetEmbeddedFiles(string pdbLocation) { - if (pdbLocationToEmbeddedFiles == null) + if (_cache == null) { - pdbLocationToEmbeddedFiles = new Dictionary>(); + _cache = new Dictionary>(); } Dictionary embeddedFilesFromPath = null; - if (pdbLocationToEmbeddedFiles.TryGetValue(pdbLocation, out embeddedFilesFromPath)) + if (_cache.TryGetValue(pdbLocation, out embeddedFilesFromPath)) { return embeddedFilesFromPath; } @@ -345,7 +345,7 @@ public static Dictionary GetEmbeddedFiles(string p } finally { - pdbLocationToEmbeddedFiles.Add(pdbLocation, embeddedFilesFromPath); + _cache.Add(pdbLocation, embeddedFilesFromPath); } return embeddedFilesFromPath; } diff --git a/src/Simulation/Common/StackTrace.cs b/src/Simulation/Common/StackTrace.cs index c667448b747..2251de7d915 100644 --- a/src/Simulation/Common/StackTrace.cs +++ b/src/Simulation/Common/StackTrace.cs @@ -78,7 +78,7 @@ public string GetOperationSourceFromPDB() public string GetURLFromPDB() { string pdbFileLocation = PortablePdbSymbolReader.GetPDBLocation(Callable); - string result = PortablePDBPathRemappingCache.TryGetFileUrl(pdbFileLocation, SourceFile); + string result = PortablePDBSourceLinkInfoCache.TryGetFileUrl(pdbFileLocation, SourceFile); return PortablePdbSymbolReader.TryFormatGitHubUrl(result, FailedLineNumber); }