Implement natvis 'na' modifier and $T substitution#1612
Conversation
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.
| 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")}")); |
There was a problem hiding this comment.
These should already be in the MI log. Is there value in keeping them?
| _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+")) |
| _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+")) |
There was a problem hiding this comment.
Pull request overview
This PR extends MIEngine’s NatVis evaluator to (1) support the na (no-address) modifier by stripping MI’s leading 0x... address prefix from certain evaluated values, and (2) perform $T1, $T2, … template-parameter substitution inside NatVis brace expressions before parsing format specifiers.
Changes:
- Add
$Tnmacro substitution over full{...}brace expressions in Natvis display-string formatting. - Detect and apply
naaddress-prefix stripping during NatVis display-string formatting and during variable evaluation/initialization by tracking an_formatHasNaflag. - Update NatVis string “cleanup” helpers and related comments around address-prefix stripping behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/MIDebugEngine/Natvis.Impl/Natvis.cs | Adds $Tn substitution in brace expressions and introduces na detection/cleanup in display-string formatting; updates format-specifier helpers and string cleanup docs. |
| src/MIDebugEngine/Engine.Impl/Variables.cs | Tracks na in parsed format specifiers and attempts to strip MI’s address prefix at variable construction/evaluation time. |
| bool hasNa = HasNaModifier(rawExpr); | ||
| string spec = ExtractFormatSpecifier(rawExpr); | ||
| string exprValue = GetExpressionValue(rawExpr, variable, scopedNames, intrinsics); |
| 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); | ||
| } | ||
| return value; | ||
| } |
| /// 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. |
| 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<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}")); | ||
| } |
| // 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}")); |
| 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; |
| } | ||
| 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+")) |
There was a problem hiding this comment.
Actually, better yet: can you extract this into a helper method so you don't repeat it three times?
| // expression (this covers both the expression and any trailing format specifier) | ||
| if (scopedNames != null) | ||
| { | ||
| rawExpr = Regex.Replace(rawExpr, "\\$T\\d+", (Match mt) => |
| return mt.Value; | ||
| }); | ||
| } | ||
| bool hasNa = HasNaModifier(rawExpr); |
| if (spec == "sub" || spec == "su") | ||
| exprValue = CleanUtf16StringValue(exprValue); | ||
| else if (spec == "sb") | ||
| exprValue = CleanAsciiStringValue(exprValue); |
There was a problem hiding this comment.
Did you mean to loose this sub/su/sb code?
There was a problem hiding this comment.
Actually, is su needed? Variabel.cs line 477 already handles su correctly.
| value = value.EndsWith("\"", StringComparison.Ordinal) | ||
| ? value.Substring(2, value.Length - 3) | ||
| : value.Substring(2); | ||
| } |
There was a problem hiding this comment.
,sub is supposed to remove the quotes/prefix
There was a problem hiding this comment.
Though this changes looks like it is correct for ,su
| return replacement; | ||
| return mt.Value; | ||
| }); | ||
| } |
There was a problem hiding this comment.
If I am reading the code correctly, this is already at least partially done by ReplaceNamesInExpression, which is called by GetExpressionValue, so I don't think you want to do this over the entire expression.
Can you trace through why ReplaceNamesInExpression isn't doing this already? (lines 1713-1717).
|
Can you add a test for your changes? You may also need to adjust a test baseline for |
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.