Browse Source

Merge pull request #4100 from icsharpcode/fix/3008-callbuilder-assert

Fix #3008: local function forwarding a display class loses its scope
pull/4104/head
Siegfried Pammer 2 weeks ago committed by GitHub
parent
commit
7873fd5481
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  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