Skip to content

Commit fa9ed9a

Browse files
committed
nested lambda variables
1 parent 34647fd commit fa9ed9a

File tree

2 files changed

+31
-3
lines changed

2 files changed

+31
-3
lines changed

src/SharpDbg.Infrastructure/Debugger/ManagedDebugger_VariableInfo.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ private async Task AddLocalVariables(ModuleInfo module, CorDebugFunction corDebu
1414
if (classContainingHoistedLocalsValue is not null)
1515
{
1616
// If we have a classContainingHoistedLocalsValue, it means captured variables from the outer scope are stored
17-
// as fields on the compiler-generated closure class - read those first.
17+
// as fields on the compiler-generated closure class - read those first, walking the full closure chain
18+
// so that variables captured from enclosing lambdas are also included.
1819
// We do NOT return here: non-captured locals declared inside the lambda body are still plain IL locals
1920
// on the lambda method frame and must also be read below.
20-
await AddMembers(classContainingHoistedLocalsValue, classContainingHoistedLocalsValue.ExactType, threadId, stackDepth, result);
21+
await AddClosureChainMembers(classContainingHoistedLocalsValue, threadId, stackDepth, result);
2122
}
2223
var corDebugIlFrame = GetFrameForThreadIdAndStackDepth(threadId, stackDepth);
2324
if (corDebugIlFrame.LocalVariables.Length is 0) return;
@@ -39,6 +40,29 @@ private async Task AddLocalVariables(ModuleInfo module, CorDebugFunction corDebu
3940
}
4041
}
4142

43+
/// Walks the compiler-generated closure chain starting at <paramref name="closureValue"/>,
44+
/// calling AddMembers on each closure class. Parent closures are linked via a field of
45+
/// kind <see cref="GeneratedNameKind.DisplayClassLocalOrField"/> (e.g. "&lt;&gt;8__1").
46+
private async Task AddClosureChainMembers(CorDebugValue closureValue, ThreadId threadId, FrameStackDepth stackDepth, List<VariableInfo> result)
47+
{
48+
await AddMembers(closureValue, closureValue.ExactType, threadId, stackDepth, result);
49+
50+
// Follow the DisplayClassLocalOrField link to the parent closure, if any
51+
var objectValue = closureValue.UnwrapDebugValueToObject();
52+
var metadataImport = objectValue.Class.Module.GetMetaDataInterface().MetaDataImport;
53+
var fields = metadataImport.EnumFields(objectValue.Class.Token);
54+
foreach (var field in fields)
55+
{
56+
var fieldProps = metadataImport.GetFieldProps(field);
57+
if (GeneratedNameParser.GetKind(fieldProps.szField) is GeneratedNameKind.DisplayClassLocalOrField)
58+
{
59+
var parentClosureValue = objectValue.GetFieldValue(objectValue.Class.Raw, field);
60+
await AddClosureChainMembers(parentClosureValue, threadId, stackDepth, result);
61+
break; // only one parent link per closure class
62+
}
63+
}
64+
}
65+
4266
/// Returns classContainingHoistedLocalsValue if applicable
4367
private async Task<CorDebugValue?> AddArguments(ModuleInfo module, CorDebugFunction corDebugFunction, List<VariableInfo> result, ThreadId threadId, FrameStackDepth stackDepth)
4468
{

src/SharpDbg.Infrastructure/Debugger/ManagedDebugger_VariableInfo_ProxyField.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ public partial class ManagedDebugger
2121
}
2222
else if (generatedNameKind is GeneratedNameKind.DisplayClassLocalOrField)
2323
{
24-
throw new NotImplementedException();
24+
// This field points to a parent closure class - follow the chain to find 'this'
25+
var parentClosureValue = objectValue.GetFieldValue(objectValue.Class.Raw, field);
26+
var parentObjectValue = parentClosureValue.UnwrapDebugValueToObject();
27+
var parentMetadataImport = parentObjectValue.Class.Module.GetMetaDataInterface().MetaDataImport;
28+
return GetAsyncOrLambdaProxyFieldValue(parentClosureValue, parentMetadataImport);
2529
}
2630
}
2731

0 commit comments

Comments
 (0)