mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
Fix #3008: local function forwarding a display class loses its scope
Roslyn passes a display class into a local function by ref, and a local function that only forwards that parameter to a sibling has no closure variable of its own. The closure analysis therefore found nothing to anchor it and fell back to the root method body, which put it out of reach of the callees it forwards to; CallBuilder then hit the assert guarding a local function reference it cannot resolve and emitted the raw metadata name of the target instead. The constructor path also mixed use-site containers into a scope the closure analysis had already determined; when the use-sites live in separate function bodies there is no common container, and resetting to the constructor body threw that scope away. Assisted-by: Claude:claude-opus-5:Claude Code
This commit is contained in:
@@ -902,5 +902,29 @@ namespace LocalFunctions
|
||||
#endif
|
||||
return outer;
|
||||
}
|
||||
|
||||
public void Issue3008_DisplayClassForwardedThroughLocalFunction(int start)
|
||||
{
|
||||
Run();
|
||||
void Run()
|
||||
{
|
||||
int captured = start;
|
||||
A();
|
||||
Console.WriteLine(captured);
|
||||
void A()
|
||||
{
|
||||
captured++;
|
||||
if (captured < 10)
|
||||
{
|
||||
B();
|
||||
}
|
||||
}
|
||||
void B()
|
||||
{
|
||||
captured += 2;
|
||||
A();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,22 +141,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
||||
foreach (var useSite in info.UseSites)
|
||||
{
|
||||
DetermineCaptureAndDeclarationScope(info, useSite);
|
||||
}
|
||||
|
||||
if (context.Function.Method.IsConstructor)
|
||||
if (context.Function.Method.IsConstructor)
|
||||
{
|
||||
// Local functions reached from a field initializer usually capture nothing, so
|
||||
// the closure analysis leaves no scope behind; the innermost block containing
|
||||
// all use-sites is a better place for them than the whole constructor body.
|
||||
// Use-sites spread over separate function bodies have no common block
|
||||
// container at all; there the scope from the closure analysis stands.
|
||||
BlockContainer useSiteScope = null;
|
||||
foreach (var useSite in info.UseSites)
|
||||
{
|
||||
if (localFunction.DeclarationScope == null)
|
||||
{
|
||||
localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite);
|
||||
}
|
||||
else
|
||||
{
|
||||
localFunction.DeclarationScope = FindCommonAncestorInstruction<BlockContainer>(useSite, localFunction.DeclarationScope);
|
||||
if (localFunction.DeclarationScope == null)
|
||||
{
|
||||
localFunction.DeclarationScope = (BlockContainer)context.Function.Body;
|
||||
}
|
||||
}
|
||||
useSiteScope = useSiteScope == null
|
||||
? BlockContainer.FindClosestContainer(useSite)
|
||||
: FindCommonAncestorInstruction<BlockContainer>(useSite, useSiteScope);
|
||||
if (useSiteScope == null)
|
||||
break;
|
||||
}
|
||||
localFunction.DeclarationScope = useSiteScope
|
||||
?? localFunction.DeclarationScope
|
||||
?? (BlockContainer)context.Function.Body;
|
||||
}
|
||||
|
||||
if (localFunction.DeclarationScope == null)
|
||||
@@ -341,9 +346,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
||||
return null;
|
||||
if (!(TransformDisplayClassUsage.IsPotentialClosure(context, field.Type.GetDefinition()) || context.Function.Method.DeclaringType.Equals(field.Type)))
|
||||
return null;
|
||||
foreach (var v in context.Function.Descendants.OfType<ILFunction>().SelectMany(f => f.Variables))
|
||||
return FindClosureVariableOfType(field.Type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the variable holding the display class instance of the given type, anywhere in the
|
||||
/// function tree currently being decompiled.
|
||||
/// </summary>
|
||||
private ILVariable FindClosureVariableOfType(IType type)
|
||||
{
|
||||
foreach (var v in context.Function.Descendants.OfType<ILFunction>().Prepend(context.Function).SelectMany(f => f.Variables))
|
||||
{
|
||||
if (!(TransformDisplayClassUsage.IsClosure(context, v, out var varType, out _) && varType.Equals(field.Type)))
|
||||
if (!(TransformDisplayClassUsage.IsClosure(context, v, out var varType, out _) && varType.Equals(type)))
|
||||
continue;
|
||||
return v;
|
||||
}
|
||||
@@ -713,6 +727,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
||||
}
|
||||
if (closureVar.Kind == VariableKind.NamedArgument)
|
||||
return false;
|
||||
if (parameterIndex >= 0 && closureVar.Kind == VariableKind.Parameter)
|
||||
{
|
||||
// The use-site sits inside another local function that received the display class as
|
||||
// a parameter and forwards it. A parameter has no initializer, so the scope the
|
||||
// display class was created in has to be recovered from the variable holding it.
|
||||
closureVar = FindClosureVariableOfType(closureVar.Type.UnwrapByRef()) ?? closureVar;
|
||||
}
|
||||
var initializer = GetClosureInitializer(closureVar);
|
||||
if (initializer == null)
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user