mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
Fix #3938: nested-type members must not inherit ExtensionInfo
The .NET 10 BCL ships static [Extension] classes that contain ordinary nested types (e.g. XDocumentExtensions.XDocumentNavigable). Decompiling such a nested type's member in isolation resolved the enclosing container's ExtensionInfo, and DecompileBody then dereferenced the missing extension-member mapping. A container without any extension blocks now reports no ExtensionInfo at all, and ResolveExtensionInfo applies a container's info only to members that actually belong to one of its extension blocks. Assisted-by: Claude:claude-fable-5:Claude Code
This commit is contained in:
committed by
Siegfried Pammer
parent
03965a2284
commit
a1726a9dbf
@@ -69,5 +69,53 @@ namespace ICSharpCode.Decompiler.Tests
|
||||
Assert.That(code, Does.Contain("Number = 42"));
|
||||
Assert.That(code, Does.Contain("Text = \"hello\""));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task NestedTypeMemberInClassicExtensionClass()
|
||||
{
|
||||
// #3938: [Extension] on the container alone must not make members of an ordinary
|
||||
// nested type extension members; here the container has no extension blocks at all.
|
||||
await DecompileNestedGetMethod("ClassicExtensions").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task NestedTypeMemberInExtensionBlockContainer()
|
||||
{
|
||||
// Same as above, but the container also has a real C# 14 extension block whose
|
||||
// ExtensionInfo must not be attributed to the nested type's members.
|
||||
await DecompileNestedGetMethod("BlockExtensions").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
static async Task DecompileNestedGetMethod(string containerName)
|
||||
{
|
||||
var csFile = Path.Combine(TestCasePath, "ExtensionContainerNestedType.cs");
|
||||
var compilation = await Tester.CompileCSharp(csFile, CompilerOptions.UseRoslynLatest | CompilerOptions.Optimize | CompilerOptions.Library).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var settings = new DecompilerSettings();
|
||||
using var file = new FileStream(compilation.PathToAssembly, FileMode.Open, FileAccess.Read);
|
||||
var module = new PEFile(compilation.PathToAssembly, file, PEStreamOptions.PrefetchEntireImage);
|
||||
var targetFramework = module.Metadata.DetectTargetFrameworkId();
|
||||
var resolver = new UniversalAssemblyResolver(compilation.PathToAssembly, false, targetFramework, null, PEStreamOptions.PrefetchMetadata);
|
||||
resolver.AddSearchDirectory(Tester.RefAssembliesToolset.GetPath(targetFramework));
|
||||
var typeSystem = new DecompilerTypeSystem(module, resolver, settings);
|
||||
var decompiler = new CSharpDecompiler(typeSystem, settings);
|
||||
|
||||
var getMethod = typeSystem.MainModule.TypeDefinitions
|
||||
.Single(t => t.Name == containerName)
|
||||
.NestedTypes.Single(t => t.Name == "Nested")
|
||||
.Methods.Single(m => m.Name == "Get");
|
||||
|
||||
var code = decompiler.Decompile(getMethod.MetadataToken).ToString();
|
||||
|
||||
Assert.That(code, Does.Contain("return 42;"));
|
||||
// The nested type's method must not be rendered inside an extension block.
|
||||
Assert.That(code, Does.Not.Contain("extension("));
|
||||
}
|
||||
finally
|
||||
{
|
||||
compilation.DeleteTempFiles();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ICSharpCode.Decompiler.Tests.TestCases.IsolatedDecompilation
|
||||
{
|
||||
// A static class whose only extension members use the classic "this" parameter syntax:
|
||||
// the type carries [Extension], but contains no C# 14 extension blocks.
|
||||
internal static class ClassicExtensions
|
||||
{
|
||||
public static int Twice(this int x)
|
||||
{
|
||||
return x * 2;
|
||||
}
|
||||
|
||||
private sealed class Nested
|
||||
{
|
||||
public int Get()
|
||||
{
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A static class that mixes a real C# 14 extension block with an ordinary nested type.
|
||||
internal static class BlockExtensions
|
||||
{
|
||||
extension(List<int> list)
|
||||
{
|
||||
public int DoubledCount => list.Count * 2;
|
||||
}
|
||||
|
||||
private sealed class Nested
|
||||
{
|
||||
public int Get()
|
||||
{
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1299,8 +1299,6 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
|
||||
bool first = true;
|
||||
ITypeDefinition? parentTypeDef = null;
|
||||
ExtensionInfo? parentExtensionInfo = null;
|
||||
|
||||
foreach (var entity in definitions)
|
||||
{
|
||||
switch (entity.Kind)
|
||||
@@ -1319,8 +1317,7 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
break;
|
||||
case HandleKind.MethodDefinition:
|
||||
IMethod method = module.GetDefinition((MethodDefinitionHandle)entity);
|
||||
parentExtensionInfo = method.ResolveExtensionInfo();
|
||||
syntaxTree.Members.Add(DoDecompile(method, decompileRun, new SimpleTypeResolveContext(method), parentExtensionInfo));
|
||||
syntaxTree.Members.Add(DoDecompile(method, decompileRun, new SimpleTypeResolveContext(method), method.ResolveExtensionInfo()));
|
||||
if (first)
|
||||
{
|
||||
parentTypeDef = method.DeclaringTypeDefinition;
|
||||
@@ -1337,14 +1334,14 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
break;
|
||||
case HandleKind.PropertyDefinition:
|
||||
IProperty property = module.GetDefinition((PropertyDefinitionHandle)entity);
|
||||
parentExtensionInfo = property.ResolveExtensionInfo();
|
||||
var propertyExtensionInfo = property.ResolveExtensionInfo();
|
||||
if (property.IsParameterizedProperty())
|
||||
{
|
||||
syntaxTree.Members.AddRange(DecompileParameterizedProperty(property, decompileRun, new SimpleTypeResolveContext(property), parentExtensionInfo));
|
||||
syntaxTree.Members.AddRange(DecompileParameterizedProperty(property, decompileRun, new SimpleTypeResolveContext(property), propertyExtensionInfo));
|
||||
}
|
||||
else
|
||||
{
|
||||
syntaxTree.Members.Add(DoDecompile(property, decompileRun, new SimpleTypeResolveContext(property), parentExtensionInfo));
|
||||
syntaxTree.Members.Add(DoDecompile(property, decompileRun, new SimpleTypeResolveContext(property), propertyExtensionInfo));
|
||||
}
|
||||
if (first)
|
||||
{
|
||||
|
||||
@@ -153,12 +153,16 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
|
||||
if ((module.TypeSystemOptions & TypeSystemOptions.ExtensionMembers) == 0)
|
||||
return null;
|
||||
var extensionInfo = LazyInit.VolatileRead(ref this.extensionInfo);
|
||||
if (extensionInfo != null)
|
||||
return extensionInfo;
|
||||
extensionInfo = new ExtensionInfo(module, this);
|
||||
if ((module.TypeSystemOptions & TypeSystemOptions.Uncached) != 0)
|
||||
return extensionInfo;
|
||||
return LazyInit.GetOrSet(ref this.extensionInfo, extensionInfo);
|
||||
if (extensionInfo == null)
|
||||
{
|
||||
extensionInfo = new ExtensionInfo(module, this);
|
||||
if ((module.TypeSystemOptions & TypeSystemOptions.Uncached) == 0)
|
||||
extensionInfo = LazyInit.GetOrSet(ref this.extensionInfo, extensionInfo);
|
||||
}
|
||||
// A static class whose extension methods all use the classic "this" parameter
|
||||
// syntax carries [Extension] but has no extension blocks; it is not an
|
||||
// extension container.
|
||||
return extensionInfo.ExtensionGroups.Count > 0 ? extensionInfo : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -890,6 +890,11 @@ namespace ICSharpCode.Decompiler.TypeSystem
|
||||
return ns;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="ExtensionInfo"/> of the container type the member's extension
|
||||
/// block belongs to, or null if the member is not part of an extension block. Fields and
|
||||
/// events always return null: extension blocks cannot declare them.
|
||||
/// </summary>
|
||||
public static ExtensionInfo? ResolveExtensionInfo(this IMember member)
|
||||
{
|
||||
if (member is null)
|
||||
@@ -898,7 +903,19 @@ namespace ICSharpCode.Decompiler.TypeSystem
|
||||
}
|
||||
var td = member.DeclaringTypeDefinition;
|
||||
Debug.Assert(td != null, "IMember.DeclaringTypeDefinition should never be null");
|
||||
return td.DeclaringTypeDefinition?.ExtensionInfo ?? td.DeclaringTypeDefinition?.DeclaringTypeDefinition?.ExtensionInfo;
|
||||
var info = td.DeclaringTypeDefinition?.ExtensionInfo ?? td.DeclaringTypeDefinition?.DeclaringTypeDefinition?.ExtensionInfo;
|
||||
if (info == null)
|
||||
return null;
|
||||
// The container's info only applies to members declared in one of its extension
|
||||
// blocks; members of other nested types must not pick it up.
|
||||
IMethod? method = member switch {
|
||||
IMethod m => m,
|
||||
IProperty p => p.Getter ?? p.Setter,
|
||||
_ => null
|
||||
};
|
||||
if (method == null || info.InfoOfExtensionMember((IMethod)method.MemberDefinition) == null)
|
||||
return null;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user