Browse Source

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
pull/4100/head
Siegfried Pammer 2 weeks ago
parent
commit
f007528af9
  1. 24
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs
  2. 51
      ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs

24
ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs

@ -902,5 +902,29 @@ namespace LocalFunctions @@ -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();
}
}
}
}
}

51
ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs

@ -141,22 +141,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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 @@ -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 @@ -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;

Loading…
Cancel
Save