Roslyn writes [Nullable] for an anonymous type as if the type was not generic, so its type arguments have no flags. Taking one per argument shifted every annotation after the anonymous type, which showed on the cached lambda fields in <>c, and tripped the dummy-flag assert when the shifted stream landed on a generic struct. [Dynamic] and [TupleElementNames] do have entries for those arguments (checked against what Roslyn emits), so only the nullable index skips them.
156 lines
7.3 KiB
C#
156 lines
7.3 KiB
C#
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
// software and associated documentation files (the "Software"), to deal in the Software
|
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all copies or
|
|
// substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection.PortableExecutable;
|
|
using System.Threading.Tasks;
|
|
|
|
using ICSharpCode.Decompiler.CSharp;
|
|
using ICSharpCode.Decompiler.Metadata;
|
|
using ICSharpCode.Decompiler.Tests.Helpers;
|
|
using ICSharpCode.Decompiler.TypeSystem;
|
|
|
|
using NUnit.Framework;
|
|
|
|
namespace ICSharpCode.Decompiler.Tests
|
|
{
|
|
/// <summary>
|
|
/// Decompiling a single member in isolation (the "IL with C#" view does this, decompiling one
|
|
/// method handle at a time) gives the transforms only a partial syntax tree. This is the mirror
|
|
/// image of the whole-file ILPretty tests: same assembled-IL fixture, but the assertion is about
|
|
/// one method's output rather than the full module.
|
|
/// </summary>
|
|
[TestFixture]
|
|
public class IsolatedMethodDecompilationTests
|
|
{
|
|
static readonly string TestCasePath = Tester.TestCasePath + "/IsolatedDecompilation";
|
|
|
|
[Test]
|
|
public async Task StaticConstructorKeepsFieldInitializers()
|
|
{
|
|
var ilFile = Path.Combine(TestCasePath, "IsolatedStaticCtor.il");
|
|
var assembly = await Tester.AssembleIL(ilFile, AssemblerOptions.Library).ConfigureAwait(false);
|
|
|
|
var settings = new DecompilerSettings();
|
|
using var file = new FileStream(assembly, FileMode.Open, FileAccess.Read);
|
|
var module = new PEFile(assembly, file, PEStreamOptions.PrefetchEntireImage);
|
|
var targetFramework = module.Metadata.DetectTargetFrameworkId();
|
|
var resolver = new UniversalAssemblyResolver(assembly, 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 staticCtor = typeSystem.MainModule.TypeDefinitions
|
|
.Single(t => t.Name == "C")
|
|
.Methods.Single(m => m.IsStatic && m.IsConstructor);
|
|
|
|
// Decompile just the .cctor handle: the type's field declarations are not part of the
|
|
// resulting syntax tree, so the static field initializers have nowhere to move to.
|
|
var code = decompiler.Decompile(staticCtor.MetadataToken).ToString();
|
|
|
|
// They must therefore remain as assignments in the constructor body rather than being
|
|
// dropped (#3774).
|
|
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);
|
|
}
|
|
|
|
[Test]
|
|
public async Task AnonymousTypeArgumentsHaveNoNullableFlags()
|
|
{
|
|
// #4016: NullableAttribute has no entries for the type arguments of an anonymous type,
|
|
// so the annotations after it must not shift. The affected fields live in the lambda
|
|
// cache class, which only shows up when it is decompiled on its own.
|
|
var csFile = Path.Combine(TestCasePath, "AnonymousTypeNullability.cs");
|
|
var compilation = await Tester.CompileCSharp(csFile, CompilerOptions.UseRoslynLatest | CompilerOptions.Optimize | CompilerOptions.Library | CompilerOptions.NullableEnable).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 lambdaCache = typeSystem.MainModule.TypeDefinitions
|
|
.Single(t => t.Name == "AnonymousTypeNullability")
|
|
.NestedTypes.Single(t => t.Name == "<>c");
|
|
|
|
var code = decompiler.Decompile(lambdaCache.MetadataToken).ToString();
|
|
|
|
Assert.That(code, Does.Contain("Func<<>f__AnonymousType0<string, string>, List<string?>> <>9__0_1;"));
|
|
Assert.That(code, Does.Contain("Func<<>f__AnonymousType1<string>, KeyValuePair<string, string?>> <>9__1_1;"));
|
|
}
|
|
finally
|
|
{
|
|
compilation.DeleteTempFiles();
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|