Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
30 changes: 30 additions & 0 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 @@ -272,6 +273,17 @@ private VariableInformation(TupleValue results, VariableInformation parent, stri
{
TypeName = results.TryFindString("type");
Value = results.TryFindString("value");
// Diagnostic: log raw tuple child value before any cleanup
_debuggedProcess.Logger?.WriteLine(LogLevel.Verbose, FormattableString.Invariant($"TupleCtor: name={Name}, exp={results.TryFindString("exp")}, type={TypeName}, format={_format}, formatHasNa={_formatHasNa}, rawValue={results.TryFindString("value")}"));
Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated
// Only strip the leading MI address prefix ("0x... \"\"") when the natvis
// format included the 'na' modifier.
if (_formatHasNa && !string.IsNullOrEmpty(Value) && Regex.IsMatch(Value, "^0x[0-9a-fA-F]+\\s+"))
Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated
Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated
{
// Recognize typical GDB prefix: 0x<hex> <string>
string before = Value;
Value = Regex.Replace(Value, "^0x[0-9a-fA-F]+\\s+", "");
_debuggedProcess.Logger?.WriteLine(LogLevel.Verbose, FormattableString.Invariant($"TupleCtor: stripped address prefix: before={before}, after={Value}"));
Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated
}
Comment thread
gregg-miskelly marked this conversation as resolved.
Name = name ?? results.FindString("exp");
if (results.Contains("dynamic"))
{
Expand Down Expand Up @@ -332,6 +344,8 @@ private VariableInformation(TupleValue results, VariableInformation parent, stri
_internalName = results.FindString("name");
IsChild = true;
_format = parent._format; // inherit formatting
// inherit whether the parent's format included the 'na' modifier
_formatHasNa = parent._formatHasNa;
_parent = parent.VariableNodeType == NodeType.AccessQualifier ? parent._parent : parent;
this.PropertyInfoFlags = parent.PropertyInfoFlags;
}
Expand Down Expand Up @@ -375,6 +389,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 Down Expand Up @@ -416,6 +433,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.
_formatHasNa = expFS.IndexOf("na", StringComparison.Ordinal) >= 0;
Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated
Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated

// 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 +718,11 @@ internal async Task Eval(uint radix, enum_EVALFLAGS dwFlags = 0, DAPEvalFlags dw
_attribsFetched = true;
}
Value = results.TryFindString("value");
// If natvis requested 'na', strip MI's leading address prefix
if (_formatHasNa && !string.IsNullOrEmpty(Value) && Regex.IsMatch(Value, "^0x[0-9a-fA-F]+\\s+"))
Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated
{
Value = Regex.Replace(Value, "^0x[0-9a-fA-F]+\\s+", "");
}
if ((string.IsNullOrEmpty(Value) || _format != null) && !string.IsNullOrEmpty(_internalName))
{
if (_format != null)
Expand All @@ -711,6 +736,11 @@ internal async Task Eval(uint radix, enum_EVALFLAGS dwFlags = 0, DAPEvalFlags dw
if (results.ResultClass == ResultClass.done)
{
Value = results.FindString("value");
// If natvis requested 'na', strip MI's leading address prefix
if (_formatHasNa && !string.IsNullOrEmpty(Value) && Regex.IsMatch(Value, "^0x[0-9a-fA-F]+\\s+"))
{
Value = Regex.Replace(Value, "^0x[0-9a-fA-F]+\\s+", "");
}
}
else if (results.ResultClass == ResultClass.error)
{
Expand Down
52 changes: 35 additions & 17 deletions src/MIDebugEngine/Natvis.Impl/Natvis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1283,12 +1283,24 @@ private string FormatValue(string format, IVariableInformation variable, IDictio
if (m.Success)
{
string rawExpr = format.Substring(i + 1, m.Length - 2);
// Substitute template parameter macros ($T1, $T2, ...) inside the whole brace
// expression (this covers both the expression and any trailing format specifier)
if (scopedNames != null)
{
rawExpr = Regex.Replace(rawExpr, "\\$T\\d+", (Match mt) =>
Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated
{
if (scopedNames.TryGetValue(mt.Value, out string replacement))
return replacement;
return mt.Value;
});
}
Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated
bool hasNa = HasNaModifier(rawExpr);
Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated
string spec = ExtractFormatSpecifier(rawExpr);
string exprValue = GetExpressionValue(rawExpr, variable, scopedNames, intrinsics);
Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated
if (spec == "sub" || spec == "su")
exprValue = CleanUtf16StringValue(exprValue);
else if (spec == "sb")
exprValue = CleanAsciiStringValue(exprValue);
Comment thread
gregg-miskelly marked this conversation as resolved.
Comment thread
gregg-miskelly marked this conversation as resolved.
if (hasNa && !string.IsNullOrEmpty(exprValue))
{
exprValue = s_addressPrefix.Replace(exprValue, "");
}
Comment thread
gregg-miskelly marked this conversation as resolved.
value.Append(exprValue);
i += m.Length - 1;
}
Expand Down Expand Up @@ -1502,27 +1514,33 @@ internal static string ExtractFormatSpecifier(string expression)
.Replace("nvo", "").Replace("na", "").Replace("nr", "").Replace("nd", "");
}

/// <summary>
/// Returns true if the NatVis expression's trailing format specifier (the part after the
/// last top-level comma) contains the "na" modifier. This intentionally inspects the
/// raw specifier text and does not normalize/remove modifiers so callers can detect
/// whether the original expression asked for the "na" behavior.
/// </summary>
private static bool HasNaModifier(string expression)
{
int commaPos = FindLastTopLevelComma(expression);
if (commaPos < 0) return false;
string tail = expression.Substring(commaPos + 1);
return tail.IndexOf("na", StringComparison.Ordinal) >= 0;
}

Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated
/// <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.
/// This method strips the leading address prefix that GDB/LLDB emits ("0x... ").
/// It does NOT remove surrounding quotes or the leading character-width prefix (u/U).
/// </summary>
internal static string CleanUtf16StringValue(string value)
{
if (string.IsNullOrEmpty(value)) return value;
// Strip leading "0x<hex> " address prefix emitted by GDB/LLDB.
value = s_addressPrefix.Replace(value, "");
// Strip surrounding u"..." or U"..." quotes.
if (value.Length >= 3 &&
(value.StartsWith("u\"", StringComparison.Ordinal) || value.StartsWith("U\"", StringComparison.Ordinal)))
{
value = value.EndsWith("\"", StringComparison.Ordinal)
? value.Substring(2, value.Length - 3)
: value.Substring(2);
}
Comment thread
gregg-miskelly marked this conversation as resolved.
return value;
}
Comment thread
gregg-miskelly marked this conversation as resolved.

Expand All @@ -1531,9 +1549,9 @@ 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).
/// This method strips the leading address prefix that GDB/LLDB emits ("0x... ").
/// It does NOT remove surrounding quotes; callers should decide whether quotes
/// should be removed based on the caller's context.
Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated
/// </summary>
internal static string CleanAsciiStringValue(string value)
{
Expand Down
Loading