diff --git a/build/package_versions.settings.targets b/build/package_versions.settings.targets index 5add95409..c1eee41bb 100644 --- a/build/package_versions.settings.targets +++ b/build/package_versions.settings.targets @@ -33,7 +33,7 @@ 15.0.392 15.0.392 17.0.0-previews-1-31410-258 - 17.0.1600 + 17.3.2093 4.3.0 \ No newline at end of file diff --git a/build/version.settings.targets b/build/version.settings.targets index 6e2da2b5c..7f0008e95 100644 --- a/build/version.settings.targets +++ b/build/version.settings.targets @@ -3,7 +3,7 @@ 17 - 1 + 4 2020 diff --git a/src/MIDebugEngine/Engine.Impl/Variables.cs b/src/MIDebugEngine/Engine.Impl/Variables.cs index d420beb3d..f087bd593 100644 --- a/src/MIDebugEngine/Engine.Impl/Variables.cs +++ b/src/MIDebugEngine/Engine.Impl/Variables.cs @@ -431,7 +431,13 @@ private string StripFormatSpecifier(string exp, out string formatSpecifier) // TODO: could return '(T(*)[n])(exp)' but requires T var m = Regex.Match(trimmed, @"^\[?(\d+)\]?$"); if (m.Success) + { + if (_engine.DebuggedProcess.MICommandFactory.Mode == MIMode.Gdb) + return "*" + exp.Substring(0, lastComma) + "@" + trimmed; // this does not work for lldb + return exp.Substring(0, lastComma); + } + // array with dynamic size if (Regex.Match(trimmed, @"^\[([a-zA-Z_][a-zA-Z_\d]*)\]$").Success) @@ -518,13 +524,8 @@ internal async Task Eval(uint radix, enum_EVALFLAGS dwFlags = 0, DAPEvalFlags dw string consoleResults = null; consoleResults = await MIDebugCommandDispatcher.ExecuteCommand(consoleCommand, _debuggedProcess, ignoreFailures: true); - Value = String.Empty; + Value = consoleResults; this.TypeName = null; - - if (!String.IsNullOrEmpty(consoleResults)) - { - _debuggedProcess.WriteOutput(consoleResults); - } } else { diff --git a/src/MIDebugEngine/Natvis.Impl/Natvis.cs b/src/MIDebugEngine/Natvis.Impl/Natvis.cs index 9c201d0e0..1abb2d780 100755 --- a/src/MIDebugEngine/Natvis.Impl/Natvis.cs +++ b/src/MIDebugEngine/Natvis.Impl/Natvis.cs @@ -72,7 +72,7 @@ public uint Size() } } - internal sealed class VisualizerWrapper : SimpleWrapper + internal class VisualizerWrapper : SimpleWrapper { public readonly Natvis.VisualizerInfo Visualizer; private readonly bool _isVisualizerView; @@ -90,6 +90,60 @@ public override string FullName() return _isVisualizerView ? Parent.Name + ",viz" : Name; } } + /// + /// Represents a VisualizedWrapper that starts at an offset. + /// + internal class PaginatedVisualizerWrapper : VisualizerWrapper + { + public readonly uint StartIndex; + + public PaginatedVisualizerWrapper(string name, AD7Engine engine, IVariableInformation underlyingVariable, Natvis.VisualizerInfo viz, bool isVisualizerView, uint startIndex=0) + : base(name, engine, underlyingVariable, viz, isVisualizerView) + { + StartIndex = startIndex; + } + } + /// + /// Represents the continuation of a LinkedListItemsType. + /// + internal sealed class LinkedListContinueWrapper : PaginatedVisualizerWrapper + { + public readonly IVariableInformation ContinueNode; + public LinkedListContinueWrapper(string name, AD7Engine engine, IVariableInformation underlyingVariable, Natvis.VisualizerInfo viz, bool isVisualizerView, IVariableInformation continueNode, uint startIndex) + : base(name, engine, underlyingVariable, viz, isVisualizerView, startIndex) + { + ContinueNode = continueNode; + } + } + + internal class Node + { + public enum ScanState + { + left, value, right + } + public ScanState State { get; set; } + public IVariableInformation Content { get; private set; } + public Node(IVariableInformation v) + { + Content = v; + State = ScanState.left; + } + } + /// + /// Represents the continuation of a TreeItemsType. + /// + internal sealed class TreeContinueWrapper : PaginatedVisualizerWrapper + { + public readonly Node ContinueNode; + public readonly Stack Nodes; + public TreeContinueWrapper(string name, AD7Engine engine, IVariableInformation underlyingVariable, Natvis.VisualizerInfo viz, bool isVisualizerView, Node continueNode, Stack nodes, uint startIndex) + : base (name, engine, underlyingVariable, viz, isVisualizerView, startIndex) + { + ContinueNode = continueNode; + Nodes = nodes; + } + } internal struct VisualizerId { @@ -506,7 +560,7 @@ private IVariableInformation[] ExpandVisualized(IVariableInformation variable) } foreach (var i in expandType.Items) { - if (i is ItemType) + if (i is ItemType && !(variable is PaginatedVisualizerWrapper)) // we do not want to repeatedly display other ItemTypes when expanding the "[More...]" node { ItemType item = (ItemType)i; if (!EvalCondition(item.Condition, variable, visualizer.ScopedNames)) @@ -526,7 +580,6 @@ private IVariableInformation[] ExpandVisualized(IVariableInformation variable) uint size = 0; string val = GetExpressionValue(item.Size, variable, visualizer.ScopedNames); size = MICore.Debugger.ParseUint(val, throwOnError: true); - size = size > MAX_EXPAND ? MAX_EXPAND : size; // limit expansion ValuePointerType[] vptrs = item.ValuePointer; foreach (var vp in vptrs) { @@ -551,7 +604,23 @@ private IVariableInformation[] ExpandVisualized(IVariableInformation variable) arrayExpr.EnsureChildren(); if (arrayExpr.CountChildren != 0) { - children.AddRange(arrayExpr.Children); + uint currentIndex = 0; + if (variable is PaginatedVisualizerWrapper pvwVariable) + { + currentIndex = pvwVariable.StartIndex; + } + uint maxIndex = currentIndex + MAX_EXPAND > size ? size : currentIndex + MAX_EXPAND; + for (uint index = currentIndex; index < maxIndex; ++index) + { + children.Add(arrayExpr.Children[index]); + } + + currentIndex += MAX_EXPAND; + if (size > currentIndex) + { + IVariableInformation moreVariable = new PaginatedVisualizerWrapper(ResourceStrings.MoreView, _process.Engine, variable, FindType(variable), isVisualizerView: true, currentIndex); + children.Add(moreVariable); + } } break; } @@ -575,8 +644,15 @@ private IVariableInformation[] ExpandVisualized(IVariableInformation variable) } string val = GetExpressionValue(item.Size, variable, visualizer.ScopedNames); uint size = MICore.Debugger.ParseUint(val, throwOnError: true); - size = size > MAX_EXPAND ? MAX_EXPAND : size; // limit expansion - IVariableInformation headVal = GetExpression(item.HeadPointer, variable, visualizer.ScopedNames); + IVariableInformation headVal; + if (variable is TreeContinueWrapper tcw) + { + headVal = tcw.ContinueNode.Content; + } + else + { + headVal = GetExpression(item.HeadPointer, variable, visualizer.ScopedNames); + } ulong head = MICore.Debugger.ParseAddr(headVal.Value); var content = new List(); if (head != 0 && size != 0) @@ -601,7 +677,18 @@ private IVariableInformation[] ExpandVisualized(IVariableInformation variable) { continue; } - TraverseTree(headVal, goLeft, goRight, getValue, children, size); + uint startIndex = 0; + IVariableInformation parent = variable; + if (variable is PaginatedVisualizerWrapper visualizerWrapper) + { + startIndex = visualizerWrapper.StartIndex; + parent = visualizerWrapper.Parent; + } + else if (variable is SimpleWrapper simpleWrapper) + { + parent = simpleWrapper.Parent; + } + TraverseTree(headVal, goLeft, goRight, getValue, children, size, variable, parent, startIndex); } } else if (i is LinkedListItemsType) @@ -632,9 +719,16 @@ private IVariableInformation[] ExpandVisualized(IVariableInformation variable) { string val = GetExpressionValue(item.Size, variable, visualizer.ScopedNames); size = MICore.Debugger.ParseUint(val); - size = size > MAX_EXPAND ? MAX_EXPAND : size; // limit expansion } - IVariableInformation headVal = GetExpression(item.HeadPointer, variable, visualizer.ScopedNames); + IVariableInformation headVal; + if (variable is LinkedListContinueWrapper llcw) + { + headVal = llcw.ContinueNode; + } + else + { + headVal = GetExpression(item.HeadPointer, variable, visualizer.ScopedNames); + } ulong head = MICore.Debugger.ParseAddr(headVal.Value); var content = new List(); if (head != 0 && size != 0) @@ -662,7 +756,18 @@ private IVariableInformation[] ExpandVisualized(IVariableInformation variable) { continue; } - TraverseList(headVal, goNext, getValue, children, size, item.NoValueHeadPointer); + uint startIndex = 0; + IVariableInformation parent = variable; + if (variable is PaginatedVisualizerWrapper visualizerWrapper) + { + startIndex = visualizerWrapper.StartIndex; + parent = visualizerWrapper.Parent; + } + else if (variable is SimpleWrapper simpleWrapper) + { + parent = simpleWrapper.Parent; + } + TraverseList(headVal, goNext, getValue, children, size, item.NoValueHeadPointer, parent, startIndex); } } else if (i is IndexListItemsType) @@ -691,7 +796,6 @@ private IVariableInformation[] ExpandVisualized(IVariableInformation variable) { string val = GetExpressionValue(s.Value, variable, visualizer.ScopedNames); size = MICore.Debugger.ParseUint(val); - size = size > MAX_EXPAND ? MAX_EXPAND : size; // limit expansion break; } } @@ -708,7 +812,13 @@ private IVariableInformation[] ExpandVisualized(IVariableInformation variable) { string processedExpr = ReplaceNamesInExpression(v.Value, variable, visualizer.ScopedNames); Dictionary indexDic = new Dictionary(); - for (uint index = 0; index < size; ++index) + uint currentIndex = 0; + if (variable is PaginatedVisualizerWrapper pvwVariable) + { + currentIndex = pvwVariable.StartIndex; + } + uint maxIndex = currentIndex + MAX_EXPAND > size ? size : currentIndex + MAX_EXPAND; + for (uint index = currentIndex; index < maxIndex; ++index) // limit expansion to first 50 elements { indexDic["$i"] = index.ToString(CultureInfo.InvariantCulture); string finalExpr = ReplaceNamesInExpression(processedExpr, null, indexDic); @@ -716,6 +826,14 @@ private IVariableInformation[] ExpandVisualized(IVariableInformation variable) expressionVariable.SyncEval(); children.Add(expressionVariable); } + + currentIndex += MAX_EXPAND; + if (size > currentIndex) + { + IVariableInformation moreVariable = new PaginatedVisualizerWrapper(ResourceStrings.MoreView, _process.Engine, variable, visualizer, isVisualizerView: true, currentIndex); + children.Add(moreVariable); + } + break; } } @@ -788,27 +906,35 @@ private Traverse GetTraverse(string direction, IVariableInformation node) return go; } - private class Node + /// + /// Traverse tree based on specified startIndex. + /// Then add wrappers for Natvis tree visualizations. + /// + /// Root of the tree + /// Traverse callback to retrieve left child of root + /// Traverse callback to retrieve right child of root + /// Callback to retrieve value of root + /// List of variables to display given current variable + /// Number of nodes in tree + /// Tree to traverse if size <= 50. Otherwise, expandable continue wrapper. + /// The tree to traverse + /// Index to start traversing from + /// + private void TraverseTree(IVariableInformation root, Traverse goLeft, Traverse goRight, Traverse getValue, List content, uint size, IVariableInformation variable, IVariableInformation parent, uint startIndex) { - public enum ScanState + uint i = startIndex; + var nodes = new Stack(); + if (variable is TreeContinueWrapper tcwVariable) { - left, value, right + nodes = tcwVariable.Nodes; } - public ScanState State { get; set; } - public IVariableInformation Content { get; private set; } - public Node(IVariableInformation v) + else { - Content = v; - State = ScanState.left; + nodes.Push(new Node(root)); } - } - private void TraverseTree(IVariableInformation root, Traverse goLeft, Traverse goRight, Traverse getValue, List content, uint size) - { - uint i = 0; - var nodes = new Stack(); - nodes.Push(new Node(root)); - while (nodes.Count > 0 && i < size) + uint maxIndex = i + MAX_EXPAND > size ? size : i + MAX_EXPAND; + while (nodes.Count > 0 && i < maxIndex) { switch (nodes.Peek().State) { @@ -847,15 +973,22 @@ private void TraverseTree(IVariableInformation root, Traverse goLeft, Traverse g break; } } + if (size > i) + { + IVariableInformation tcw = new TreeContinueWrapper(ResourceStrings.MoreView, _process.Engine, parent, FindType(parent), isVisualizerView: true, nodes.Peek(), nodes, i); + content.Add(tcw); + } } - private void TraverseList(IVariableInformation root, Traverse goNext, Traverse getValue, List content, uint size, bool noValueInRoot) + private void TraverseList(IVariableInformation root, Traverse goNext, Traverse getValue, List content, uint size, bool noValueInRoot, IVariableInformation parent, uint startIndex) { - uint i = 0; + uint i = startIndex; IVariableInformation node = root; ulong rootAddr = MICore.Debugger.ParseAddr(node.Value); ulong nextAddr = rootAddr; - while (node != null && nextAddr != 0 && i < size) + + uint maxIndex = i + MAX_EXPAND > size ? size : i + MAX_EXPAND; + while (node != null && nextAddr != 0 && i < maxIndex) { if (!noValueInRoot || nextAddr != rootAddr) { @@ -866,7 +999,7 @@ private void TraverseList(IVariableInformation root, Traverse goNext, Traverse g i++; } } - if (i < size) + if (i < maxIndex) { node = goNext(node); } @@ -877,6 +1010,11 @@ private void TraverseList(IVariableInformation root, Traverse goNext, Traverse g break; } } + if (size > i) + { + IVariableInformation llcw = new LinkedListContinueWrapper(ResourceStrings.MoreView, _process.Engine, parent, FindType(parent), isVisualizerView: true, goNext(node), i); + content.Add(llcw); + } } private static string BaseName(string type) diff --git a/src/MIDebugEngine/ResourceStrings.Designer.cs b/src/MIDebugEngine/ResourceStrings.Designer.cs index a084c9a27..12d25ce8d 100755 --- a/src/MIDebugEngine/ResourceStrings.Designer.cs +++ b/src/MIDebugEngine/ResourceStrings.Designer.cs @@ -301,6 +301,15 @@ internal static string ModuleUnloadMessage { } } + /// + /// Looks up a localized string similar to [More...]. + /// + internal static string MoreView { + get { + return ResourceManager.GetString("MoreView", resourceCulture); + } + } + /// /// Looks up a localized string similar to Explicit refresh required for visualized expressions. /// diff --git a/src/MIDebugEngine/ResourceStrings.resx b/src/MIDebugEngine/ResourceStrings.resx index fc9111daf..ea389d970 100755 --- a/src/MIDebugEngine/ResourceStrings.resx +++ b/src/MIDebugEngine/ResourceStrings.resx @@ -156,6 +156,9 @@ Symbols loaded. + + [More...] + Symbols loaded - {0} diff --git a/src/OpenDebugAD7/AD7DebugSession.cs b/src/OpenDebugAD7/AD7DebugSession.cs index 5e0720b9f..425061684 100644 --- a/src/OpenDebugAD7/AD7DebugSession.cs +++ b/src/OpenDebugAD7/AD7DebugSession.cs @@ -811,7 +811,6 @@ private void StepInternal(int threadId, enum_STEPKIND stepKind, SteppingGranular } } - BeforeContinue(); ErrorBuilder builder = new ErrorBuilder(() => errorMessage); m_isStepping = true; @@ -837,6 +836,9 @@ private void StepInternal(int threadId, enum_STEPKIND stepKind, SteppingGranular m_isStopped = true; throw; } + // The program should now be stepping, so it is safe to discard the + // cached program state. + BeforeContinue(); } private enum ClientId diff --git a/test/CppTests/Tests/NatvisTests.cs b/test/CppTests/Tests/NatvisTests.cs index d21b1b34f..0a91a3a8d 100644 --- a/test/CppTests/Tests/NatvisTests.cs +++ b/test/CppTests/Tests/NatvisTests.cs @@ -118,14 +118,13 @@ public void TestIndexListItems(ITestSettings settings) this.Comment("Verifying IndexListItems natvis"); var arr = currentFrame.GetVariable("arr"); - Assert.Equal("{ size=15 }", arr.Value); + Assert.Equal("{ size=52 }", arr.Value); // Index element for IndexListItems // Natvis retrieves items in reverse order. - Assert.Equal("196", arr.GetVariable("[0]").Value); - Assert.Equal("16", arr.GetVariable("[10]").Value); - Assert.Equal("0", arr.GetVariable("[14]").Value); - // TODO: Add test below when we can support the [More..] expansion to handle >50 elements + Assert.Equal("2601", arr.GetVariable("[0]").Value); + Assert.Equal("1681", arr.GetVariable("[10]").Value); + Assert.Equal("0", arr.GetVariable("[More...]").GetVariable("[51]").Value); } runner.Expects.ExitedEvent(exitCode: 0).TerminatedEvent().AfterContinue(); @@ -168,14 +167,14 @@ public void TestArrayItems(ITestSettings settings) this.Comment("Verifying ArrayItems natvis"); var ll = currentFrame.GetVariable("vec"); - Assert.Equal("{ size=10 }", ll.Value); + Assert.Equal("{ size=52 }", ll.Value); // Custom Item in natvis - Assert.Equal("10", ll.GetVariable("Size").Value); + Assert.Equal("52", ll.GetVariable("Size").Value); // Index element for ArrayItems Assert.Equal("20", ll.GetVariable("[5]").Value); - // TODO: Add test below when we can support the [More..] expansion to handle >50 elements + Assert.Equal("0", ll.GetVariable("[More...]").GetVariable("[51]").Value); } runner.Expects.ExitedEvent(exitCode: 0).TerminatedEvent().AfterContinue(); @@ -220,8 +219,7 @@ public void TestLinkedListItems(ITestSettings settings) // Index element for LinkedListItems Assert.Equal("5", ll.GetVariable("[5]").Value); - // TODO: Uncomment line below when we can support the [More..] expansion to handle >50 elements - // Assert.Equal("75", ll.GetVariable("[More...]").GetVariable("[75]").Value); + Assert.Equal("75", ll.GetVariable("[More...]").GetVariable("[75]").Value); } runner.Expects.ExitedEvent(exitCode: 0).TerminatedEvent().AfterContinue(); @@ -330,6 +328,45 @@ public void TestThisConditional(ITestSettings settings) } } + [Theory] + [DependsOnTest(nameof(CompileNatvisDebuggee))] + [RequiresTestSettings] + // Disable on macOS + [UnsupportedDebugger(SupportedDebugger.Lldb, SupportedArchitecture.x64 | SupportedArchitecture.x86)] + public void TestArrayPointer(ITestSettings settings) + { + this.TestPurpose("This test checks if the comma format specifier is visualized."); + this.WriteSettings(settings); + + IDebuggee debuggee = Debuggee.Open(this, settings.CompilerSettings, NatvisName, DebuggeeMonikers.Natvis.Default); + + this.Comment("Run the debuggee, check argument count"); + using (IDebuggerRunner runner = CreateDebugAdapterRunner(settings)) + { + this.Comment("Configure launch"); + LaunchCommand launch = new LaunchCommand(settings.DebuggerSettings, debuggee.OutputPath); + runner.RunCommand(launch); + + this.Comment("Set Breakpoint"); + SourceBreakpoints writerBreakpoints = debuggee.Breakpoints(NatvisSourceName, ReturnSourceLine); + runner.SetBreakpoints(writerBreakpoints); + + runner.Expects.StoppedEvent(StoppedReason.Breakpoint).AfterConfigurationDone(); + + using (IThreadInspector threadInspector = runner.GetThreadInspector()) + { + IFrameInspector currentFrame = threadInspector.Stack.First(); + + this.Comment("Verifying comma format specifier"); + int[] expected = { 0, 1, 4, 9 }; + currentFrame.AssertEvaluateAsIntArray("arr._array,4", EvaluateContext.Watch, expected); + } + + runner.Expects.ExitedEvent(exitCode: 0).TerminatedEvent().AfterContinue(); + runner.DisconnectAndVerify(); + } + } + #endregion } } diff --git a/test/CppTests/debuggees/natvis/src/main.cpp b/test/CppTests/debuggees/natvis/src/main.cpp index 191a646bd..4f37b46b7 100644 --- a/test/CppTests/debuggees/natvis/src/main.cpp +++ b/test/CppTests/debuggees/natvis/src/main.cpp @@ -14,7 +14,7 @@ int main(int argc, char** argv) { SimpleDisplayObject obj_1; - SimpleVector vec(10); + SimpleVector vec(52); vec.Set(5, 20); SimpleLinkedList ll; @@ -32,8 +32,8 @@ int main(int argc, char** argv) map.Insert(4); map.Insert(-72); - SimpleArray arr(15); - for (int i = 0 ; i < 15; i++) + SimpleArray arr(52); + for (int i = 0 ; i < 52; i++) { arr[i] = i * i; } diff --git a/test/DebugAdapterRunner/DebugAdapterRunner.nuspec b/test/DebugAdapterRunner/DebugAdapterRunner.nuspec index 541bac89b..92e356863 100644 --- a/test/DebugAdapterRunner/DebugAdapterRunner.nuspec +++ b/test/DebugAdapterRunner/DebugAdapterRunner.nuspec @@ -14,7 +14,7 @@ https://go.microsoft.com/fwlink/?LinkID=746387 © Microsoft Corporation. All rights reserved. - +