From 507e16f563b6d464d5475c8b44fc0f8e377d0d58 Mon Sep 17 00:00:00 2001 From: SachinM123 <114114188+SachinM123@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:26:58 -0700 Subject: [PATCH 1/6] Implement natvis 'na' modifier and $T substitution Add support for the natvis 'na' (no-address) modifier to strip MI's leading address prefix from string values. This requires tracking the format specifier through variable initialization and applying cleanup at value retrieval points. Also implement template parameter substitution ($T1, $T2, etc.) within natvis brace expressions before format specifier extraction. --- src/MIDebugEngine/Engine.Impl/Variables.cs | 30 +++++++++++++ src/MIDebugEngine/Natvis.Impl/Natvis.cs | 52 +++++++++++++++------- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/src/MIDebugEngine/Engine.Impl/Variables.cs b/src/MIDebugEngine/Engine.Impl/Variables.cs index b6dd867bb..4ccb84b86 100644 --- a/src/MIDebugEngine/Engine.Impl/Variables.cs +++ b/src/MIDebugEngine/Engine.Impl/Variables.cs @@ -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; @@ -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")}")); + // 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+")) + { + // Recognize typical GDB prefix: 0x + 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}")); + } Name = name ?? results.FindString("exp"); if (results.Contains("dynamic")) { @@ -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; } @@ -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; @@ -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; // 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 @@ -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+")) + { + Value = Regex.Replace(Value, "^0x[0-9a-fA-F]+\\s+", ""); + } if ((string.IsNullOrEmpty(Value) || _format != null) && !string.IsNullOrEmpty(_internalName)) { if (_format != null) @@ -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) { diff --git a/src/MIDebugEngine/Natvis.Impl/Natvis.cs b/src/MIDebugEngine/Natvis.Impl/Natvis.cs index 5e7e30d05..499ee08b8 100755 --- a/src/MIDebugEngine/Natvis.Impl/Natvis.cs +++ b/src/MIDebugEngine/Natvis.Impl/Natvis.cs @@ -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) => + { + if (scopedNames.TryGetValue(mt.Value, out string replacement)) + return replacement; + return mt.Value; + }); + } + bool hasNa = HasNaModifier(rawExpr); string spec = ExtractFormatSpecifier(rawExpr); string exprValue = GetExpressionValue(rawExpr, variable, scopedNames, intrinsics); - if (spec == "sub" || spec == "su") - exprValue = CleanUtf16StringValue(exprValue); - else if (spec == "sb") - exprValue = CleanAsciiStringValue(exprValue); + if (hasNa && !string.IsNullOrEmpty(exprValue)) + { + exprValue = s_addressPrefix.Replace(exprValue, ""); + } value.Append(exprValue); i += m.Length - 1; } @@ -1502,27 +1514,33 @@ internal static string ExtractFormatSpecifier(string expression) .Replace("nvo", "").Replace("na", "").Replace("nr", "").Replace("nd", ""); } + /// + /// 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. + /// + 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; + } + /// /// Cleans up the raw value that GDB/LLDB returns for a const char16_t* /// expression (i.e. one evaluated with the ,sub / ,su format specifier). /// GDB and LLDB both prefix the string with the pointer address, e.g. /// 0x00007fff5fbff6c0 u"Hello" - /// This method strips the address and the surrounding u"…" 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). /// internal static string CleanUtf16StringValue(string value) { if (string.IsNullOrEmpty(value)) return value; // Strip leading "0x " 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); - } return value; } @@ -1531,9 +1549,9 @@ internal static string CleanUtf16StringValue(string value) /// (i.e. one evaluated with the ,sb format specifier). /// GDB and LLDB prefix the string with the pointer address, e.g. /// 0x00007fff5fbff6c0 "Hello" - /// This method strips the address and the surrounding "…" quotes so that - /// the NatVis DisplayString shows just the string content (matching VS behaviour, - /// where {ptr,sb} 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. /// internal static string CleanAsciiStringValue(string value) { From 442a1d31e4cd4168c00f8327d340868142ab0141 Mon Sep 17 00:00:00 2001 From: SachinM123 <114114188+SachinM123@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:49:57 -0700 Subject: [PATCH 2/6] address pr comments --- src/MIDebugEngine/Engine.Impl/Variables.cs | 47 +++++++++++----------- src/MIDebugEngine/Natvis.Impl/Natvis.cs | 12 +++--- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/MIDebugEngine/Engine.Impl/Variables.cs b/src/MIDebugEngine/Engine.Impl/Variables.cs index 4ccb84b86..6b45397e9 100644 --- a/src/MIDebugEngine/Engine.Impl/Variables.cs +++ b/src/MIDebugEngine/Engine.Impl/Variables.cs @@ -93,6 +93,18 @@ internal sealed class VariableInformation : IVariableInformation static readonly Lazy s_addressPattern = new Lazy(() => new Regex(@"^(0x[0-9a-fA-F]+)\b")); + static readonly Regex s_naPattern = new Regex(@"^0x[0-9a-fA-F]+\s+(.)"); + + private static string StripLeadingAddress(string value, bool formatNa) + { + if(!formatNa || string.IsNullOrEmpty(value)) + { + return value; + } + + return s_naPattern.Replace(value, "$1"); + } + public string Address() { // ask GDB to evaluate "&expression" @@ -239,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; @@ -251,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; @@ -262,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; } @@ -273,17 +285,10 @@ 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")}")); - // 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+")) - { - // Recognize typical GDB prefix: 0x - 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}")); - } + // Only strip the leading MI address prefix ("0x... ") when the natvis format included the 'na' modifier. + Value = StripLeadingAddress(Value, _formatHasNa); + + Name = name ?? results.FindString("exp"); if (results.Contains("dynamic")) { @@ -418,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 _)) @@ -436,6 +442,7 @@ private string ProcessFormatSpecifiers(string exp, out string formatSpecifier) // 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; + formatNa = _formatHasNa; // 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 @@ -719,10 +726,7 @@ internal async Task Eval(uint radix, enum_EVALFLAGS dwFlags = 0, DAPEvalFlags dw } 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+")) - { - Value = Regex.Replace(Value, "^0x[0-9a-fA-F]+\\s+", ""); - } + Value = StripLeadingAddress(Value, _formatHasNa); if ((string.IsNullOrEmpty(Value) || _format != null) && !string.IsNullOrEmpty(_internalName)) { if (_format != null) @@ -737,10 +741,7 @@ internal async Task Eval(uint radix, enum_EVALFLAGS dwFlags = 0, DAPEvalFlags dw { 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+", ""); - } + Value = StripLeadingAddress(Value, _formatHasNa); } else if (results.ResultClass == ResultClass.error) { diff --git a/src/MIDebugEngine/Natvis.Impl/Natvis.cs b/src/MIDebugEngine/Natvis.Impl/Natvis.cs index 499ee08b8..fbc4aaa9c 100755 --- a/src/MIDebugEngine/Natvis.Impl/Natvis.cs +++ b/src/MIDebugEngine/Natvis.Impl/Natvis.cs @@ -1287,7 +1287,7 @@ private string FormatValue(string format, IVariableInformation variable, IDictio // expression (this covers both the expression and any trailing format specifier) if (scopedNames != null) { - rawExpr = Regex.Replace(rawExpr, "\\$T\\d+", (Match mt) => + rawExpr = Regex.Replace(rawExpr, @"\$T\d+", (Match mt) => { if (scopedNames.TryGetValue(mt.Value, out string replacement)) return replacement; @@ -1295,7 +1295,6 @@ private string FormatValue(string format, IVariableInformation variable, IDictio }); } bool hasNa = HasNaModifier(rawExpr); - string spec = ExtractFormatSpecifier(rawExpr); string exprValue = GetExpressionValue(rawExpr, variable, scopedNames, intrinsics); if (hasNa && !string.IsNullOrEmpty(exprValue)) { @@ -1506,10 +1505,15 @@ private static int FindLastTopLevelComma(string expression) /// : modifiers "nvo", "na", /// "nr", "nd" are stripped before returning. Returns null when no specifier is present. /// - internal static string ExtractFormatSpecifier(string expression) + 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 expression.Substring(commaPos + 1).Trim() .Replace("nvo", "").Replace("na", "").Replace("nr", "").Replace("nd", ""); } @@ -1550,8 +1554,6 @@ internal static string CleanUtf16StringValue(string value) /// GDB and LLDB prefix the string with the pointer address, e.g. /// 0x00007fff5fbff6c0 "Hello" /// 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. /// internal static string CleanAsciiStringValue(string value) { From 3f38aef26a62844eab2f5a287d8291ff9ae26aec Mon Sep 17 00:00:00 2001 From: SachinM123 <114114188+SachinM123@users.noreply.github.com> Date: Mon, 27 Jul 2026 17:15:46 -0700 Subject: [PATCH 3/6] address pr comments match tests with out param from extractformatspecifier made VariableInformation.StripLeadingAddress internal static to make public and updated natvis.cs accordingly changed _formathasna to inherit properly and call stripleadingaddress more strictly --- src/MIDebugEngine/Engine.Impl/Variables.cs | 26 +++++++++++-------- src/MIDebugEngine/Natvis.Impl/Natvis.cs | 4 +-- .../NatvisFormatSpecifierTest.cs | 11 ++++---- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/MIDebugEngine/Engine.Impl/Variables.cs b/src/MIDebugEngine/Engine.Impl/Variables.cs index 6b45397e9..3b0c8b6aa 100644 --- a/src/MIDebugEngine/Engine.Impl/Variables.cs +++ b/src/MIDebugEngine/Engine.Impl/Variables.cs @@ -95,9 +95,9 @@ internal sealed class VariableInformation : IVariableInformation static readonly Regex s_naPattern = new Regex(@"^0x[0-9a-fA-F]+\s+(.)"); - private static string StripLeadingAddress(string value, bool formatNa) + internal static string StripLeadingAddress(string value) { - if(!formatNa || string.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { return value; } @@ -285,9 +285,11 @@ private VariableInformation(TupleValue results, VariableInformation parent, stri { TypeName = results.TryFindString("type"); Value = results.TryFindString("value"); - // Only strip the leading MI address prefix ("0x... ") when the natvis format included the 'na' modifier. - Value = StripLeadingAddress(Value, _formatHasNa); - + _formatHasNa = parent._formatHasNa; + if (_formatHasNa) + { + Value = StripLeadingAddress(Value); + } Name = name ?? results.FindString("exp"); if (results.Contains("dynamic")) @@ -349,8 +351,6 @@ 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; } @@ -725,8 +725,10 @@ 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 - Value = StripLeadingAddress(Value, _formatHasNa); + if (_formatHasNa) + { + Value = StripLeadingAddress(Value); + } if ((string.IsNullOrEmpty(Value) || _format != null) && !string.IsNullOrEmpty(_internalName)) { if (_format != null) @@ -740,8 +742,10 @@ 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 - Value = StripLeadingAddress(Value, _formatHasNa); + if (_formatHasNa) + { + Value = StripLeadingAddress(Value); + } } else if (results.ResultClass == ResultClass.error) { diff --git a/src/MIDebugEngine/Natvis.Impl/Natvis.cs b/src/MIDebugEngine/Natvis.Impl/Natvis.cs index fbc4aaa9c..0b89bd484 100755 --- a/src/MIDebugEngine/Natvis.Impl/Natvis.cs +++ b/src/MIDebugEngine/Natvis.Impl/Natvis.cs @@ -1296,9 +1296,9 @@ private string FormatValue(string format, IVariableInformation variable, IDictio } bool hasNa = HasNaModifier(rawExpr); string exprValue = GetExpressionValue(rawExpr, variable, scopedNames, intrinsics); - if (hasNa && !string.IsNullOrEmpty(exprValue)) + if (hasNa) { - exprValue = s_addressPrefix.Replace(exprValue, ""); + exprValue = VariableInformation.StripLeadingAddress(exprValue); } value.Append(exprValue); i += m.Length - 1; diff --git a/src/MIDebugEngineUnitTests/NatvisFormatSpecifierTest.cs b/src/MIDebugEngineUnitTests/NatvisFormatSpecifierTest.cs index 287be0072..02e9a94b0 100644 --- a/src/MIDebugEngineUnitTests/NatvisFormatSpecifierTest.cs +++ b/src/MIDebugEngineUnitTests/NatvisFormatSpecifierTest.cs @@ -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 -------------------------------------------- From 79422c4cd8091c149481d4e68e49b74959aed0a8 Mon Sep 17 00:00:00 2001 From: SachinM123 <114114188+SachinM123@users.noreply.github.com> Date: Mon, 27 Jul 2026 17:59:49 -0700 Subject: [PATCH 4/6] fix CleanUtf16StringValue --- src/MIDebugEngine/Natvis.Impl/Natvis.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/MIDebugEngine/Natvis.Impl/Natvis.cs b/src/MIDebugEngine/Natvis.Impl/Natvis.cs index 0b89bd484..196d272df 100755 --- a/src/MIDebugEngine/Natvis.Impl/Natvis.cs +++ b/src/MIDebugEngine/Natvis.Impl/Natvis.cs @@ -1544,7 +1544,14 @@ internal static string CleanUtf16StringValue(string value) { if (string.IsNullOrEmpty(value)) return value; // Strip leading "0x " address prefix emitted by GDB/LLDB. - value = s_addressPrefix.Replace(value, ""); + value = VariableInformation.StripLeadingAddress(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); + } return value; } @@ -1559,7 +1566,7 @@ internal static string CleanAsciiStringValue(string value) { if (string.IsNullOrEmpty(value)) return value; // Strip leading "0x " address prefix emitted by GDB/LLDB. - value = s_addressPrefix.Replace(value, ""); + value = VariableInformation.StripLeadingAddress(value); // Strip surrounding "..." quotes. if (value.Length >= 2 && value.StartsWith("\"", StringComparison.Ordinal)) { From 42fa4c90a036a893b1af694554f2df1d50246243 Mon Sep 17 00:00:00 2001 From: SachinM123 <114114188+SachinM123@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:08:29 -0700 Subject: [PATCH 5/6] pr changes --- src/MIDebugEngine/Engine.Impl/Variables.cs | 3 +-- src/MIDebugEngine/Natvis.Impl/Natvis.cs | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/MIDebugEngine/Engine.Impl/Variables.cs b/src/MIDebugEngine/Engine.Impl/Variables.cs index 3b0c8b6aa..9524d677c 100644 --- a/src/MIDebugEngine/Engine.Impl/Variables.cs +++ b/src/MIDebugEngine/Engine.Impl/Variables.cs @@ -441,8 +441,7 @@ private string ProcessFormatSpecifiers(string exp, out string formatSpecifier, o 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; - formatNa = _formatHasNa; + 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 diff --git a/src/MIDebugEngine/Natvis.Impl/Natvis.cs b/src/MIDebugEngine/Natvis.Impl/Natvis.cs index 196d272df..7e7f9540e 100755 --- a/src/MIDebugEngine/Natvis.Impl/Natvis.cs +++ b/src/MIDebugEngine/Natvis.Impl/Natvis.cs @@ -1526,10 +1526,8 @@ internal static string ExtractFormatSpecifier(string expression, out bool hasNa) /// 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; + ExtractFormatSpecifier(expression, out bool hasNa); + return hasNa; } /// From 55881f82105899b0c585515429194ddc1dcb6631 Mon Sep 17 00:00:00 2001 From: SachinM123 <114114188+SachinM123@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:10:57 -0700 Subject: [PATCH 6/6] Update Natvis.cs --- src/MIDebugEngine/Natvis.Impl/Natvis.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/MIDebugEngine/Natvis.Impl/Natvis.cs b/src/MIDebugEngine/Natvis.Impl/Natvis.cs index 7e7f9540e..9ad2a437e 100755 --- a/src/MIDebugEngine/Natvis.Impl/Natvis.cs +++ b/src/MIDebugEngine/Natvis.Impl/Natvis.cs @@ -1294,7 +1294,7 @@ private string FormatValue(string format, IVariableInformation variable, IDictio return mt.Value; }); } - bool hasNa = HasNaModifier(rawExpr); + ExtractFormatSpecifier(rawExpr, out bool hasNa); string exprValue = GetExpressionValue(rawExpr, variable, scopedNames, intrinsics); if (hasNa) { @@ -1524,12 +1524,6 @@ internal static string ExtractFormatSpecifier(string expression, out bool hasNa) /// raw specifier text and does not normalize/remove modifiers so callers can detect /// whether the original expression asked for the "na" behavior. /// - private static bool HasNaModifier(string expression) - { - ExtractFormatSpecifier(expression, out bool hasNa); - return hasNa; - } - /// /// Cleans up the raw value that GDB/LLDB returns for a const char16_t* /// expression (i.e. one evaluated with the ,sub / ,su format specifier).