Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 38 additions & 4 deletions src/MIDebugEngine/Engine.Impl/Variables.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.VisualStudio.Debugger.Interop;
using Microsoft.VisualStudio.Debugger.Interop.DAP;
using System;
using Microsoft.DebugEngineHost;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
Expand Down Expand Up @@ -92,6 +93,18 @@ internal sealed class VariableInformation : IVariableInformation

static readonly Lazy<Regex> s_addressPattern = new Lazy<Regex>(() => new Regex(@"^(0x[0-9a-fA-F]+)\b"));

static readonly Regex s_naPattern = new Regex(@"^0x[0-9a-fA-F]+\s+(.)");

internal static string StripLeadingAddress(string value)
{
if (string.IsNullOrEmpty(value))
{
return value;
}

return s_naPattern.Replace(value, "$1");
}
Comment thread
gregg-miskelly marked this conversation as resolved.

public string Address()
{
// ask GDB to evaluate "&expression"
Expand Down Expand Up @@ -238,7 +251,7 @@ internal VariableInformation(string displayName, string expr, ThreadContext ctx,
: this(ctx, engine, thread)
{
// strip off formatting string
_strippedName = ProcessFormatSpecifiers(expr, out _format);
_strippedName = ProcessFormatSpecifiers(expr, out _format, out _formatHasNa);
Name = displayName;
IsParameter = isParameter;
_parent = null;
Expand All @@ -250,7 +263,7 @@ internal VariableInformation(string expr, IVariableInformation parent, AD7Engine
: this(parent.ThreadContext, engine, parent.Client)
{
// strip off formatting string
_strippedName = ProcessFormatSpecifiers(expr, out _format);
_strippedName = ProcessFormatSpecifiers(expr, out _format, out _formatHasNa);
Name = displayName ?? expr;
_parent = parent;
VariableNodeType = NodeType.Synthetic;
Expand All @@ -261,7 +274,7 @@ internal VariableInformation(string expr, VariableInformation parent)
: this(parent._ctx, parent._engine, parent.Client)
{
// strip off formatting string
_strippedName = ProcessFormatSpecifiers(expr, out _format);
_strippedName = ProcessFormatSpecifiers(expr, out _format, out _formatHasNa);
Name = expr;
VariableNodeType = NodeType.Root;
}
Expand All @@ -272,6 +285,12 @@ private VariableInformation(TupleValue results, VariableInformation parent, stri
{
TypeName = results.TryFindString("type");
Value = results.TryFindString("value");
_formatHasNa = parent._formatHasNa;
if (_formatHasNa)
{
Value = StripLeadingAddress(Value);
}

Name = name ?? results.FindString("exp");
if (results.Contains("dynamic"))
{
Expand Down Expand Up @@ -375,6 +394,9 @@ public VariableInformation FindChildByName(string name)
private DeferedFormatExpression _deferedFormatExpression;
private IVariableInformation _parent;
private string _format;
// Indicates the original format specifier included the natvis "na" modifier
// (used to decide whether to strip MI's leading address prefix from string values)
private bool _formatHasNa = false;
private string _strippedName; // "Name" stripped of format specifiers
private string _fullname;

Expand All @@ -401,8 +423,9 @@ public enum NodeType

private static Regex s_isFunction = new Regex(@".+\(.*\).*");

private string ProcessFormatSpecifiers(string exp, out string formatSpecifier)
private string ProcessFormatSpecifiers(string exp, out string formatSpecifier, out bool formatNa)
{
formatNa = false;
formatSpecifier = null; // will be used with -var-set-format

if (EngineUtils.IsConsoleExecCmd(exp, out string _, out string _))
Expand All @@ -416,6 +439,9 @@ private string ProcessFormatSpecifiers(string exp, out string formatSpecifier)

// Find the format specifier expression
string expFS = exp.Substring(lastComma + 1).Trim();
// Detect whether the natvis 'na' modifier is present in the original format specifier.
// We must detect this before we strip modifiers below.
formatNa = expFS.IndexOf("na", StringComparison.Ordinal) >= 0;

// Strip off modifiers that may be included together with another format specifier, e.g. 'nvoXb' is a valid format specifier, but we only care about the 'Xb' part
// This is not quite the right fix -- really the below switch statement should be a series of if statements. But since none of the supported format specifiers
Expand Down Expand Up @@ -698,6 +724,10 @@ internal async Task Eval(uint radix, enum_EVALFLAGS dwFlags = 0, DAPEvalFlags dw
_attribsFetched = true;
}
Value = results.TryFindString("value");
if (_formatHasNa)
{
Value = StripLeadingAddress(Value);
}
if ((string.IsNullOrEmpty(Value) || _format != null) && !string.IsNullOrEmpty(_internalName))
{
if (_format != null)
Expand All @@ -711,6 +741,10 @@ internal async Task Eval(uint radix, enum_EVALFLAGS dwFlags = 0, DAPEvalFlags dw
if (results.ResultClass == ResultClass.done)
{
Value = results.FindString("value");
if (_formatHasNa)
{
Value = StripLeadingAddress(Value);
}
}
else if (results.ResultClass == ResultClass.error)
{
Expand Down
25 changes: 20 additions & 5 deletions src/MIDebugEngine/Natvis.Impl/Natvis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1502,13 +1502,31 @@ internal static string ExtractFormatSpecifier(string expression)
.Replace("nvo", "").Replace("na", "").Replace("nr", "").Replace("nd", "");
}

/// <summary>
/// Returns the format specifier from a NatVis expression (the part after the last
/// top-level comma), normalized the same way as
/// <see cref="VariableInformation.ProcessFormatSpecifiers"/>: modifiers "nvo", "na",
/// "nr", "nd" are stripped before returning. Returns null when no specifier is present.
/// also returns whether modifier 'na' was there via the out parameter
/// </summary>
internal static string ExtractFormatSpecifier(string expression, out bool hasNa)
{
hasNa = false;
int commaPos = FindLastTopLevelComma(expression);
if (commaPos < 0) return null;

string tail = expression.Substring(commaPos + 1).Trim();
hasNa = tail.IndexOf("na", StringComparison.Ordinal) >= 0;

return tail
.Replace("nvo", "").Replace("na", "").Replace("nr", "").Replace("nd", "");
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I missing something? It looks like nothing is calling your new method

/// <summary>
/// Cleans up the raw value that GDB/LLDB returns for a <c>const char16_t*</c>
/// expression (i.e. one evaluated with the <c>,sub</c> / <c>,su</c> format specifier).
/// GDB and LLDB both prefix the string with the pointer address, e.g.
/// <c>0x00007fff5fbff6c0 u"Hello"</c>
/// This method strips the address and the surrounding <c>u"…"</c> quotes so that
/// the NatVis DisplayString shows just the string content.
/// </summary>
internal static string CleanUtf16StringValue(string value)
{
Expand All @@ -1531,9 +1549,6 @@ internal static string CleanUtf16StringValue(string value)
/// (i.e. one evaluated with the <c>,sb</c> format specifier).
/// GDB and LLDB prefix the string with the pointer address, e.g.
/// <c>0x00007fff5fbff6c0 "Hello"</c>
/// This method strips the address and the surrounding <c>"…"</c> quotes so that
/// the NatVis DisplayString shows just the string content (matching VS behaviour,
/// where <c>{ptr,sb}</c> evaluates to bare text without quotes).
/// </summary>
internal static string CleanAsciiStringValue(string value)
{
Expand Down
11 changes: 6 additions & 5 deletions src/MIDebugEngineUnitTests/NatvisFormatSpecifierTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,34 @@ public class NatvisFormatSpecifierTest
[Fact]
public void ExtractFormatSpecifier_Sub_Extracted()
{
Assert.Equal("sub", Natvis.ExtractFormatSpecifier("schemeStr(),sub"));
Assert.Equal("sub", Natvis.ExtractFormatSpecifier("schemeStr(),sub", out _));
}

[Fact]
public void ExtractFormatSpecifier_Decimal_Extracted()
{
Assert.Equal("d", Natvis.ExtractFormatSpecifier("year(),d"));
Assert.Equal("d", Natvis.ExtractFormatSpecifier("year(),d", out _));
}

[Fact]
public void ExtractFormatSpecifier_NoSpecifier_ReturnsNull()
{
Assert.Null(Natvis.ExtractFormatSpecifier("cspec == 1"));
Assert.Null(Natvis.ExtractFormatSpecifier("cspec == 1", out _));
}

[Fact]
public void ExtractFormatSpecifier_NvoModifierStripped()
{
// "nvoXb": strip "nvo" modifier, result is "Xb"
Assert.Equal("Xb", Natvis.ExtractFormatSpecifier("data1,nvoXb"));
Assert.Equal("Xb", Natvis.ExtractFormatSpecifier("data1,nvoXb", out _));
}

[Fact]
public void ExtractFormatSpecifier_NaModifierStripped()
{
// "view(RecZone)na": strip "na", result is "view(RecZone)"
Assert.Equal("view(RecZone)", Natvis.ExtractFormatSpecifier("this,view(RecZone)na"));
Assert.Equal("view(RecZone)", Natvis.ExtractFormatSpecifier("this,view(RecZone)na", out bool hasNa));
Assert.True(hasNa);
}

// -- CleanUtf16StringValue --------------------------------------------
Expand Down
85 changes: 85 additions & 0 deletions src/MIDebugEngineUnitTests/NatvisFormatStringTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using Xunit;
using Microsoft.MIDebugEngine.Natvis;
using Microsoft.MIDebugEngine;

namespace MIDebugEngineUnitTests
{
/// <summary>
/// unit tests for natvis format string processing with format specifiers
/// tests the handling of format specifiers like, 'na', sub (UTF-16), sb (ASCII).
/// </summary>
public class NatvisFormatStringTest
{
// -- format specifier with template parameters ($T1, $T2,) --
[Fact]
public void ExtractFormatSpecifier_HasTemplateParameter_TemplateSubstituted()
{
string spec = Natvis.ExtractFormatSpecifier("myVector,sub");
Assert.Equal("sub", spec);
}

// -- Format specifier na --

[Fact]
public void ExtractFormatSpecifier_WithNaModifier_HasNaIsTrue()
{
string spec = Natvis.ExtractFormatSpecifier("someExpr,na", out bool hasNa);
Assert.Equal("", spec);
Assert.True(hasNa);
}

[Fact]
public void ExtractFormatSpecifier_WithNaAndFormat_HasNaIsTrueAndSpecReturned()
{
string spec = Natvis.ExtractFormatSpecifier("myString,subna", out bool hasNa);
Assert.Equal("sub", spec);
Assert.True(hasNa);
}

[Fact]
public void ExtractFormatSpecifier_WithoutNaModifier_HasNaIsFalse()
{
string spec = Natvis.ExtractFormatSpecifier("myString,sub", out bool hasNa);
Assert.Equal("sub", spec);
Assert.False(hasNa);
}

// -- UTF-16 and ASCII string cleanup --

[Fact]
public void CleanUtf16StringValue_WithAddressAndQuotes_AddressAndQuotesStripped()
{
string result = Natvis.CleanUtf16StringValue("0x00007fff5fbff6c0 u\"Hello\"");
Assert.Equal("Hello", result);
}

[Fact]
public void CleanUtf16StringValue_WithCapitalU_QuotesStripped()
{
string result = Natvis.CleanUtf16StringValue("U\"Hello\"");
Assert.Equal("Hello", result);
}

[Fact]
public void CleanUtf16StringValue_JustAddress_AddressStripped()
{
string result = Natvis.CleanUtf16StringValue("0x00007fff u");
Assert.Equal("u", result);
}

[Fact]
public void CleanAsciiStringValue_WithAddressAndQuotes_AddressAndQuotesStripped()
{
string result = Natvis.CleanAsciiStringValue("0x00007fff5fbff6c0 \"Hello\"");
Assert.Equal("Hello", result);
}

[Fact]
public void CleanAsciiStringValue_TruncatedNoClosingQuote_OpeningQuoteStripped()
{
string result = Natvis.CleanAsciiStringValue("0x00007fff \"Hello...");
Assert.Equal("Hello...", result);
}
}
}

Loading