Browse Source

Merge branch 'master' into feature/ReferenceAssemblyOverly

pull/3013/head
workgroupengineering 11 months ago committed by GitHub
parent
commit
4b92ed6fa6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      .github/workflows/build-ilspy.yml
  2. 3
      BuildTools/format.bat
  3. 11
      BuildTools/pre-commit
  4. 4
      Directory.Packages.props
  5. 4
      ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
  6. 7
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  7. 3
      ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs
  8. 68
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs
  9. 4
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs
  10. 9
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs
  11. 11
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs
  12. 15
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs
  13. 19
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs
  14. 25
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/StringInterpolation.cs
  15. 19
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs
  16. 8
      ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs
  17. 15
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  18. 1
      ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs
  19. 292
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  20. 51
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  21. 4
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
  22. 20
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  23. 6
      ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs
  24. 4
      ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs
  25. 4
      ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs
  26. 28
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  27. 50
      ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs
  28. 30
      ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
  29. 62
      ICSharpCode.Decompiler/CSharp/Transforms/DecimalConstantTransform.cs
  30. 6
      ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
  31. 1
      ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs
  32. 2
      ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs
  33. 11
      ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs
  34. 8
      ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs
  35. 47
      ICSharpCode.Decompiler/DecompilerSettings.cs
  36. 4
      ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs
  37. 3
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  38. 159
      ICSharpCode.Decompiler/IL/ApplyPdbLocalTypeInfoTypeVisitor.cs
  39. 4
      ICSharpCode.Decompiler/IL/ILReader.cs
  40. 2
      ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs
  41. 7
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  42. 68
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  43. 10
      ICSharpCode.Decompiler/IL/Transforms/IntroduceRefReadOnlyModifierOnLocals.cs
  44. 4
      ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs
  45. 58
      ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
  46. 90
      ICSharpCode.Decompiler/IL/Transforms/ParameterNullCheckTransform.cs
  47. 114
      ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
  48. 13
      ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs
  49. 24
      ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
  50. 3
      ICSharpCode.Decompiler/Semantics/ByReferenceResolveResult.cs
  51. 9
      ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs
  52. 12
      ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
  53. 15
      ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs
  54. 18
      ICSharpCode.Decompiler/TypeSystem/IParameter.cs
  55. 3
      ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs
  56. 17
      ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs
  57. 2
      ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs
  58. 25
      ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs
  59. 3
      ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs
  60. 76
      ICSharpCode.ILSpyX/Analyzers/AnalyzerContext.cs
  61. 14
      ICSharpCode.ILSpyX/Analyzers/ExportAnalyzerAttribute.cs
  62. 43
      ILSpy.Tests/Analyzers/ExportAnalyzerAttributeTests.cs
  63. 1
      ILSpy.Tests/ILSpy.Tests.csproj
  64. 1
      ILSpy/Languages/CSharpLanguage.cs
  65. 18
      ILSpy/Properties/Resources.Designer.cs
  66. 6
      ILSpy/Properties/Resources.resx
  67. 3
      ILSpy/Properties/Resources.zh-Hans.resx

2
.github/workflows/build-ilspy.yml

@ -35,7 +35,7 @@ jobs: @@ -35,7 +35,7 @@ jobs:
uses: microsoft/setup-msbuild@v2
- name: Install dotnet-format
run: dotnet tool install -g dotnet-format --version "6.2.315104" --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json
run: dotnet tool install -g dotnet-format --version "8.0.453106" --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json
- name: Get Version
id: version

3
BuildTools/format.bat

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
@rem This file can be used to trigger the commit hook's formatting,
@rem modifying the local formatting even if not committing all changes.
"%ProgramFiles%\Git\usr\bin\bash.exe" BuildTools\pre-commit --format

11
BuildTools/pre-commit

@ -5,16 +5,21 @@ @@ -5,16 +5,21 @@
set -eu
DOTNET_FORMAT_VERSION=6.2.315104
DOTNET_FORMAT_VERSION=8.0.453106
DOTNET_PATH="$LOCALAPPDATA/ICSharpCode/ILSpy/dotnet-format-$DOTNET_FORMAT_VERSION"
if [ ! -d "$DOTNET_PATH" ]; then
dotnet tool install --tool-path "$DOTNET_PATH" dotnet-format --version "$DOTNET_FORMAT_VERSION" --add-source "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json"
echo "Downloading dotnet-format $DOTNET_FORMAT_VERSION..."
dotnet tool install --tool-path "$DOTNET_PATH" dotnet-format --version "$DOTNET_FORMAT_VERSION" --add-source "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json"
fi
"$DOTNET_PATH/dotnet-format.exe" --version
if git diff --quiet --ignore-submodules; then
if [ "${1:-}" = "--format" ]; then
# called via format.bat
"$DOTNET_PATH/dotnet-format.exe" whitespace --no-restore --verbosity detailed ILSpy.sln
elif git diff --quiet --ignore-submodules; then
"$DOTNET_PATH/dotnet-format.exe" whitespace --no-restore --verbosity detailed ILSpy.sln
git add -u -- \*\*.cs
else
echo Partial commit: only verifying formatting
exec "$DOTNET_PATH/dotnet-format.exe" whitespace --verify-no-changes --no-restore --verbosity detailed ILSpy.sln
fi

4
Directory.Packages.props

@ -16,8 +16,8 @@ @@ -16,8 +16,8 @@
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
<PackageVersion Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<PackageVersion Include="McMaster.Extensions.Hosting.CommandLine" Version="4.1.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.9.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.10.0" />
<PackageVersion Include="Microsoft.DiaSymReader.Converter.Xml" Version="1.1.0-beta2-22171-02" />
<PackageVersion Include="Microsoft.DiaSymReader" Version="1.4.0" />
<PackageVersion Include="Microsoft.DiaSymReader.Native" Version="17.0.0-beta1.21524.1" />

4
ICSharpCode.Decompiler.Tests/Helpers/Tester.cs

@ -528,7 +528,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -528,7 +528,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
{
CompilerResults results = new CompilerResults();
results.PathToAssembly = outputFileName;
string testBasePath = RoundtripAssembly.TestDir;
string testBasePath = Roundtrip.RoundtripAssembly.TestDir;
if (!Directory.Exists(testBasePath))
{
Assert.Ignore($"Compilation with mcs ignored: test directory '{testBasePath}' needs to be checked out separately." + Environment.NewLine +
@ -596,7 +596,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -596,7 +596,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
CompilerOptions.UseRoslyn1_3_2 => CSharp.LanguageVersion.CSharp6,
CompilerOptions.UseRoslyn2_10_0 => CSharp.LanguageVersion.CSharp7_3,
CompilerOptions.UseRoslyn3_11_0 => CSharp.LanguageVersion.CSharp9_0,
_ => cscOptions.HasFlag(CompilerOptions.Preview) ? CSharp.LanguageVersion.Latest : CSharp.LanguageVersion.CSharp11_0,
_ => cscOptions.HasFlag(CompilerOptions.Preview) ? CSharp.LanguageVersion.Latest : CSharp.LanguageVersion.CSharp12_0,
};
DecompilerSettings settings = new(langVersion) {
// Never use file-scoped namespaces

7
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<NoWarn>1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632;626;8618;8714;8602;8981</NoWarn>
<DefineConstants>ROSLYN;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120</DefineConstants>
<GenerateAssemblyVersionAttribute>False</GenerateAssemblyVersionAttribute>
<GenerateAssemblyFileVersionAttribute>False</GenerateAssemblyFileVersionAttribute>
@ -40,11 +41,11 @@ @@ -40,11 +41,11 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;DEBUG;ROSLYN;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100</DefineConstants>
<DefineConstants>TRACE;DEBUG;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>TRACE;ROSLYN;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100</DefineConstants>
<DefineConstants>TRACE;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<ItemGroup>
@ -152,7 +153,7 @@ @@ -152,7 +153,7 @@
<None Include="TestCases\Pretty\Records.cs" />
<Compile Include="TestCases\VBPretty\Issue2192.cs" />
<Compile Include="Util\FileUtilityTests.cs" />
<None Include="TestCases\Pretty\FunctionPointers.cs" />
<Compile Include="TestCases\Pretty\FunctionPointers.cs" />
<None Include="TestCases\Pretty\CS9_ExtensionGetEnumerator.cs" />
<None Include="TestCases\Pretty\UsingVariables.cs" />
<None Include="TestCases\Pretty\AsyncForeach.cs" />

3
ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs

@ -29,11 +29,12 @@ using CliWrap; @@ -29,11 +29,12 @@ using CliWrap;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.ProjectDecompiler;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Tests;
using ICSharpCode.Decompiler.Tests.Helpers;
using NUnit.Framework;
namespace ICSharpCode.Decompiler.Tests
namespace ICSharpCode.Decompiler.Roundtrip
{
[TestFixture, Parallelizable(ParallelScope.All)]
public class RoundtripAssembly

68
ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs

@ -36,6 +36,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -36,6 +36,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
Issue1747();
CallAmbiguousOutParam();
CallWithInParam();
CallWithRefReadOnlyParam();
#if CS90
NativeIntTests(new IntPtr(1), 2);
#endif
@ -277,6 +278,73 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -277,6 +278,73 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
}
#endregion
#region Ref readonly Parameter
static void CallWithRefReadOnlyParam()
{
#if CS120
#pragma warning disable CS9193
Console.WriteLine("OverloadSetWithRefReadOnlyParam:");
OverloadSetWithRefReadOnlyParam(1);
OverloadSetWithRefReadOnlyParam(2L);
int i = 3;
OverloadSetWithRefReadOnlyParam(in i);
OverloadSetWithRefReadOnlyParam((long)4);
Console.WriteLine("OverloadSetWithRefReadOnlyParam2:");
OverloadSetWithRefReadOnlyParam2(1);
OverloadSetWithRefReadOnlyParam2((object)1);
Console.WriteLine("OverloadSetWithRefReadOnlyParam3:");
OverloadSetWithRefReadOnlyParam3(1);
OverloadSetWithRefReadOnlyParam3<int>(2);
OverloadSetWithRefReadOnlyParam3((object)3);
Console.WriteLine("RefReadOnlyVsRegularParam:");
RefReadOnlyVsRegularParam(1);
i = 2;
RefReadOnlyVsRegularParam(in i);
#endif
}
#if CS120
static void OverloadSetWithRefReadOnlyParam(ref readonly int i)
{
Console.WriteLine("ref readonly int " + i);
}
static void OverloadSetWithRefReadOnlyParam(long l)
{
Console.WriteLine("long " + l);
}
static void OverloadSetWithRefReadOnlyParam2(ref readonly long i)
{
Console.WriteLine("ref readonly long " + i);
}
static void OverloadSetWithRefReadOnlyParam2(object o)
{
Console.WriteLine("object " + o);
}
static void OverloadSetWithRefReadOnlyParam3(ref readonly int i)
{
Console.WriteLine("ref readonly int " + i);
}
static void OverloadSetWithRefReadOnlyParam3<T>(T a)
{
Console.WriteLine("T " + a);
}
static void RefReadOnlyVsRegularParam(ref readonly int i)
{
Console.WriteLine("ref readonly int " + i);
}
static void RefReadOnlyVsRegularParam(int i)
{
Console.WriteLine("int " + i);
}
#endif
#endregion
#region In Parameter
static void CallWithInParam()
{

4
ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs

@ -4719,12 +4719,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -4719,12 +4719,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
#endif
private static void StringPropertyCompoundAssign()
private static void StringPropertyCompoundAssign(char c)
{
StaticStringProperty += "a";
StaticStringProperty += 1;
StaticStringProperty += c;
new CustomClass().StringProp += "a";
new CustomClass().StringProp += 1;
new CustomClass().StringProp += c;
}
public uint PreIncrementIndexer(string name)

9
ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs

@ -1,4 +1,7 @@ @@ -1,4 +1,7 @@
using System;
#if CS120
using System.Collections.Generic;
#endif
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
@ -74,6 +77,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -74,6 +77,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
#endif
#if CS120
private static void CallWithRefReadonly(ref readonly Dictionary<object, dynamic> d)
{
}
#endif
private static void CallWithRef(ref dynamic d)
{
}

11
ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs

@ -95,14 +95,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -95,14 +95,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public unsafe delegate*<object, ref readonly dynamic> F12;
public unsafe delegate*<in dynamic, object> F13;
public unsafe delegate*<out dynamic, object> F14;
public unsafe D<delegate*<dynamic>[], dynamic> F15;
public unsafe delegate*<A<object>.B<dynamic>> F16;
#if CS120
public unsafe delegate*<ref readonly dynamic, object> F15;
#endif
public unsafe D<delegate*<dynamic>[], dynamic> F16;
public unsafe delegate*<A<object>.B<dynamic>> F17;
}
internal class FunctionPointersWithNativeIntegerTypes
{
public unsafe delegate*<nint, nint, nint> F1;
#if !(CS110 && NET70)
#if !(CS110 && NET70)
public unsafe delegate*<IntPtr, IntPtr, nint> F2;
public unsafe delegate*<nint, IntPtr, IntPtr> F3;
public unsafe delegate*<IntPtr, nint, IntPtr> F4;
@ -111,7 +114,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -111,7 +114,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public unsafe delegate*<delegate*<IntPtr, IntPtr, nint>, IntPtr> F7;
public unsafe delegate*<IntPtr, delegate*<IntPtr, nint, IntPtr>> F8;
public unsafe delegate*<IntPtr, delegate*<IntPtr, IntPtr, IntPtr>> F9;
#endif
#endif
}
internal class FunctionPointersWithRefParams

15
ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs

@ -24,6 +24,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -24,6 +24,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
internal class OptionalArguments : List<int>
{
public delegate int D(int p = 10);
public enum MyEnum
{
A,
@ -283,5 +285,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -283,5 +285,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
}
#endif
#if CS120
public static D LambdaWithOptionalParameter()
{
return (int x = 10) => x;
}
public static void Use(D d)
{
d();
d(42);
}
#endif
}
}

19
ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs

@ -320,5 +320,24 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -320,5 +320,24 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
LastOrDefault() = 10000;
Console.WriteLine(ElementAtOrDefault(-5));
}
#if CS120
public ref readonly int M(in int x)
{
return ref x;
}
public ref readonly int M2(ref readonly int x)
{
return ref x;
}
public void Test()
{
int x = 32;
M(in x);
M2(in x);
}
#endif
}
}

25
ICSharpCode.Decompiler.Tests/TestCases/Pretty/StringInterpolation.cs

@ -117,5 +117,30 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -117,5 +117,30 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public void RequiresCast(IFormattable value)
{
}
public string ConcatStringCharSC(string s, char c)
{
return s + c;
}
public string ConcatStringCharCS(string s, char c)
{
return c + s;
}
public string ConcatStringCharSCS(string s, char c)
{
return s + c + s;
}
public string ConcatStringCharCSS(string s, char c)
{
return c + s + s;
}
public string ConcatStringCharCSSC(string s, char c)
{
return c + s + s + c;
}
}
}

19
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs

@ -291,5 +291,24 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -291,5 +291,24 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public static void Test(object x)
{
}
#if CS120
public static void AcceptIn(in S o)
{
}
public static void AcceptRefReadOnly(ref readonly S o)
{
}
private static void Use(in S param)
{
AcceptIn(new S(5));
S o = new S(10);
AcceptRefReadOnly(in o);
AcceptIn(in param);
AcceptRefReadOnly(in param);
}
#endif
}
}

8
ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs

@ -1390,18 +1390,18 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem @@ -1390,18 +1390,18 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem
{
ITypeDefinition impl = GetTypeDefinition(typeof(ExplicitGenericInterfaceImplementation));
IType genericInterfaceOfString = compilation.FindType(typeof(IGenericInterface<string>));
IMethod implMethod1 = impl.Methods.Single(m => !m.IsConstructor && !m.Parameters[1].IsRef);
IMethod implMethod2 = impl.Methods.Single(m => !m.IsConstructor && m.Parameters[1].IsRef);
IMethod implMethod1 = impl.Methods.Single(m => !m.IsConstructor && m.Parameters[1].ReferenceKind == ReferenceKind.None);
IMethod implMethod2 = impl.Methods.Single(m => !m.IsConstructor && m.Parameters[1].ReferenceKind == ReferenceKind.Ref);
Assert.That(implMethod1.IsExplicitInterfaceImplementation);
Assert.That(implMethod2.IsExplicitInterfaceImplementation);
IMethod interfaceMethod1 = (IMethod)implMethod1.ExplicitlyImplementedInterfaceMembers.Single();
Assert.That(interfaceMethod1.DeclaringType, Is.EqualTo(genericInterfaceOfString));
Assert.That(!interfaceMethod1.Parameters[1].IsRef);
Assert.That(interfaceMethod1.Parameters[1].ReferenceKind == ReferenceKind.None);
IMethod interfaceMethod2 = (IMethod)implMethod2.ExplicitlyImplementedInterfaceMembers.Single();
Assert.That(interfaceMethod2.DeclaringType, Is.EqualTo(genericInterfaceOfString));
Assert.That(interfaceMethod2.Parameters[1].IsRef);
Assert.That(interfaceMethod2.Parameters[1].ReferenceKind == ReferenceKind.Ref);
}
[Test]

15
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -91,7 +91,6 @@ namespace ICSharpCode.Decompiler.CSharp @@ -91,7 +91,6 @@ namespace ICSharpCode.Decompiler.CSharp
new InlineReturnTransform(), // must run before DetectPinnedRegions
new RemoveInfeasiblePathTransform(),
new DetectPinnedRegions(), // must run after inlining but before non-critical control flow transforms
new ParameterNullCheckTransform(), // must run after inlining but before yield/async
new YieldReturnDecompiler(), // must run after inlining but before loop detection
new AsyncAwaitDecompiler(), // must run after inlining but before loop detection
new DetectCatchWhenConditionBlocks(), // must run after inlining but before loop detection
@ -185,7 +184,6 @@ namespace ICSharpCode.Decompiler.CSharp @@ -185,7 +184,6 @@ namespace ICSharpCode.Decompiler.CSharp
new AddCheckedBlocks(),
new DeclareVariables(), // should run after most transforms that modify statements
new TransformFieldAndConstructorInitializers(), // must run after DeclareVariables
new DecimalConstantTransform(),
new PrettifyAssignments(), // must run after DeclareVariables
new IntroduceUsingDeclarations(),
new IntroduceExtensionMethods(), // must run after IntroduceUsingDeclarations
@ -1169,7 +1167,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1169,7 +1167,7 @@ namespace ICSharpCode.Decompiler.CSharp
Roles.Comment);
var forwardingCall = new InvocationExpression(new MemberReferenceExpression(new ThisReferenceExpression(), memberDecl.Name,
methodDecl.TypeParameters.Select(tp => new SimpleType(tp.Name))),
methodDecl.Parameters.Select(p => ForwardParameter(p))
methodDecl.Parameters.Select(ForwardParameter)
);
if (m.ReturnType.IsKnownType(KnownTypeCode.Void))
{
@ -1187,12 +1185,17 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1187,12 +1185,17 @@ namespace ICSharpCode.Decompiler.CSharp
{
switch (p.ParameterModifier)
{
case ParameterModifier.Ref:
case ReferenceKind.None:
return new IdentifierExpression(p.Name);
case ReferenceKind.Ref:
case ReferenceKind.RefReadOnly:
return new DirectionExpression(FieldDirection.Ref, new IdentifierExpression(p.Name));
case ParameterModifier.Out:
case ReferenceKind.Out:
return new DirectionExpression(FieldDirection.Out, new IdentifierExpression(p.Name));
case ReferenceKind.In:
return new DirectionExpression(FieldDirection.In, new IdentifierExpression(p.Name));
default:
return new IdentifierExpression(p.Name);
throw new NotSupportedException();
}
}

1
ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs

@ -34,6 +34,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -34,6 +34,7 @@ namespace ICSharpCode.Decompiler.CSharp
CSharp9_0 = 900,
CSharp10_0 = 1000,
CSharp11_0 = 1100,
CSharp12_0 = 1200,
Preview = 1100,
Latest = 0x7FFFFFFF
}

292
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -78,7 +78,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -78,7 +78,7 @@ namespace ICSharpCode.Decompiler.CSharp
ResolveResult GetResolveResult(int index, TranslatedExpression expression)
{
var param = expectedParameters[index];
if (useImplicitlyTypedOut && param.IsOut && expression.Type is ByReferenceType brt)
if (useImplicitlyTypedOut && param.ReferenceKind == ReferenceKind.Out && expression.Type is ByReferenceType brt)
return new OutVarResolveResult(brt.ElementType);
return expression.ResolveResult;
}
@ -134,8 +134,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -134,8 +134,7 @@ namespace ICSharpCode.Decompiler.CSharp
{
if (!useImplicitlyTypedOut)
return expression;
if (expression.GetResolveResult() is ByReferenceResolveResult brrr
&& brrr.IsOut)
if (expression.GetResolveResult() is ByReferenceResolveResult { ReferenceKind: ReferenceKind.Out } brrr)
{
expression.AddAnnotation(UseImplicitlyTypedOutAnnotation.Instance);
}
@ -224,10 +223,94 @@ namespace ICSharpCode.Decompiler.CSharp @@ -224,10 +223,94 @@ namespace ICSharpCode.Decompiler.CSharp
valueTupleAssembly: inst.Method.DeclaringType.GetDefinition()?.ParentModule
)).WithILInstruction(inst);
}
if (settings.StringConcat && IsSpanBasedStringConcat(inst, out var operands))
{
return BuildStringConcat(inst.Method, operands).WithILInstruction(inst);
}
return Build(inst.OpCode, inst.Method, inst.Arguments, constrainedTo: inst.ConstrainedTo)
.WithILInstruction(inst);
}
private ExpressionWithResolveResult BuildStringConcat(IMethod method, List<(ILInstruction Instruction, KnownTypeCode TypeCode)> operands)
{
IType type = typeSystem.FindType(operands[0].TypeCode);
ExpressionWithResolveResult result = expressionBuilder.Translate(operands[0].Instruction, type).ConvertTo(type, expressionBuilder);
var rr = new MemberResolveResult(null, method);
for (int i = 1; i < operands.Count; i++)
{
type = typeSystem.FindType(operands[i].TypeCode);
var expr = expressionBuilder.Translate(operands[i].Instruction, type).ConvertTo(type, expressionBuilder);
result = new BinaryOperatorExpression(result.Expression, BinaryOperatorType.Add, expr).WithRR(rr);
}
return result;
}
static bool IsSpanBasedStringConcat(CallInstruction call, out List<(ILInstruction, KnownTypeCode)> operands)
{
operands = null;
if (!IsSpanBasedStringConcat(call.Method))
{
return false;
}
int? firstStringArgumentIndex = null;
operands = new();
foreach (var arg in call.Arguments)
{
if (arg is Call opImplicit && IsStringToReadOnlySpanCharImplicitConversion(opImplicit.Method))
{
firstStringArgumentIndex ??= arg.ChildIndex;
operands.Add((opImplicit.Arguments.Single(), KnownTypeCode.String));
}
else if (arg is NewObj { Arguments: [AddressOf addressOf] } newObj && ILInlining.IsReadOnlySpanCharCtor(newObj.Method))
{
operands.Add((addressOf.Value, KnownTypeCode.Char));
}
else
{
return false;
}
}
return call.Arguments.Count >= 2 && firstStringArgumentIndex <= 1;
}
internal static bool IsSpanBasedStringConcat(IMethod method)
{
if (method is not { Name: "Concat", IsStatic: true })
{
return false;
}
if (!method.DeclaringType.IsKnownType(KnownTypeCode.String))
{
return false;
}
foreach (var p in method.Parameters)
{
if (!p.Type.IsKnownType(KnownTypeCode.ReadOnlySpanOfT))
return false;
if (!p.Type.TypeArguments[0].IsKnownType(KnownTypeCode.Char))
return false;
}
return true;
}
internal static bool IsStringToReadOnlySpanCharImplicitConversion(IMethod method)
{
return method.IsOperator
&& method.Name == "op_Implicit"
&& method.Parameters.Count == 1
&& method.ReturnType.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)
&& method.ReturnType.TypeArguments[0].IsKnownType(KnownTypeCode.Char)
&& method.Parameters[0].Type.IsKnownType(KnownTypeCode.String);
}
public ExpressionWithResolveResult Build(OpCode callOpCode, IMethod method,
IReadOnlyList<ILInstruction> callArguments,
IReadOnlyList<int> argumentToParameterMap = null,
@ -345,69 +428,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -345,69 +428,11 @@ namespace ICSharpCode.Decompiler.CSharp
argumentList.GetArgumentResolveResults(), isExpandedForm: argumentList.IsExpandedForm, isDelegateInvocation: true));
}
if (settings.StringInterpolation && IsInterpolatedStringCreation(method, argumentList) &&
TryGetStringInterpolationTokens(argumentList, out string format, out var tokens))
if (settings.StringInterpolation && IsInterpolatedStringCreation(method, argumentList))
{
var arguments = argumentList.Arguments;
var content = new List<InterpolatedStringContent>();
bool unpackSingleElementArray = !argumentList.IsExpandedForm && argumentList.Length == 2
&& argumentList.Arguments[1].Expression is ArrayCreateExpression ace
&& ace.Initializer?.Elements.Count == 1;
void UnpackSingleElementArray(ref TranslatedExpression argument)
{
if (!unpackSingleElementArray)
return;
var arrayCreation = (ArrayCreateExpression)argumentList.Arguments[1].Expression;
var arrayCreationRR = (ArrayCreateResolveResult)argumentList.Arguments[1].ResolveResult;
var element = arrayCreation.Initializer.Elements.First().Detach();
argument = new TranslatedExpression(element, arrayCreationRR.InitializerElements.First());
}
if (tokens.Count > 0)
{
foreach (var (kind, index, alignment, text) in tokens)
{
TranslatedExpression argument;
switch (kind)
{
case TokenKind.String:
content.Add(new InterpolatedStringText(text));
break;
case TokenKind.Argument:
argument = arguments[index + 1];
UnpackSingleElementArray(ref argument);
content.Add(new Interpolation(argument));
break;
case TokenKind.ArgumentWithFormat:
argument = arguments[index + 1];
UnpackSingleElementArray(ref argument);
content.Add(new Interpolation(argument, suffix: text));
break;
case TokenKind.ArgumentWithAlignment:
argument = arguments[index + 1];
UnpackSingleElementArray(ref argument);
content.Add(new Interpolation(argument, alignment));
break;
case TokenKind.ArgumentWithAlignmentAndFormat:
argument = arguments[index + 1];
UnpackSingleElementArray(ref argument);
content.Add(new Interpolation(argument, alignment, text));
break;
}
}
var formattableStringType = expressionBuilder.compilation.FindType(KnownTypeCode.FormattableString);
var isrr = new InterpolatedStringResolveResult(expressionBuilder.compilation.FindType(KnownTypeCode.String),
format, argumentList.GetArgumentResolveResults(1).ToArray());
var expr = new InterpolatedStringExpression();
expr.Content.AddRange(content);
if (method.Name == "Format")
return expr.WithRR(isrr);
return new CastExpression(expressionBuilder.ConvertType(formattableStringType),
expr.WithRR(isrr))
.WithRR(new ConversionResolveResult(formattableStringType, isrr, Conversion.ImplicitInterpolatedStringConversion));
}
var result = HandleStringInterpolation(method, argumentList);
if (result.Expression != null)
return result;
}
int allowedParamCount = (method.ReturnType.IsKnownType(KnownTypeCode.Void) ? 1 : 0);
@ -431,6 +456,20 @@ namespace ICSharpCode.Decompiler.CSharp @@ -431,6 +456,20 @@ namespace ICSharpCode.Decompiler.CSharp
return HandleImplicitConversion(method, argumentList.Arguments[0]);
}
if (settings.LiftNullables && method.Name == "GetValueOrDefault"
&& method.DeclaringType.IsKnownType(KnownTypeCode.NullableOfT)
&& method.DeclaringType.TypeArguments[0].IsKnownType(KnownTypeCode.Boolean)
&& argumentList.Length == 0)
{
argumentList.CheckNoNamedOrOptionalArguments();
return new BinaryOperatorExpression(
target.Expression,
BinaryOperatorType.Equality,
new PrimitiveExpression(true))
.WithRR(new CSharpInvocationResolveResult(target.ResolveResult, method,
argumentList.GetArgumentResolveResults(), isExpandedForm: argumentList.IsExpandedForm));
}
var transform = GetRequiredTransformationsForCall(expectedTargetDetails, method, ref target,
ref argumentList, CallTransformation.All, out IParameterizedMember foundMethod);
@ -482,6 +521,75 @@ namespace ICSharpCode.Decompiler.CSharp @@ -482,6 +521,75 @@ namespace ICSharpCode.Decompiler.CSharp
argumentList.GetArgumentResolveResultsDirect(), isExpandedForm: argumentList.IsExpandedForm));
}
private ExpressionWithResolveResult HandleStringInterpolation(IMethod method, ArgumentList argumentList)
{
if (!TryGetStringInterpolationTokens(argumentList, out string format, out var tokens))
return default;
var arguments = argumentList.Arguments;
var content = new List<InterpolatedStringContent>();
bool unpackSingleElementArray = !argumentList.IsExpandedForm && argumentList.Length == 2
&& argumentList.Arguments[1].Expression is ArrayCreateExpression ace
&& ace.Initializer?.Elements.Count == 1;
void UnpackSingleElementArray(ref TranslatedExpression argument)
{
if (!unpackSingleElementArray)
return;
var arrayCreation = (ArrayCreateExpression)argumentList.Arguments[1].Expression;
var arrayCreationRR = (ArrayCreateResolveResult)argumentList.Arguments[1].ResolveResult;
var element = arrayCreation.Initializer.Elements.First().Detach();
argument = new TranslatedExpression(element, arrayCreationRR.InitializerElements.First());
}
if (tokens.Count == 0)
{
return default;
}
foreach (var (kind, index, alignment, text) in tokens)
{
TranslatedExpression argument;
switch (kind)
{
case TokenKind.String:
content.Add(new InterpolatedStringText(text));
break;
case TokenKind.Argument:
argument = arguments[index + 1];
UnpackSingleElementArray(ref argument);
content.Add(new Interpolation(argument));
break;
case TokenKind.ArgumentWithFormat:
argument = arguments[index + 1];
UnpackSingleElementArray(ref argument);
content.Add(new Interpolation(argument, suffix: text));
break;
case TokenKind.ArgumentWithAlignment:
argument = arguments[index + 1];
UnpackSingleElementArray(ref argument);
content.Add(new Interpolation(argument, alignment));
break;
case TokenKind.ArgumentWithAlignmentAndFormat:
argument = arguments[index + 1];
UnpackSingleElementArray(ref argument);
content.Add(new Interpolation(argument, alignment, text));
break;
}
}
var formattableStringType = expressionBuilder.compilation.FindType(KnownTypeCode.FormattableString);
var isrr = new InterpolatedStringResolveResult(expressionBuilder.compilation.FindType(KnownTypeCode.String),
format, argumentList.GetArgumentResolveResults(1).ToArray());
var expr = new InterpolatedStringExpression();
expr.Content.AddRange(content);
if (method.Name == "Format")
return expr.WithRR(isrr);
return new CastExpression(expressionBuilder.ConvertType(formattableStringType),
expr.WithRR(isrr))
.WithRR(new ConversionResolveResult(formattableStringType, isrr, Conversion.ImplicitInterpolatedStringConversion));
}
/// <summary>
/// Converts a call to an Add method to a collection initializer expression.
/// </summary>
@ -843,7 +951,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -843,7 +951,7 @@ namespace ICSharpCode.Decompiler.CSharp
if (parameter.ReferenceKind != ReferenceKind.None)
{
arg = ExpressionBuilder.ChangeDirectionExpressionTo(arg, parameter.ReferenceKind);
arg = ExpressionBuilder.ChangeDirectionExpressionTo(arg, parameter.ReferenceKind, callArguments[i] is AddressOf);
}
arguments.Add(arg);
@ -947,7 +1055,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -947,7 +1055,11 @@ namespace ICSharpCode.Decompiler.CSharp
RequireTarget = 1,
RequireTypeArguments = 2,
NoOptionalArgumentAllowed = 4,
All = 7
/// <summary>
/// Add calls to AsRefReadOnly for in parameters that did not have an explicit DirectionExpression yet.
/// </summary>
EnforceExplicitIn = 8,
All = 0xf,
}
private CallTransformation GetRequiredTransformationsForCall(ExpectedTargetDetails expectedTargetDetails, IMethod method,
@ -1098,6 +1210,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1098,6 +1210,11 @@ namespace ICSharpCode.Decompiler.CSharp
requireTypeArguments = true;
typeArguments = method.TypeArguments.ToArray();
}
else if ((allowedTransforms & CallTransformation.EnforceExplicitIn) != 0)
{
EnforceExplicitIn(argumentList.Arguments, argumentList.ExpectedParameters);
allowedTransforms &= ~CallTransformation.EnforceExplicitIn;
}
else
{
break;
@ -1117,6 +1234,32 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1117,6 +1234,32 @@ namespace ICSharpCode.Decompiler.CSharp
return transform;
}
private void EnforceExplicitIn(TranslatedExpression[] arguments, IParameter[] expectedParameters)
{
for (int i = 0; i < arguments.Length; i++)
{
if (expectedParameters[i].ReferenceKind != ReferenceKind.In)
continue;
if (arguments[i].Expression is DirectionExpression)
continue;
arguments[i] = WrapInAsRefReadOnly(arguments[i]);
expressionBuilder.statementBuilder.EmitAsRefReadOnly = true;
}
}
private TranslatedExpression WrapInAsRefReadOnly(TranslatedExpression arg)
{
return new DirectionExpression(
FieldDirection.In,
new InvocationExpression {
Target = new IdentifierExpression("ILSpyHelper_AsRefReadOnly"),
Arguments = { arg.Expression }
}
).WithRR(new ByReferenceResolveResult(arg.Type, ReferenceKind.In))
.WithoutILInstruction();
}
private bool IsPossibleExtensionMethodCallOnNull(IMethod method, IList<TranslatedExpression> arguments)
{
return method.IsExtensionMethod && arguments.Count > 0 && arguments[0].Expression is NullReferenceExpression;
@ -1155,14 +1298,20 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1155,14 +1298,20 @@ namespace ICSharpCode.Decompiler.CSharp
}
else
{
IParameter parameter = expectedParameters[i];
IType parameterType;
if (expectedParameters[i].Type.Kind == TypeKind.Dynamic)
if (parameter.Type.Kind == TypeKind.Dynamic)
{
parameterType = expressionBuilder.compilation.FindType(KnownTypeCode.Object);
}
else
{
parameterType = expectedParameters[i].Type;
parameterType = parameter.Type;
}
if (parameter.ReferenceKind == ReferenceKind.In && parameterType is ByReferenceType brt && arguments[i].Type is not ByReferenceType)
{
parameterType = brt.ElementType;
}
arguments[i] = arguments[i].ConvertTo(parameterType, expressionBuilder, allowImplicitConversion: false);
@ -1271,7 +1420,10 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1271,7 +1420,10 @@ namespace ICSharpCode.Decompiler.CSharp
resolver.CurrentTypeDefinition == method.DeclaringTypeDefinition;
if (lookup.IsAccessible(ctor, allowProtectedAccess))
{
or.AddCandidate(ctor);
Log.Indent();
OverloadResolutionErrors errors = or.AddCandidate(ctor);
Log.Unindent();
or.LogCandidateAddingResult(" Candidate", ctor, errors);
}
}
}

51
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1799,7 +1799,17 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1799,7 +1799,17 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitUserDefinedCompoundAssign(UserDefinedCompoundAssign inst, TranslationContext context)
{
IType loadType = inst.Method.Parameters[0].Type;
IType loadType;
bool isSpanBasedStringConcat = CallBuilder.IsSpanBasedStringConcat(inst.Method);
if (isSpanBasedStringConcat)
{
loadType = typeSystem.FindType(KnownTypeCode.String);
}
else
{
loadType = inst.Method.Parameters[0].Type;
}
ExpressionWithResolveResult target;
if (inst.TargetKind == CompoundTargetKind.Address)
{
@ -1821,11 +1831,24 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1821,11 +1831,24 @@ namespace ICSharpCode.Decompiler.CSharp
if (UserDefinedCompoundAssign.IsStringConcat(inst.Method))
{
Debug.Assert(inst.Method.Parameters.Count == 2);
var value = Translate(inst.Value).ConvertTo(inst.Method.Parameters[1].Type, this, allowImplicitConversion: true);
var valueExpr = ReplaceMethodCallsWithOperators.RemoveRedundantToStringInConcat(value, inst.Method, isLastArgument: true).Detach();
Expression valueExpr;
ResolveResult valueResolveResult;
if (isSpanBasedStringConcat && inst.Value is NewObj { Arguments: [AddressOf addressOf] })
{
IType charType = typeSystem.FindType(KnownTypeCode.Char);
var value = Translate(addressOf.Value, charType).ConvertTo(charType, this);
valueExpr = value.Expression;
valueResolveResult = value.ResolveResult;
}
else
{
var value = Translate(inst.Value).ConvertTo(inst.Method.Parameters[1].Type, this, allowImplicitConversion: true);
valueExpr = ReplaceMethodCallsWithOperators.RemoveRedundantToStringInConcat(value, inst.Method, isLastArgument: true).Detach();
valueResolveResult = value.ResolveResult;
}
return new AssignmentExpression(target, AssignmentOperatorType.Add, valueExpr)
.WithILInstruction(inst)
.WithRR(new OperatorResolveResult(inst.Method.ReturnType, ExpressionType.AddAssign, inst.Method, inst.IsLifted, new[] { target.ResolveResult, value.ResolveResult }));
.WithRR(new OperatorResolveResult(inst.Method.ReturnType, ExpressionType.AddAssign, inst.Method, inst.IsLifted, new[] { target.ResolveResult, valueResolveResult }));
}
else if (inst.Method.Parameters.Count == 2)
{
@ -2409,7 +2432,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2409,7 +2432,7 @@ namespace ICSharpCode.Decompiler.CSharp
// C# 10 lambdas can have attributes, but anonymous methods cannot
isLambda = true;
}
else if (settings.UseLambdaSyntax && ame.Parameters.All(p => p.ParameterModifier == ParameterModifier.None))
else if (settings.UseLambdaSyntax && ame.Parameters.All(p => p.ParameterModifier == ReferenceKind.None && !p.IsParams))
{
// otherwise use lambda only if an expression lambda is possible
isLambda = (body.Statements.Count == 1 && body.Statements.Single() is ReturnStatement);
@ -4174,7 +4197,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -4174,7 +4197,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
if (info.HasFlag(CSharpArgumentInfoFlags.IsOut))
{
translatedExpression = ChangeDirectionExpressionTo(translatedExpression, ReferenceKind.Out);
translatedExpression = ChangeDirectionExpressionTo(translatedExpression, ReferenceKind.Out, argument is AddressOf);
}
if (info.HasFlag(CSharpArgumentInfoFlags.NamedArgument) && !string.IsNullOrWhiteSpace(info.Name))
{
@ -4184,11 +4207,21 @@ namespace ICSharpCode.Decompiler.CSharp @@ -4184,11 +4207,21 @@ namespace ICSharpCode.Decompiler.CSharp
return translatedExpression;
}
internal static TranslatedExpression ChangeDirectionExpressionTo(TranslatedExpression input, ReferenceKind kind)
internal static TranslatedExpression ChangeDirectionExpressionTo(TranslatedExpression input, ReferenceKind kind, bool isAddressOf)
{
if (!(input.Expression is DirectionExpression dirExpr && input.ResolveResult is ByReferenceResolveResult brrr))
return input;
dirExpr.FieldDirection = (FieldDirection)kind;
if (isAddressOf && kind is ReferenceKind.In or ReferenceKind.RefReadOnly)
{
return input.UnwrapChild(dirExpr.Expression);
}
dirExpr.FieldDirection = kind switch {
ReferenceKind.Ref => FieldDirection.Ref,
ReferenceKind.Out => FieldDirection.Out,
ReferenceKind.In => FieldDirection.In,
ReferenceKind.RefReadOnly => FieldDirection.In,
_ => throw new NotSupportedException("Unsupported reference kind: " + kind)
};
dirExpr.RemoveAnnotations<ByReferenceResolveResult>();
if (brrr.ElementResult == null)
brrr = new ByReferenceResolveResult(brrr.ElementType, kind);
@ -4448,7 +4481,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -4448,7 +4481,7 @@ namespace ICSharpCode.Decompiler.CSharp
var arg = Translate(argInst, typeHint: paramType).ConvertTo(paramType, this, allowImplicitConversion: true);
if (paramRefKind != ReferenceKind.None)
{
arg = ChangeDirectionExpressionTo(arg, paramRefKind);
arg = ChangeDirectionExpressionTo(arg, paramRefKind, argInst is AddressOf);
}
invocation.Arguments.Add(arg);
}

4
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs

@ -135,7 +135,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -135,7 +135,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{
if ((ConversionFlags & ConversionFlags.ShowParameterModifiers) == 0)
{
param.ParameterModifier = ParameterModifier.None;
param.ParameterModifier = ReferenceKind.None;
param.IsScopedRef = false;
param.IsParams = false;
}
if ((ConversionFlags & ConversionFlags.ShowParameterDefaultValues) == 0)
{

20
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -1077,7 +1077,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1077,7 +1077,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
return true;
}
var p = lambdaExpression.Parameters.Single();
return !(p.Type.IsNull && p.ParameterModifier == ParameterModifier.None);
return !(p.Type.IsNull && p.ParameterModifier == ReferenceKind.None && !p.IsParams);
}
public virtual void VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression)
@ -2608,6 +2608,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -2608,6 +2608,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
WriteKeyword(ParameterDeclaration.ThisModifierRole);
Space();
}
if (parameterDeclaration.IsParams)
{
WriteKeyword(ParameterDeclaration.ParamsModifierRole);
Space();
}
if (parameterDeclaration.IsScopedRef)
{
WriteKeyword(ParameterDeclaration.ScopedRefRole);
@ -2615,19 +2620,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -2615,19 +2620,20 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
}
switch (parameterDeclaration.ParameterModifier)
{
case ParameterModifier.Ref:
case ReferenceKind.Ref:
WriteKeyword(ParameterDeclaration.RefModifierRole);
Space();
break;
case ParameterModifier.Out:
WriteKeyword(ParameterDeclaration.OutModifierRole);
case ReferenceKind.RefReadOnly:
WriteKeyword(ParameterDeclaration.RefModifierRole);
WriteKeyword(ParameterDeclaration.ReadonlyModifierRole);
Space();
break;
case ParameterModifier.Params:
WriteKeyword(ParameterDeclaration.ParamsModifierRole);
case ReferenceKind.Out:
WriteKeyword(ParameterDeclaration.OutModifierRole);
Space();
break;
case ParameterModifier.In:
case ReferenceKind.In:
WriteKeyword(ParameterDeclaration.InModifierRole);
Space();
break;

6
ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs

@ -196,7 +196,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -196,7 +196,7 @@ namespace ICSharpCode.Decompiler.CSharp
return false;
if (!target.MatchLdThis())
return false;
if (method.Parameters[i].IsIn)
if (method.Parameters[i].ReferenceKind is ReferenceKind.In or ReferenceKind.RefReadOnly)
{
if (!valueInst.MatchLdObj(out valueInst, out _))
return false;
@ -1012,11 +1012,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1012,11 +1012,11 @@ namespace ICSharpCode.Decompiler.CSharp
var deconstruct = method.Parameters[i];
var ctor = primaryCtor.Parameters[i];
if (!deconstruct.IsOut)
if (deconstruct.ReferenceKind != ReferenceKind.Out)
return false;
IType ctorType = ctor.Type;
if (ctor.IsIn)
if (ctor.ReferenceKind is ReferenceKind.In or ReferenceKind.RefReadOnly)
ctorType = ((ByReferenceType)ctorType).ElementType;
if (!ctorType.Equals(((ByReferenceType)deconstruct.Type).ElementType))
return false;

4
ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs

@ -1128,7 +1128,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -1128,7 +1128,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
foreach (IMethod op in operators)
{
IType sourceType = op.Parameters[0].Type;
if (sourceType.Kind == TypeKind.ByReference && op.Parameters[0].IsIn && fromType.Kind != TypeKind.ByReference)
if (sourceType.Kind == TypeKind.ByReference && op.Parameters[0].ReferenceKind == ReferenceKind.In && fromType.Kind != TypeKind.ByReference)
{
sourceType = ((ByReferenceType)sourceType).ElementType;
}
@ -1235,7 +1235,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -1235,7 +1235,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
// type, as long as no parameter of D has the out parameter modifier.
foreach (IParameter p in d.Parameters)
{
if (p.IsOut)
if (p.ReferenceKind == ReferenceKind.Out)
return Conversion.None;
}
}

4
ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs

@ -671,8 +671,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -671,8 +671,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
{
// AllowImplicitIn: `in` parameters can be filled implicitly without `in` DirectionExpression
// IsExtensionMethodInvocation: `this ref` and `this in` parameters can be filled implicitly
if (((paramRefKind == ReferenceKind.In && AllowImplicitIn)
|| (IsExtensionMethodInvocation && parameterIndex == 0 && (paramRefKind == ReferenceKind.In || paramRefKind == ReferenceKind.Ref))
if (((paramRefKind is ReferenceKind.In or ReferenceKind.RefReadOnly && AllowImplicitIn)
|| (IsExtensionMethodInvocation && parameterIndex == 0 && (paramRefKind is ReferenceKind.In or ReferenceKind.Ref or ReferenceKind.RefReadOnly))
) && candidate.ParameterTypes[parameterIndex].SkipModifiers() is ByReferenceType brt)
{
// Treat the parameter as if it was not declared "in" for the following steps

28
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -47,6 +47,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -47,6 +47,8 @@ namespace ICSharpCode.Decompiler.CSharp
internal IType currentResultType;
internal bool currentIsIterator;
internal bool EmitAsRefReadOnly;
public StatementBuilder(IDecompilerTypeSystem typeSystem, ITypeResolveContext decompilationContext,
ILFunction currentFunction, DecompilerSettings settings, DecompileRun decompileRun,
CancellationToken cancellationToken)
@ -1367,6 +1369,32 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1367,6 +1369,32 @@ namespace ICSharpCode.Decompiler.CSharp
{
var blockStatement = ConvertBlockContainer(new BlockStatement(), container, container.Blocks, isLoop);
DeclareLocalFunctions(currentFunction, container, blockStatement);
if (currentFunction.Body == container)
{
if (EmitAsRefReadOnly)
{
var methodDecl = new MethodDeclaration();
if (settings.StaticLocalFunctions)
{
methodDecl.Modifiers = Modifiers.Static;
}
methodDecl.ReturnType = new ComposedType() { HasReadOnlySpecifier = true, HasRefSpecifier = true, BaseType = new SimpleType("T") };
methodDecl.Name = "ILSpyHelper_AsRefReadOnly";
methodDecl.TypeParameters.Add(new TypeParameterDeclaration("T"));
methodDecl.Parameters.Add(new ParameterDeclaration { ParameterModifier = ReferenceKind.In, Type = new SimpleType("T"), Name = "temp" });
methodDecl.Body = new BlockStatement();
methodDecl.Body.AddChild(new Comment(
"ILSpy generated this function to help ensure overload resolution can pick the overload using 'in'"),
Roles.Comment);
methodDecl.Body.Add(new ReturnStatement(new DirectionExpression(FieldDirection.Ref, new IdentifierExpression("temp"))));
blockStatement.Statements.Add(
new LocalFunctionDeclarationStatement(methodDecl)
);
}
}
return blockStatement;
}

50
ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs

@ -26,32 +26,19 @@ @@ -26,32 +26,19 @@
#nullable enable
using System;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.CSharp.Syntax
{
public enum ParameterModifier
{
None,
Ref,
Out,
Params,
In,
Scoped
}
public class ParameterDeclaration : AstNode
{
public static readonly Role<AttributeSection> AttributeRole = EntityDeclaration.AttributeRole;
public static readonly TokenRole ThisModifierRole = new TokenRole("this");
public static readonly TokenRole ScopedRefRole = new TokenRole("scoped");
[Obsolete("Renamed to ScopedRefRole")]
public static readonly TokenRole RefScopedRole = ScopedRefRole;
public static readonly TokenRole RefModifierRole = new TokenRole("ref");
public static readonly TokenRole ReadonlyModifierRole = ComposedType.ReadonlyRole;
public static readonly TokenRole OutModifierRole = new TokenRole("out");
public static readonly TokenRole InModifierRole = new TokenRole("in");
[Obsolete("C# 11 preview: \"ref scoped\" no longer supported")]
public static readonly TokenRole ValueScopedRole = new TokenRole("scoped");
public static readonly TokenRole ParamsModifierRole = new TokenRole("params");
#region PatternPlaceholder
@ -107,6 +94,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -107,6 +94,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
}
bool hasThisModifier;
bool isParams;
bool isScopedRef;
public CSharpTokenNode ThisKeyword {
@ -127,16 +115,15 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -127,16 +115,15 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
}
}
public bool IsScopedRef {
get { return isScopedRef; }
public bool IsParams {
get { return isParams; }
set {
ThrowIfFrozen();
isScopedRef = value;
isParams = value;
}
}
[Obsolete("Renamed to IsScopedRef")]
public bool IsRefScoped {
public bool IsScopedRef {
get { return isScopedRef; }
set {
ThrowIfFrozen();
@ -144,15 +131,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -144,15 +131,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
}
}
[Obsolete("C# 11 preview: \"ref scoped\" no longer supported")]
public bool IsValueScoped {
get { return false; }
set { }
}
ReferenceKind parameterModifier;
ParameterModifier parameterModifier;
public ParameterModifier ParameterModifier {
public ReferenceKind ParameterModifier {
get { return parameterModifier; }
set {
ThrowIfFrozen();
@ -240,19 +221,6 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -240,19 +221,6 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{
}
public ParameterDeclaration(AstType type, string name, ParameterModifier modifier = ParameterModifier.None)
{
Type = type;
Name = name;
ParameterModifier = modifier;
}
public ParameterDeclaration(string name, ParameterModifier modifier = ParameterModifier.None)
{
Name = name;
ParameterModifier = modifier;
}
public new ParameterDeclaration Clone()
{
return (ParameterDeclaration)base.Clone();

30
ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs

@ -372,14 +372,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -372,14 +372,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
for (int i = 0; i < fpt.ParameterTypes.Length; i++)
{
var paramDecl = new ParameterDeclaration();
paramDecl.ParameterModifier = fpt.ParameterReferenceKinds[i] switch {
ReferenceKind.In => ParameterModifier.In,
ReferenceKind.Ref => ParameterModifier.Ref,
ReferenceKind.Out => ParameterModifier.Out,
_ => ParameterModifier.None,
};
paramDecl.ParameterModifier = fpt.ParameterReferenceKinds[i];
IType parameterType = fpt.ParameterTypes[i];
if (paramDecl.ParameterModifier != ParameterModifier.None && parameterType is ByReferenceType brt)
if (paramDecl.ParameterModifier != ReferenceKind.None && parameterType is ByReferenceType brt)
{
paramDecl.Type = ConvertType(brt.ElementType);
}
@ -1649,22 +1644,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1649,22 +1644,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
if (parameter == null)
throw new ArgumentNullException(nameof(parameter));
ParameterDeclaration decl = new ParameterDeclaration();
if (parameter.IsRef)
{
decl.ParameterModifier = ParameterModifier.Ref;
}
else if (parameter.IsOut)
{
decl.ParameterModifier = ParameterModifier.Out;
}
else if (parameter.IsIn)
{
decl.ParameterModifier = ParameterModifier.In;
}
else if (parameter.IsParams)
{
decl.ParameterModifier = ParameterModifier.Params;
}
decl.ParameterModifier = parameter.ReferenceKind;
decl.IsParams = parameter.IsParams;
decl.IsScopedRef = parameter.Lifetime.ScopedRef;
if (ShowAttributes)
{
@ -1685,7 +1666,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1685,7 +1666,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{
decl.Name = parameter.Name;
}
if (parameter.IsOptional && decl.ParameterModifier is ParameterModifier.None or ParameterModifier.In && parameter.HasConstantValueInSignature && this.ShowConstantValues)
if (parameter.IsOptional && decl.ParameterModifier is ReferenceKind.None or ReferenceKind.In or ReferenceKind.RefReadOnly
&& parameter.HasConstantValueInSignature && this.ShowConstantValues)
{
try
{

62
ICSharpCode.Decompiler/CSharp/Transforms/DecimalConstantTransform.cs

@ -1,62 +0,0 @@ @@ -1,62 +0,0 @@
// Copyright (c) 2011 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 ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.CSharp.Transforms
{
/// <summary>
/// Transforms decimal constant fields.
/// </summary>
public class DecimalConstantTransform : DepthFirstAstVisitor, IAstTransform
{
static readonly PrimitiveType decimalType = new PrimitiveType("decimal");
public override void VisitFieldDeclaration(FieldDeclaration fieldDeclaration)
{
const Modifiers staticReadOnly = Modifiers.Static | Modifiers.Readonly;
if ((fieldDeclaration.Modifiers & staticReadOnly) == staticReadOnly && decimalType.IsMatch(fieldDeclaration.ReturnType))
{
foreach (var attributeSection in fieldDeclaration.Attributes)
{
foreach (var attribute in attributeSection.Attributes)
{
var t = attribute.Type.GetSymbol() as IType;
if (t != null && t.Name == "DecimalConstantAttribute" && t.Namespace == "System.Runtime.CompilerServices")
{
attribute.Remove();
if (attributeSection.Attributes.Count == 0)
attributeSection.Remove();
fieldDeclaration.Modifiers = (fieldDeclaration.Modifiers & ~staticReadOnly) | Modifiers.Const;
return;
}
}
}
}
}
public void Run(AstNode rootNode, TransformContext context)
{
if (!context.Settings.DecimalConstants)
return;
rootNode.AcceptVisitor(this);
}
}
}

6
ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs

@ -205,7 +205,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -205,7 +205,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{
foreach (var stmt in rootNode.DescendantsAndSelf.OfType<ExpressionStatement>())
{
if (!IsValidInStatementExpression(stmt.Expression))
if (stmt.Expression is DirectionExpression dir && IsValidInStatementExpression(dir.Expression))
{
stmt.Expression = dir.Expression.Detach();
}
else if (!IsValidInStatementExpression(stmt.Expression))
{
// fetch ILFunction
var function = stmt.Ancestors.SelectMany(a => a.Annotations.OfType<ILFunction>()).First(f => f.Parent == null);

1
ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs

@ -168,6 +168,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -168,6 +168,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
"System.Runtime.CompilerServices.NativeIntegerAttribute",
"System.Runtime.CompilerServices.RefSafetyRulesAttribute",
"System.Runtime.CompilerServices.ScopedRefAttribute",
"System.Runtime.CompilerServices.RequiresLocationAttribute",
"Microsoft.CodeAnalysis.EmbeddedAttribute",
};

2
ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs

@ -389,7 +389,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -389,7 +389,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
private static bool ValidateParameter(ParameterDeclaration p)
{
return p.ParameterModifier == ParameterModifier.None && p.Attributes.Count == 0;
return p.ParameterModifier == Decompiler.TypeSystem.ReferenceKind.None && p.Attributes.Count == 0;
}
}
}

11
ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs

@ -1167,6 +1167,17 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -1167,6 +1167,17 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
}
return base.VisitBinaryOperatorExpression(expr);
}
public override AstNode VisitUnaryOperatorExpression(UnaryOperatorExpression expr)
{
if (expr.Operator == UnaryOperatorType.Not && expr.Expression is BinaryOperatorExpression { Operator: BinaryOperatorType.Equality } binary)
{
binary.Operator = BinaryOperatorType.InEquality;
expr.ReplaceWith(binary.Detach());
return VisitBinaryOperatorExpression(binary);
}
return base.VisitUnaryOperatorExpression(expr);
}
#endregion
#region C# 7.3 pattern based fixed (for value types)

8
ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs

@ -274,7 +274,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -274,7 +274,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
}
foreach (var arg in arguments)
{
if (arg.GetResolveResult() is InvocationResolveResult rr && IsStringConcat(rr.Member))
var rr = arg.GetResolveResult();
if (rr is InvocationResolveResult irr && IsStringConcat(irr.Member))
{
// Roslyn + mcs also flatten nested string.Concat() invocations within a operator+ use,
// which causes it to use the incorrect evaluation order despite the code using an
@ -282,6 +283,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -282,6 +283,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
// This problem is avoided if the outer call remains string.Concat() as well.
return false;
}
if (rr.Type.IsByRefLike)
{
// ref structs cannot be converted to object for use with +
return false;
}
}
// One of the first two arguments must be string, otherwise the + operator

47
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -152,7 +152,6 @@ namespace ICSharpCode.Decompiler @@ -152,7 +152,6 @@ namespace ICSharpCode.Decompiler
}
if (languageVersion < CSharp.LanguageVersion.CSharp11_0)
{
parameterNullCheck = false;
scopedRef = false;
requiredMembers = false;
numericIntPtr = false;
@ -160,11 +159,17 @@ namespace ICSharpCode.Decompiler @@ -160,11 +159,17 @@ namespace ICSharpCode.Decompiler
unsignedRightShift = false;
checkedOperators = false;
}
if (languageVersion < CSharp.LanguageVersion.CSharp12_0)
{
refReadOnlyParameters = false;
}
}
public CSharp.LanguageVersion GetMinimumRequiredVersion()
{
if (parameterNullCheck || scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators)
if (refReadOnlyParameters)
return CSharp.LanguageVersion.CSharp12_0;
if (scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators)
return CSharp.LanguageVersion.CSharp11_0;
if (fileScopedNamespaces || recordStructs)
return CSharp.LanguageVersion.CSharp10_0;
@ -443,26 +448,6 @@ namespace ICSharpCode.Decompiler @@ -443,26 +448,6 @@ namespace ICSharpCode.Decompiler
}
}
bool parameterNullCheck = false;
/// <summary>
/// Use C# 11 preview parameter null-checking (<code>string param!!</code>).
/// </summary>
[Category("C# 11.0 / VS 2022.4")]
[Description("DecompilerSettings.ParameterNullCheck")]
[Browsable(false)]
[Obsolete("This feature did not make it into C# 11, and may be removed in a future version of the decompiler.")]
public bool ParameterNullCheck {
get { return parameterNullCheck; }
set {
if (parameterNullCheck != value)
{
parameterNullCheck = value;
OnPropertyChanged();
}
}
}
bool anonymousMethods = true;
/// <summary>
@ -2012,6 +1997,24 @@ namespace ICSharpCode.Decompiler @@ -2012,6 +1997,24 @@ namespace ICSharpCode.Decompiler
}
}
bool refReadOnlyParameters = true;
/// <summary>
/// Gets/sets whether RequiresLocationAttribute on parameters should be replaced with 'ref readonly' modifiers.
/// </summary>
[Category("C# 12.0 / VS 2022.8")]
[Description("DecompilerSettings.RefReadOnlyParameters")]
public bool RefReadOnlyParameters {
get { return refReadOnlyParameters; }
set {
if (refReadOnlyParameters != value)
{
refReadOnlyParameters = value;
OnPropertyChanged();
}
}
}
bool separateLocalVariableDeclarations = false;
/// <summary>

4
ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs

@ -262,7 +262,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -262,7 +262,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
bool hasOutArgs = false;
foreach (var arg in call.Arguments)
{
if (arg.MatchLdLoca(out var v) && call.GetParameter(arg.ChildIndex)?.IsOut == true)
if (arg.MatchLdLoca(out var v) && call.GetParameter(arg.ChildIndex)?.ReferenceKind == ReferenceKind.Out)
{
// Visiting ldloca would require the variable to be initialized,
// but we don't need out arguments to be initialized.
@ -278,7 +278,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -278,7 +278,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
{
foreach (var arg in call.Arguments)
{
if (arg.MatchLdLoca(out var v) && call.GetParameter(arg.ChildIndex)?.IsOut == true)
if (arg.MatchLdLoca(out var v) && call.GetParameter(arg.ChildIndex)?.ReferenceKind == ReferenceKind.Out)
{
HandleStore(v);
}

3
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -97,7 +97,6 @@ @@ -97,7 +97,6 @@
<Compile Include="DecompilationProgress.cs" />
<Compile Include="Disassembler\IEntityProcessor.cs" />
<Compile Include="Disassembler\SortByNameProcessor.cs" />
<Compile Include="IL\ApplyPdbLocalTypeInfoTypeVisitor.cs" />
<Compile Include="Metadata\MetadataFile.cs" />
<Compile Include="Metadata\ModuleReferenceMetadata.cs" />
<Compile Include="NRTAttributes.cs" />
@ -135,7 +134,6 @@ @@ -135,7 +134,6 @@
<Compile Include="IL\Transforms\InterpolatedStringTransform.cs" />
<Compile Include="IL\Transforms\IntroduceNativeIntTypeOnLocals.cs" />
<Compile Include="IL\Transforms\LdLocaDupInitObjTransform.cs" />
<Compile Include="IL\Transforms\ParameterNullCheckTransform.cs" />
<Compile Include="IL\Transforms\PatternMatchingTransform.cs" />
<Compile Include="IL\Transforms\RemoveInfeasiblePathTransform.cs" />
<Compile Include="Instrumentation\DecompilerEventSource.cs" />
@ -336,7 +334,6 @@ @@ -336,7 +334,6 @@
<Compile Include="DecompilerException.cs" />
<Compile Include="DecompilerSettings.cs" />
<Compile Include="CSharp\Transforms\ContextTrackingVisitor.cs" />
<Compile Include="CSharp\Transforms\DecimalConstantTransform.cs" />
<Compile Include="CSharp\Transforms\DeclareVariables.cs" />
<Compile Include="CSharp\Transforms\EscapeInvalidIdentifiers.cs" />
<Compile Include="CSharp\Transforms\FixNameCollisions.cs" />

159
ICSharpCode.Decompiler/IL/ApplyPdbLocalTypeInfoTypeVisitor.cs

@ -1,159 +0,0 @@ @@ -1,159 +0,0 @@
using System;
using System.Collections.Immutable;
using ICSharpCode.Decompiler.DebugInfo;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
namespace ICSharpCode.Decompiler.IL
{
/// <summary>
/// Heavily based on <see cref="ApplyAttributeTypeVisitor"/>
/// </summary>
sealed class ApplyPdbLocalTypeInfoTypeVisitor : TypeVisitor
{
private readonly bool[] dynamicData;
private readonly string[] tupleElementNames;
private int dynamicTypeIndex = 0;
private int tupleTypeIndex = 0;
private ApplyPdbLocalTypeInfoTypeVisitor(bool[] dynamicData, string[] tupleElementNames)
{
this.dynamicData = dynamicData;
this.tupleElementNames = tupleElementNames;
}
public static IType Apply(IType type, PdbExtraTypeInfo pdbExtraTypeInfo)
{
if (pdbExtraTypeInfo.DynamicFlags is null && pdbExtraTypeInfo.TupleElementNames is null)
return type;
return type.AcceptVisitor(new ApplyPdbLocalTypeInfoTypeVisitor(pdbExtraTypeInfo.DynamicFlags, pdbExtraTypeInfo.TupleElementNames));
}
public override IType VisitModOpt(ModifiedType type)
{
dynamicTypeIndex++;
return base.VisitModOpt(type);
}
public override IType VisitModReq(ModifiedType type)
{
dynamicTypeIndex++;
return base.VisitModReq(type);
}
public override IType VisitPointerType(PointerType type)
{
dynamicTypeIndex++;
return base.VisitPointerType(type);
}
public override IType VisitArrayType(ArrayType type)
{
dynamicTypeIndex++;
return base.VisitArrayType(type);
}
public override IType VisitByReferenceType(ByReferenceType type)
{
dynamicTypeIndex++;
return base.VisitByReferenceType(type);
}
public override IType VisitTupleType(TupleType type)
{
if (tupleElementNames != null && tupleTypeIndex < tupleElementNames.Length)
{
int tupleCardinality = type.Cardinality;
string[] extractedValues = new string[tupleCardinality];
Array.Copy(tupleElementNames, tupleTypeIndex, extractedValues, 0,
Math.Min(tupleCardinality, tupleElementNames.Length - tupleTypeIndex));
var elementNames = ImmutableArray.CreateRange(extractedValues);
tupleTypeIndex += tupleCardinality;
int level = 0;
var elementTypes = new IType[type.ElementTypes.Length];
for (int i = 0; i < type.ElementTypes.Length; i++)
{
dynamicTypeIndex++;
IType elementType = type.ElementTypes[i];
if (i != 0 && (i - level) % TupleType.RestPosition == 0 && elementType is TupleType tuple)
{
tupleTypeIndex += tuple.Cardinality;
level++;
}
elementTypes[i] = elementType.AcceptVisitor(this);
}
return new TupleType(
type.Compilation,
elementTypes.ToImmutableArray(),
elementNames,
type.GetDefinition()?.ParentModule
);
}
return base.VisitTupleType(type);
}
public override IType VisitParameterizedType(ParameterizedType type)
{
if (TupleType.IsTupleCompatible(type, out var tupleCardinality))
tupleTypeIndex += tupleCardinality;
// Visit generic type and type arguments.
// Like base implementation, except that it increments dynamicTypeIndex.
var genericType = type.GenericType.AcceptVisitor(this);
bool changed = type.GenericType != genericType;
var arguments = new IType[type.TypeArguments.Count];
for (int i = 0; i < type.TypeArguments.Count; i++)
{
dynamicTypeIndex++;
arguments[i] = type.TypeArguments[i].AcceptVisitor(this);
changed = changed || arguments[i] != type.TypeArguments[i];
}
if (!changed)
return type;
return new ParameterizedType(genericType, arguments);
}
public override IType VisitFunctionPointerType(FunctionPointerType type)
{
dynamicTypeIndex++;
if (type.ReturnIsRefReadOnly)
{
dynamicTypeIndex++;
}
var returnType = type.ReturnType.AcceptVisitor(this);
bool changed = type.ReturnType != returnType;
var parameters = new IType[type.ParameterTypes.Length];
for (int i = 0; i < parameters.Length; i++)
{
dynamicTypeIndex += type.ParameterReferenceKinds[i] switch {
ReferenceKind.None => 1,
ReferenceKind.Ref => 1,
ReferenceKind.Out => 2, // in/out also count the modreq
ReferenceKind.In => 2,
_ => throw new NotSupportedException()
};
parameters[i] = type.ParameterTypes[i].AcceptVisitor(this);
changed = changed || parameters[i] != type.ParameterTypes[i];
}
if (!changed)
return type;
return type.WithSignature(returnType, parameters.ToImmutableArray());
}
public override IType VisitTypeDefinition(ITypeDefinition type)
{
IType newType = type;
var ktc = type.KnownTypeCode;
if (ktc == KnownTypeCode.Object && dynamicData is not null)
{
if (dynamicTypeIndex >= dynamicData.Length)
newType = SpecialType.Dynamic;
else if (dynamicData[dynamicTypeIndex])
newType = SpecialType.Dynamic;
}
return newType;
}
}
}

4
ICSharpCode.Decompiler/IL/ILReader.cs

@ -285,7 +285,7 @@ namespace ICSharpCode.Decompiler.IL @@ -285,7 +285,7 @@ namespace ICSharpCode.Decompiler.IL
{
IParameter parameter = method.Parameters[paramIndex - offset];
ILVariable ilVar = CreateILVariable(paramIndex - offset, parameter.Type, parameter.Name);
ilVar.IsRefReadOnly = parameter.IsIn;
ilVar.IsRefReadOnly = parameter.ReferenceKind is ReferenceKind.In or ReferenceKind.RefReadOnly;
parameterVariables[paramIndex] = ilVar;
paramIndex++;
}
@ -308,7 +308,7 @@ namespace ICSharpCode.Decompiler.IL @@ -308,7 +308,7 @@ namespace ICSharpCode.Decompiler.IL
if (UseDebugSymbols && DebugInfo is not null &&
DebugInfo.TryGetExtraTypeInfo((MethodDefinitionHandle)method.MetadataToken, index, out var pdbExtraTypeInfo))
{
type = ApplyPdbLocalTypeInfoTypeVisitor.Apply(type, pdbExtraTypeInfo);
type = ApplyAttributeTypeVisitor.ApplyAttributesToType(type, compilation, module.TypeSystemOptions, pdbExtraTypeInfo);
}
ILVariable ilVar = new ILVariable(kind, type, index);

2
ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs

@ -267,7 +267,7 @@ namespace ICSharpCode.Decompiler.IL @@ -267,7 +267,7 @@ namespace ICSharpCode.Decompiler.IL
for (int i = firstOutParam; i < method.Parameters.Count; i++)
{
if (!method.Parameters[i].IsOut)
if (method.Parameters[i].ReferenceKind != ReferenceKind.Out)
return false;
}

7
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -96,8 +96,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -96,8 +96,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return;
}
else if (inst.Kind == ComparisonKind.Inequality && inst.LiftingKind == ComparisonLiftingKind.None
&& inst.Right.MatchLdcI4(0) && (IfInstruction.IsInConditionSlot(inst) || inst.Left is Comp)
)
&& inst.Right.MatchLdcI4(0) && (IfInstruction.IsInConditionSlot(inst) || inst.Left is Comp))
{
// if (comp(x != 0)) ==> if (x)
// comp(comp(...) != 0) => comp(...)
@ -107,6 +106,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -107,6 +106,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
inst.Left.AcceptVisitor(this);
return;
}
if (context.Settings.LiftNullables)
{
new NullableLiftingTransform(context).Run(inst);
}
base.VisitComp(inst);
if (inst.IsLifted)

68
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -244,7 +244,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -244,7 +244,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var loadInst = r.LoadInst;
if (loadInst.OpCode == OpCode.LdLoca)
{
if (!IsGeneratedValueTypeTemporary((LdLoca)loadInst, v, inlinedExpression, options))
if (!IsGeneratedTemporaryForAddressOf((LdLoca)loadInst, v, inlinedExpression, options))
return false;
}
else
@ -289,7 +289,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -289,7 +289,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// </summary>
/// <param name="loadInst">The load instruction (a descendant within 'next')</param>
/// <param name="v">The variable being inlined.</param>
static bool IsGeneratedValueTypeTemporary(LdLoca loadInst, ILVariable v, ILInstruction inlinedExpression, InliningOptions options)
static bool IsGeneratedTemporaryForAddressOf(LdLoca loadInst, ILVariable v, ILInstruction inlinedExpression, InliningOptions options)
{
Debug.Assert(loadInst.Variable == v);
if (!options.HasFlag(InliningOptions.AllowInliningOfLdloca))
@ -326,6 +326,39 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -326,6 +326,39 @@ namespace ICSharpCode.Decompiler.IL.Transforms
throw new InvalidOperationException("invalid expression classification");
}
}
else if (IsPassedToReadOnlySpanOfCharCtor(loadInst))
{
// Always inlining is possible here, because it's an 'in' or 'ref readonly' parameter
// and the C# compiler allows calling it with an rvalue, even though that might produce
// a warning. Note that we don't need to check the expression classification, because
// expressionBuilder.VisitAddressOf will handle creating the copy for us.
// This is necessary, because there are compiler-generated uses of this ctor when
// concatenating a string to a char and our following transforms assume the char is
// already inlined.
return true;
}
else if (IsPassedToInParameter(loadInst))
{
if (options.HasFlag(InliningOptions.Aggressive))
{
// Inlining might be required in ctor initializers (see #2714).
// expressionBuilder.VisitAddressOf will handle creating the copy for us.
return true;
}
switch (ClassifyExpression(inlinedExpression))
{
case ExpressionClassification.RValue:
// For rvalues passed to in parameters, the C# compiler generates a temporary.
return true;
case ExpressionClassification.MutableLValue:
case ExpressionClassification.ReadonlyLValue:
// For lvalues passed to in parameters, the C# compiler never generates temporaries.
return false;
default:
throw new InvalidOperationException("invalid expression classification");
}
}
else if (IsUsedAsThisPointerInFieldRead(loadInst))
{
// mcs generated temporaries for field reads on rvalues (#1555)
@ -415,6 +448,34 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -415,6 +448,34 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return inst != ldloca && inst.Parent is LdObj;
}
static bool IsPassedToInParameter(LdLoca ldloca)
{
if (ldloca.Parent is not CallInstruction call)
{
return false;
}
return call.GetParameter(ldloca.ChildIndex)?.ReferenceKind is ReferenceKind.In;
}
static bool IsPassedToReadOnlySpanOfCharCtor(LdLoca ldloca)
{
if (ldloca.Parent is not NewObj call)
{
return false;
}
return IsReadOnlySpanCharCtor(call.Method);
}
internal static bool IsReadOnlySpanCharCtor(IMethod method)
{
return method.IsConstructor
&& method.Parameters.Count == 1
&& method.DeclaringType.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)
&& method.DeclaringType.TypeArguments[0].IsKnownType(KnownTypeCode.Char)
&& method.Parameters[0].Type is ByReferenceType brt
&& brt.ElementType.IsKnownType(KnownTypeCode.Char);
}
/// <summary>
/// Gets whether the instruction, when converted into C#, turns into an l-value that can
/// be used to mutate a value-type.
@ -476,6 +537,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -476,6 +537,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
/// <summary>
/// Gets whether the ILInstruction will turn into a C# expresion that is considered readonly by the C# compiler.
/// </summary>
internal static bool IsReadonlyReference(ILInstruction addr)
{
switch (addr)

10
ICSharpCode.Decompiler/IL/Transforms/IntroduceRefReadOnlyModifierOnLocals.cs

@ -52,8 +52,18 @@ namespace ICSharpCode.Decompiler.IL @@ -52,8 +52,18 @@ namespace ICSharpCode.Decompiler.IL
{
foreach (var store in variable.StoreInstructions.OfType<StLoc>())
{
// Check if C# requires that the local is ref-readonly in order to allow the store:
if (ILInlining.IsReadonlyReference(store.Value))
return true;
// Check whether the local needs to be ref-readonly to avoid changing the semantics of
// a readonly.ldelema:
ILInstruction val = store.Value;
while (val is LdFlda ldflda)
{
val = ldflda.Target;
}
if (val is LdElema { IsReadOnly: true })
return true;
}
return false;
}

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

@ -556,9 +556,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -556,9 +556,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
internal static bool IsClosureParameter(IParameter parameter, ITypeResolveContext context)
{
if (!parameter.IsRef)
if (parameter.Type is not ByReferenceType brt)
return false;
var type = ((ByReferenceType)parameter.Type).ElementType.GetDefinition();
var type = brt.ElementType.GetDefinition();
return type != null
&& type.Kind == TypeKind.Struct
&& TransformDisplayClassUsage.IsPotentialClosure(context.CurrentTypeDefinition, type);

58
ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs

@ -85,6 +85,33 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -85,6 +85,33 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
}
/// <summary>
/// VS2022.10 / Roslyn 4.10.0 adds an optimization that turns
/// a == 42 into a.GetValueOrDefault() == 42 without any HasValue check.
/// </summary>
public void Run(Comp comp)
{
if (!comp.IsLifted && comp.Kind.IsEqualityOrInequality())
{
var left = comp.Left;
var right = comp.Right;
if (MatchGetValueOrDefault(left, out ILInstruction arg)
&& right.MatchLdcI(out var value) && value != 0)
{
context.Step("comp(a.GetValueOrDefault() == const) -> comp.lifted(a == const)", comp);
comp.LiftingKind = ComparisonLiftingKind.CSharp;
comp.Left = new LdObj(arg, ((Call)left).Method.DeclaringType);
}
else if (MatchGetValueOrDefault(right, out arg)
&& left.MatchLdcI(out value) && value != 0)
{
context.Step("comp(const == a.GetValueOrDefault()) -> comp.lifted(const == a)", comp);
comp.LiftingKind = ComparisonLiftingKind.CSharp;
comp.Right = new LdObj(arg, ((Call)right).Method.DeclaringType);
}
}
}
public bool RunStatements(Block block, int pos)
{
// e.g.:
@ -198,14 +225,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -198,14 +225,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return LiftCSharpEqualityComparison(comp, ComparisonKind.Inequality, trueInst)
?? LiftCSharpUserEqualityComparison(comp, ComparisonKind.Inequality, trueInst);
}
else if (IsGenericNewPattern(comp.Left, comp.Right, trueInst, falseInst))
else if (!comp.IsLifted && IsGenericNewPattern(comp.Left, comp.Right, trueInst, falseInst))
{
// (default(T) == null) ? Activator.CreateInstance<T>() : default(T)
// => Activator.CreateInstance<T>()
return trueInst;
}
}
else
else if (!comp.IsLifted)
{
// Not (in)equality, but one of < <= > >=.
// Returns false unless all HasValue bits are true.
@ -374,11 +401,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -374,11 +401,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
result = default(CompOrDecimal);
result.Instruction = inst;
if (inst is Comp comp && !comp.IsLifted)
if (inst is Comp comp)
{
result.Kind = comp.Kind;
result.Left = comp.Left;
result.Right = comp.Right;
result.IsLifted = comp.IsLifted;
return true;
}
else if (inst is Call call && call.Method.IsOperator && call.Arguments.Count == 2 && !call.IsLifted)
@ -422,6 +450,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -422,6 +450,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public ComparisonKind Kind;
public ILInstruction Left;
public ILInstruction Right;
public bool IsLifted;
public IType LeftExpectedType {
get {
@ -501,6 +530,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -501,6 +530,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// The HasValue comparison must be the same operator as the Value comparison.
if (hasValueTest is Comp hasValueComp)
{
if (valueComp.IsLifted || hasValueComp.IsLifted)
return null;
// Comparing two nullables: HasValue comparison must be the same operator as the Value comparison
if ((hasValueTestNegated ? hasValueComp.Kind.Negate() : hasValueComp.Kind) != newComparisonKind)
return null;
@ -549,6 +580,24 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -549,6 +580,24 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// </summary>
ILInstruction LiftCSharpComparison(CompOrDecimal comp, ComparisonKind newComparisonKind)
{
if (comp.IsLifted)
{
// Happens when legacy csc generates 'num.GetValueOrDefault() == const && num.HasValue',
// checking HasValue is redundant here, modern Roslyn versions optimize it and our
// NullableLifting transform on Comp will already lift the lhs of the logic.and.
// Treat this case as if the transform had not undone the optimization yet.
if (nullableVars.Count != 1)
{
return null;
}
if (comp.Left.MatchLdLoc(nullableVars[0]) || comp.Right.MatchLdLoc(nullableVars[0]))
{
return comp.MakeLifted(newComparisonKind, comp.Left.Clone(), comp.Right.Clone());
}
return null;
}
var (left, right, bits) = DoLiftBinary(comp.Left, comp.Right, comp.LeftExpectedType, comp.RightExpectedType);
// due to the restrictions on side effects, we only allow instructions that are pure after lifting.
// (we can't check this before lifting due to the calls to GetValueOrDefault())
@ -584,7 +633,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -584,7 +633,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// call op_Inequality(call GetValueOrDefault(ldloca nullable1), call GetValueOrDefault(ldloca nullable2))
// else
// ldc.i4 0
if (hasValueComp.IsLifted)
return null;
if (!MatchHasValueCall(hasValueComp.Left, out ILVariable nullable1))
return null;
if (!MatchHasValueCall(hasValueComp.Right, out ILVariable nullable2))

90
ICSharpCode.Decompiler/IL/Transforms/ParameterNullCheckTransform.cs

@ -1,90 +0,0 @@ @@ -1,90 +0,0 @@
// Copyright (c) 2017 Siegfried Pammer
//
// 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.
#nullable enable
using System.Diagnostics.CodeAnalysis;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL.Transforms
{
/// <summary>
/// Implements transforming &lt;PrivateImplementationDetails&gt;.ThrowIfNull(name, "name");
/// </summary>
class ParameterNullCheckTransform : IILTransform
{
void IILTransform.Run(ILFunction function, ILTransformContext context)
{
#pragma warning disable 618 // ParameterNullCheck is obsolete
if (!context.Settings.ParameterNullCheck)
return;
#pragma warning restore 618
// we only need to look at the entry-point as parameter null-checks
// do not produce any IL control-flow instructions
Block entryPoint = ((BlockContainer)function.Body).EntryPoint;
int index = 0;
// Early versions of this pattern produced call ThrowIfNull instructions after
// state-machine initialization instead of right at the start of the method.
// In order to support both patterns, we scan all instructions,
// if the current function is decorated with a state-machine attribute.
bool scanFullBlock = function.Method != null
&& (function.Method.HasAttribute(KnownAttribute.IteratorStateMachine)
|| function.Method.HasAttribute(KnownAttribute.AsyncIteratorStateMachine)
|| function.Method.HasAttribute(KnownAttribute.AsyncStateMachine));
// loop over all instructions
while (index < entryPoint.Instructions.Count)
{
// The pattern does not match for the current instruction
if (!MatchThrowIfNullCall(entryPoint.Instructions[index], out ILVariable? parameterVariable))
{
if (scanFullBlock)
{
// continue scanning
index++;
continue;
}
else
{
// abort
break;
}
}
// remove the call to ThrowIfNull
entryPoint.Instructions.RemoveAt(index);
// remember to generate !! when producing the final output.
parameterVariable.HasNullCheck = true;
}
}
// call <PrivateImplementationDetails>.ThrowIfNull(ldloc parameterVariable, ldstr "parameterVariable")
private bool MatchThrowIfNullCall(ILInstruction instruction, [NotNullWhen(true)] out ILVariable? parameterVariable)
{
parameterVariable = null;
if (instruction is not Call call)
return false;
if (call.Arguments.Count != 2)
return false;
if (!call.Method.IsStatic || !call.Method.FullNameIs("<PrivateImplementationDetails>", "ThrowIfNull"))
return false;
if (!(call.Arguments[0].MatchLdLoc(out parameterVariable) && parameterVariable.Kind == VariableKind.Parameter))
return false;
return true;
}
}
}

114
ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs

@ -243,7 +243,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -243,7 +243,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
while (blob.RemainingBytes > 0)
{
block.Instructions.Add(StElemPtr(tempStore, blob.Offset, new LdcI4(blob.ReadByte()), elementType));
block.Instructions.Add(StElemPtr(tempStore, blob.Offset, ReadElement(ref blob, elementType), elementType));
}
block.FinalInstruction = new LdLoc(tempStore);
@ -271,13 +271,30 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -271,13 +271,30 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
}
private ILInstruction ReadElement(ref BlobReader blob, IType elementType)
{
switch (elementType.GetSize())
{
case 1:
return new LdcI4(blob.ReadByte());
case 2:
return new LdcI4(blob.ReadInt16());
case 4:
return new LdcI4(blob.ReadInt32());
case 8:
return new LdcI8(blob.ReadInt64());
default:
throw new NotSupportedException();
}
}
bool HandleCpblkInitializer(Block block, int pos, ILVariable v, long length, out BlobReader blob, out IType elementType)
{
blob = default;
elementType = null;
if (!block.Instructions[pos].MatchCpblk(out var dest, out var src, out var size))
return false;
if (!dest.MatchLdLoc(v) || !src.MatchLdsFlda(out var field) || !size.MatchLdcI4((int)length))
if (!dest.MatchLdLoc(v) || !MatchGetStaticFieldAddress(src, out var field) || !size.MatchLdcI4((int)length))
return false;
if (!(v.IsSingleDefinition && v.LoadCount == 2))
return false;
@ -303,9 +320,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -303,9 +320,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
else if (value is NewObj { Arguments: { Count: 2 } } newObj
&& newObj.Method.DeclaringType.IsKnownType(KnownTypeCode.SpanOfT)
&& newObj.Arguments[0].MatchLdLoc(v)
&& newObj.Arguments[1].MatchLdcI4((int)length))
&& newObj.Arguments[1].MatchLdcI4(out var elementCount))
{
elementType = ((ParameterizedType)newObj.Method.DeclaringType).TypeArguments[0];
if (elementCount != length / elementType.GetSize())
return false;
}
else
{
@ -315,7 +334,32 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -315,7 +334,32 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
bool HandleSequentialLocAllocInitializer(Block block, int pos, ILVariable store, ILInstruction locAllocInstruction, out IType elementType, out StObj[] values, out int instructionsToRemove)
bool MatchGetStaticFieldAddress(ILInstruction input, out IField field)
{
if (input.MatchLdsFlda(out field))
return true;
// call get_Item(addressof System.ReadOnlySpan`1[[T]](call CreateSpan(ldmembertoken field)), ldc.i4 0)
if (input is not Call { Method.Name: "get_Item", Arguments.Count: 2 } call)
return false;
if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.ReadOnlySpanOfT))
return false;
if (!call.Arguments[1].MatchLdcI4(0))
return false;
if (call.Arguments[0] is not AddressOf addressOf)
return false;
if (addressOf.Value is not Call { Method.Name: "CreateSpan", Arguments.Count: 1 } createSpanCall)
return false;
if (!IsRuntimeHelpers(createSpanCall.Method.DeclaringType))
return false;
if (!createSpanCall.Arguments[0].MatchLdMemberToken(out var member))
return false;
field = member as IField;
return field != null;
}
static bool IsRuntimeHelpers(IType type) => type is { Name: "RuntimeHelpers", Namespace: "System.Runtime.CompilerServices" };
unsafe bool HandleSequentialLocAllocInitializer(Block block, int pos, ILVariable store, ILInstruction locAllocInstruction, out IType elementType, out StObj[] values, out int instructionsToRemove)
{
int elementCount = 0;
long minExpectedOffset = 0;
@ -326,24 +370,60 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -326,24 +370,60 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!locAllocInstruction.MatchLocAlloc(out var lengthInstruction))
return false;
if (block.Instructions[pos].MatchInitblk(out var dest, out var value, out var size)
&& lengthInstruction.MatchLdcI(out long byteCount))
BlobReader blob = default;
if (lengthInstruction.MatchLdcI(out long byteCount))
{
if (!dest.MatchLdLoc(store) || !size.MatchLdcI(byteCount))
return false;
instructionsToRemove++;
pos++;
if (block.Instructions[pos].MatchInitblk(out var dest, out var value, out var size))
{
if (!dest.MatchLdLoc(store) || !size.MatchLdcI(byteCount))
return false;
instructionsToRemove++;
pos++;
}
else if (block.Instructions[pos].MatchCpblk(out dest, out var src, out size))
{
if (!dest.MatchLdLoc(store) || !size.MatchLdcI(byteCount))
return false;
if (!MatchGetStaticFieldAddress(src, out var field))
return false;
var fd = context.PEFile.Metadata.GetFieldDefinition((FieldDefinitionHandle)field.MetadataToken);
if (!fd.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
return false;
blob = fd.GetInitialValue(context.PEFile, context.TypeSystem);
instructionsToRemove++;
pos++;
}
}
for (int i = pos; i < block.Instructions.Count; i++)
{
// match the basic stobj pattern
if (!block.Instructions[i].MatchStObj(out ILInstruction target, out value, out var currentType)
if (!block.Instructions[i].MatchStObj(out ILInstruction target, out var value, out var currentType)
|| value.Descendants.OfType<IInstructionWithVariableOperand>().Any(inst => inst.Variable == store))
break;
if (elementType != null && !currentType.Equals(elementType))
// first
if (elementType == null)
{
elementType = currentType;
if (blob.StartPointer != null)
{
var countInstruction = PointerArithmeticOffset.Detect(lengthInstruction, elementType, checkForOverflow: true);
if (countInstruction == null || !countInstruction.MatchLdcI(out long valuesLength) || valuesLength < 1)
return false;
values = new StObj[(int)valuesLength];
int valueIndex = 0;
while (blob.RemainingBytes > 0 && valueIndex < values.Length)
{
values[valueIndex] = StElemPtr(store, blob.Offset, ReadElement(ref blob, elementType), elementType);
valueIndex++;
}
}
}
else if (!currentType.Equals(elementType))
{
break;
elementType = currentType;
}
// match the target
// should be either ldloc store (at offset 0)
// or binary.add(ldloc store, offset) where offset is either 'elementSize' or 'i * elementSize'
@ -403,7 +483,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -403,7 +483,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return new StObj(targetInst, storeInstruction.Value, storeInstruction.Type);
}
ILInstruction StElemPtr(ILVariable target, int offset, LdcI4 value, IType type)
StObj StElemPtr(ILVariable target, int offset, ILInstruction value, IType type)
{
var targetInst = offset == 0 ? (ILInstruction)new LdLoc(target) : new BinaryNumericInstruction(
BinaryNumericOperator.Add,
@ -698,12 +778,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -698,12 +778,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
IMethod method = call.Method;
if (!method.IsStatic || method.Name != "InitializeArray" || method.DeclaringTypeDefinition == null)
return false;
var declaringType = method.DeclaringTypeDefinition;
if (declaringType.DeclaringType != null || declaringType.Name != "RuntimeHelpers"
|| declaringType.Namespace != "System.Runtime.CompilerServices")
{
if (!IsRuntimeHelpers(method.DeclaringType))
return false;
}
array = call.Arguments[0];
if (!call.Arguments[1].MatchLdMemberToken(out var member))
return false;

13
ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs

@ -21,6 +21,7 @@ using System.Diagnostics; @@ -21,6 +21,7 @@ using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
@ -452,7 +453,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -452,7 +453,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; // for now we only support binary compound assignments
if (!targetType.IsKnownType(KnownTypeCode.String))
return false;
if (!IsMatchingCompoundLoad(concatCall.Arguments[0], compoundStore, out var target, out var targetKind, out var finalizeMatch, forbiddenVariable: storeInSetter?.Variable))
var arg = concatCall.Arguments[0];
if (arg is Call call && CallBuilder.IsStringToReadOnlySpanCharImplicitConversion(call.Method))
{
arg = call.Arguments[0];
if (!(concatCall.Arguments[1] is NewObj { Arguments: [AddressOf addressOf] } newObj) || !ILInlining.IsReadOnlySpanCharCtor(newObj.Method))
{
return false;
}
}
if (!IsMatchingCompoundLoad(arg, compoundStore, out var target, out var targetKind, out var finalizeMatch, forbiddenVariable: storeInSetter?.Variable))
return false;
context.Step($"Compound assignment (string concatenation)", compoundStore);
finalizeMatch?.Invoke(context);

24
ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs

@ -367,8 +367,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -367,8 +367,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
disposeCall = cv;
}
if (disposeCall.Method.FullName != disposeMethodFullName)
if (disposeCall.Method.IsStatic)
return false;
if (disposeTypeCode == KnownTypeCode.IAsyncDisposable)
{
if (disposeCall.Method.Name != "DisposeAsync")
return false;
}
else
{
if (disposeCall.Method.FullName != disposeMethodFullName)
return false;
}
if (disposeCall.Method.Parameters.Count > 0)
return false;
if (disposeCall.Arguments.Count != 1)
@ -505,9 +516,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -505,9 +516,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (!awaitInstruction.MatchAwait(out var arg))
return false;
if (!arg.MatchAddressOf(out awaitInstruction, out var type))
return false;
// TODO check type: does it match the structural 'Awaitable' pattern?
if (arg.MatchAddressOf(out var awaitInstructionInAddressOf, out var type))
{
awaitInstruction = awaitInstructionInAddressOf;
}
else
{
awaitInstruction = arg;
}
return true;
}
}

3
ICSharpCode.Decompiler/Semantics/ByReferenceResolveResult.cs

@ -30,9 +30,6 @@ namespace ICSharpCode.Decompiler.Semantics @@ -30,9 +30,6 @@ namespace ICSharpCode.Decompiler.Semantics
public class ByReferenceResolveResult : ResolveResult
{
public ReferenceKind ReferenceKind { get; }
public bool IsOut => ReferenceKind == ReferenceKind.Out;
public bool IsRef => ReferenceKind == ReferenceKind.Ref;
public bool IsIn => ReferenceKind == ReferenceKind.In;
public readonly ResolveResult ElementResult;

9
ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs

@ -21,6 +21,7 @@ using System.Collections.Immutable; @@ -21,6 +21,7 @@ using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.Decompiler.DebugInfo;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.Decompiler.Util;
@ -163,6 +164,13 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -163,6 +164,13 @@ namespace ICSharpCode.Decompiler.TypeSystem
}
}
public static IType ApplyAttributesToType(IType inputType, ICompilation compilation, TypeSystemOptions options, PdbExtraTypeInfo pdbExtraTypeInfo)
{
if (pdbExtraTypeInfo.DynamicFlags is null && pdbExtraTypeInfo.TupleElementNames is null)
return inputType;
return inputType.AcceptVisitor(new ApplyAttributeTypeVisitor(compilation, pdbExtraTypeInfo.DynamicFlags != null, pdbExtraTypeInfo.DynamicFlags, false, null, options, pdbExtraTypeInfo.TupleElementNames, Nullability.Oblivious, null));
}
readonly ICompilation compilation;
readonly bool hasDynamicAttribute;
readonly bool[] dynamicAttributeData;
@ -343,6 +351,7 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -343,6 +351,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
ReferenceKind.Ref => 1,
ReferenceKind.Out => 2, // in/out also count the modreq
ReferenceKind.In => 2,
ReferenceKind.RefReadOnly => 2, // counts the modopt
_ => throw new NotSupportedException()
};
parameters[i] = type.ParameterTypes[i].AcceptVisitor(this);

12
ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs

@ -124,8 +124,6 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -124,8 +124,6 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// will be reported as custom attribute.
/// </summary>
ScopedRef = 0x4000,
[Obsolete("Use ScopedRef instead")]
LifetimeAnnotations = ScopedRef,
/// <summary>
/// Replace 'IntPtr' types with the 'nint' type even in absence of [NativeIntegerAttribute].
/// Note: DecompilerTypeSystem constructor removes this setting from the options if
@ -133,11 +131,19 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -133,11 +131,19 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// </summary>
NativeIntegersWithoutAttribute = 0x8000,
/// <summary>
/// If this option is active, [RequiresLocationAttribute] on parameters is removed
/// and parameters are marked as ref readonly.
/// Otherwise, the attribute is preserved but the parameters are not marked
/// as if it was a ref parameter without any attributes.
/// </summary>
RefReadOnlyParameters = 0x10000,
/// <summary>
/// Default settings: typical options for the decompiler, with all C# languages features enabled.
/// </summary>
Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters
| RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods
| NativeIntegers | FunctionPointers | ScopedRef | NativeIntegersWithoutAttribute
| RefReadOnlyParameters
}
/// <summary>
@ -177,6 +183,8 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -177,6 +183,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
typeSystemOptions |= TypeSystemOptions.ScopedRef;
if (settings.NumericIntPtr)
typeSystemOptions |= TypeSystemOptions.NativeIntegersWithoutAttribute;
if (settings.RefReadOnlyParameters)
typeSystemOptions |= TypeSystemOptions.RefReadOnlyParameters;
return typeSystemOptions;
}

15
ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs

@ -58,17 +58,22 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -58,17 +58,22 @@ namespace ICSharpCode.Decompiler.TypeSystem
{
IType paramType = p;
ReferenceKind kind = ReferenceKind.None;
if (p is ModifiedType modreq)
if (p is ModifiedType mod)
{
if (modreq.Modifier.IsKnownType(KnownAttribute.In))
if (mod.Modifier.IsKnownType(KnownAttribute.In))
{
kind = ReferenceKind.In;
paramType = modreq.ElementType;
paramType = mod.ElementType;
}
else if (modreq.Modifier.IsKnownType(KnownAttribute.Out))
else if (mod.Modifier.IsKnownType(KnownAttribute.Out))
{
kind = ReferenceKind.Out;
paramType = modreq.ElementType;
paramType = mod.ElementType;
}
else if (mod.Modifier.IsKnownType(KnownAttribute.RequiresLocation))
{
kind = ReferenceKind.RefReadOnly;
paramType = mod.ElementType;
}
}
if (paramType.Kind == TypeKind.ByReference)

18
ICSharpCode.Decompiler/TypeSystem/IParameter.cs

@ -32,7 +32,8 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -32,7 +32,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
None,
Out,
Ref,
In
In,
RefReadOnly,
}
public struct LifetimeAnnotation
@ -71,21 +72,6 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -71,21 +72,6 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// </summary>
LifetimeAnnotation Lifetime { get; }
/// <summary>
/// Gets whether this parameter is a C# 'ref' parameter.
/// </summary>
bool IsRef { get; }
/// <summary>
/// Gets whether this parameter is a C# 'out' parameter.
/// </summary>
bool IsOut { get; }
/// <summary>
/// Gets whether this parameter is a C# 'in' parameter.
/// </summary>
bool IsIn { get; }
/// <summary>
/// Gets whether this parameter is a C# 'params' parameter.
/// </summary>

3
ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs

@ -255,6 +255,9 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -255,6 +255,9 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
case "ScopedRefAttribute":
return (options & TypeSystemOptions.ScopedRef) != 0
&& (target == SymbolKind.Parameter);
case "RequiresLocationAttribute":
return (options & TypeSystemOptions.RefReadOnlyParameters) != 0
&& (target == SymbolKind.Parameter);
default:
return false;
}

17
ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs

@ -76,9 +76,6 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -76,9 +76,6 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
public IEnumerable<IAttribute> GetAttributes() => attributes;
public ReferenceKind ReferenceKind => referenceKind;
public bool IsRef => referenceKind == ReferenceKind.Ref;
public bool IsOut => referenceKind == ReferenceKind.Out;
public bool IsIn => referenceKind == ReferenceKind.In;
public bool IsParams => isParams;
@ -115,12 +112,14 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -115,12 +112,14 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
public static string ToString(IParameter parameter)
{
StringBuilder b = new StringBuilder();
if (parameter.IsRef)
b.Append("ref ");
if (parameter.IsOut)
b.Append("out ");
if (parameter.IsIn)
b.Append("in ");
b.Append(parameter.ReferenceKind switch {
ReferenceKind.None => "",
ReferenceKind.Ref => "ref ",
ReferenceKind.Out => "out ",
ReferenceKind.In => "in ",
ReferenceKind.RefReadOnly => "ref readonly ",
_ => throw new NotSupportedException()
});
if (parameter.IsParams)
b.Append("params ");
b.Append(parameter.Name);

2
ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs

@ -95,6 +95,7 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -95,6 +95,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
CallerFilePath,
CallerLineNumber,
ScopedRef,
RequiresLocation,
// Type parameter attributes:
IsUnmanaged,
@ -173,6 +174,7 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -173,6 +174,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
new TopLevelTypeName("System.Runtime.CompilerServices", nameof(CallerFilePathAttribute)),
new TopLevelTypeName("System.Runtime.CompilerServices", nameof(CallerLineNumberAttribute)),
new TopLevelTypeName("System.Runtime.CompilerServices", "ScopedRefAttribute"),
new TopLevelTypeName("System.Runtime.CompilerServices", "RequiresLocationAttribute"),
// Type parameter attributes:
new TopLevelTypeName("System.Runtime.CompilerServices", "IsUnmanagedAttribute"),
// Marshalling attributes:

25
ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs

@ -21,7 +21,6 @@ using System.Collections.Generic; @@ -21,7 +21,6 @@ using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using ICSharpCode.Decompiler.Util;
@ -64,7 +63,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -64,7 +63,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
var metadata = module.metadata;
var parameter = metadata.GetParameter(handle);
bool defaultValueAssignmentAllowed = ReferenceKind is ReferenceKind.None or ReferenceKind.In;
bool defaultValueAssignmentAllowed = ReferenceKind is ReferenceKind.None or ReferenceKind.In or ReferenceKind.RefReadOnly;
if (IsOptional && (!defaultValueAssignmentAllowed || !HasConstantValueInSignature))
{
@ -76,13 +75,10 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -76,13 +75,10 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
b.Add(KnownAttribute.DefaultParameterValue, KnownTypeCode.Object, GetConstantValue(throwOnInvalidMetadata: false));
}
if (!IsOut && !IsIn)
{
if ((attributes & ParameterAttributes.In) == ParameterAttributes.In)
b.Add(KnownAttribute.In);
if ((attributes & ParameterAttributes.Out) == ParameterAttributes.Out)
b.Add(KnownAttribute.Out);
}
if ((attributes & ParameterAttributes.In) == ParameterAttributes.In && ReferenceKind is not (ReferenceKind.In or ReferenceKind.RefReadOnly))
b.Add(KnownAttribute.In);
if ((attributes & ParameterAttributes.Out) == ParameterAttributes.Out && ReferenceKind != ReferenceKind.Out)
b.Add(KnownAttribute.Out);
b.Add(parameter.GetCustomAttributes(), SymbolKind.Parameter);
b.AddMarshalInfo(parameter.GetMarshallingDescriptor());
@ -93,9 +89,6 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -93,9 +89,6 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
const ParameterAttributes inOut = ParameterAttributes.In | ParameterAttributes.Out;
public ReferenceKind ReferenceKind => DetectRefKind();
public bool IsRef => DetectRefKind() == ReferenceKind.Ref;
public bool IsOut => Type.Kind == TypeKind.ByReference && (attributes & inOut) == ParameterAttributes.Out;
public bool IsIn => DetectRefKind() == ReferenceKind.In;
public bool IsOptional => (attributes & ParameterAttributes.Optional) != 0;
@ -112,6 +105,14 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -112,6 +105,14 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
if (parameterDef.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.IsReadOnly))
return ReferenceKind.In;
}
if ((module.TypeSystemOptions & TypeSystemOptions.RefReadOnlyParameters) != 0
&& (attributes & inOut) == ParameterAttributes.In)
{
var metadata = module.metadata;
var parameterDef = metadata.GetParameter(handle);
if (parameterDef.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.RequiresLocation))
return ReferenceKind.RefReadOnly;
}
return ReferenceKind.Ref;
}

3
ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs

@ -37,9 +37,6 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -37,9 +37,6 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
IEnumerable<IAttribute> IParameter.GetAttributes() => baseParameter.GetAttributes();
ReferenceKind IParameter.ReferenceKind => baseParameter.ReferenceKind;
bool IParameter.IsRef => baseParameter.IsRef;
bool IParameter.IsOut => baseParameter.IsOut;
bool IParameter.IsIn => baseParameter.IsIn;
bool IParameter.IsParams => baseParameter.IsParams;
bool IParameter.IsOptional => baseParameter.IsOptional;
bool IParameter.HasConstantValueInSignature => baseParameter.HasConstantValueInSignature;

76
ICSharpCode.ILSpyX/Analyzers/AnalyzerContext.cs

@ -34,49 +34,49 @@ namespace ICSharpCode.ILSpyX.Analyzers @@ -34,49 +34,49 @@ namespace ICSharpCode.ILSpyX.Analyzers
{
public required AssemblyList AssemblyList { get; init; }
/// <summary>
/// CancellationToken. Currently Analyzers do not support cancellation from the UI, but it should be checked nonetheless.
/// </summary>
public CancellationToken CancellationToken { get; init; }
/// <summary>
/// CancellationToken. Currently Analyzers do not support cancellation from the UI, but it should be checked nonetheless.
/// </summary>
public CancellationToken CancellationToken { get; init; }
/// <summary>
/// Currently used language.
/// </summary>
public required ILanguage Language { get; init; }
/// <summary>
/// Currently used language.
/// </summary>
public required ILanguage Language { get; init; }
/// <summary>
/// Allows the analyzer to control whether the tree nodes will be sorted.
/// Must be set within <see cref="IAnalyzer.Analyze(ISymbol, AnalyzerContext)"/>
/// before the results are enumerated.
/// </summary>
public bool SortResults { get; set; }
/// <summary>
/// Allows the analyzer to control whether the tree nodes will be sorted.
/// Must be set within <see cref="IAnalyzer.Analyze(ISymbol, AnalyzerContext)"/>
/// before the results are enumerated.
/// </summary>
public bool SortResults { get; set; }
public MethodBodyBlock? GetMethodBody(IMethod method)
{
if (!method.HasBody || method.MetadataToken.IsNil || method.ParentModule?.MetadataFile == null)
return null;
var module = method.ParentModule.MetadataFile;
var md = module.Metadata.GetMethodDefinition((MethodDefinitionHandle)method.MetadataToken);
try
{
return module.GetMethodBody(md.RelativeVirtualAddress);
}
catch (BadImageFormatException)
{
return null;
}
}
public MethodBodyBlock? GetMethodBody(IMethod method)
{
if (!method.HasBody || method.MetadataToken.IsNil || method.ParentModule?.MetadataFile == null)
return null;
var module = method.ParentModule.MetadataFile;
var md = module.Metadata.GetMethodDefinition((MethodDefinitionHandle)method.MetadataToken);
try
{
return module.GetMethodBody(md.RelativeVirtualAddress);
}
catch (BadImageFormatException)
{
return null;
}
}
public AnalyzerScope GetScopeOf(IEntity entity)
{
return new AnalyzerScope(AssemblyList, entity);
}
public AnalyzerScope GetScopeOf(IEntity entity)
{
return new AnalyzerScope(AssemblyList, entity);
}
readonly ConcurrentDictionary<MetadataFile, DecompilerTypeSystem> typeSystemCache = new();
readonly ConcurrentDictionary<MetadataFile, DecompilerTypeSystem> typeSystemCache = new();
public DecompilerTypeSystem GetOrCreateTypeSystem(MetadataFile module)
{
return typeSystemCache.GetOrAdd(module, m => new DecompilerTypeSystem(m, m.GetAssemblyResolver()));
}
public DecompilerTypeSystem GetOrCreateTypeSystem(MetadataFile module)
{
return typeSystemCache.GetOrAdd(module, m => new DecompilerTypeSystem(m, m.GetAssemblyResolver()));
}
}
}

14
ICSharpCode.ILSpyX/Analyzers/ExportAnalyzerAttribute.cs

@ -17,7 +17,10 @@ @@ -17,7 +17,10 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reflection;
namespace ICSharpCode.ILSpyX.Analyzers
{
@ -31,5 +34,16 @@ namespace ICSharpCode.ILSpyX.Analyzers @@ -31,5 +34,16 @@ namespace ICSharpCode.ILSpyX.Analyzers
public required string Header { get; init; }
public int Order { get; set; }
public static IEnumerable<(ExportAnalyzerAttribute AttributeData, Type AnalyzerType)> GetAnnotatedAnalyzers()
{
foreach (var type in typeof(ExportAnalyzerAttribute).Assembly.GetTypes())
{
if (type.GetCustomAttribute(typeof(ExportAnalyzerAttribute), false) is ExportAnalyzerAttribute exportAnalyzerAttribute)
{
yield return (exportAnalyzerAttribute, type);
}
}
}
}
}

43
ILSpy.Tests/Analyzers/ExportAnalyzerAttributeTests.cs

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
// Copyright (c) 2024 Andreas Weizel
//
// 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.Linq;
using ICSharpCode.ILSpyX.Analyzers;
using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests.Analyzers
{
[TestFixture]
public class ExportAnalyzerAttributeTests
{
[Test]
public void CollectAnalyzers()
{
var analyzerNames = ExportAnalyzerAttribute.GetAnnotatedAnalyzers()
.Select(analyzer => analyzer.AnalyzerType.Name)
.ToArray();
Assert.That(analyzerNames.Contains("AttributeAppliedToAnalyzer"));
Assert.That(analyzerNames.Contains("EventImplementedByAnalyzer"));
Assert.That(analyzerNames.Contains("MethodUsedByAnalyzer"));
Assert.That(analyzerNames.Contains("PropertyOverriddenByAnalyzer"));
Assert.That(analyzerNames.Contains("TypeInstantiatedByAnalyzer"));
}
}
}

1
ILSpy.Tests/ILSpy.Tests.csproj

@ -34,6 +34,7 @@ @@ -34,6 +34,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="Analyzers\ExportAnalyzerAttributeTests.cs" />
<Compile Include="Analyzers\AnalyzerScopeTests.cs" />
<Compile Include="Analyzers\MemberImplementsInterfaceAnalyzerTests.cs" />
<Compile Include="Analyzers\MethodUsesAnalyzerTests.cs" />

1
ILSpy/Languages/CSharpLanguage.cs

@ -114,6 +114,7 @@ namespace ICSharpCode.ILSpy @@ -114,6 +114,7 @@ namespace ICSharpCode.ILSpy
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp9_0.ToString(), "C# 9.0 / VS 2019.8"),
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp10_0.ToString(), "C# 10.0 / VS 2022"),
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp11_0.ToString(), "C# 11.0 / VS 2022.4"),
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp12_0.ToString(), "C# 12.0 / VS 2022.8"),
};
}
return versions;

18
ILSpy/Properties/Resources.Designer.cs generated

@ -1154,15 +1154,6 @@ namespace ICSharpCode.ILSpy.Properties { @@ -1154,15 +1154,6 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Use parameter null checking.
/// </summary>
public static string DecompilerSettings_ParameterNullCheck {
get {
return ResourceManager.GetString("DecompilerSettings.ParameterNullCheck", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Pattern combinators (and, or, not).
/// </summary>
@ -1235,6 +1226,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -1235,6 +1226,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to &apos;ref readonly&apos; parameters.
/// </summary>
public static string DecompilerSettings_RefReadOnlyParameters {
get {
return ResourceManager.GetString("DecompilerSettings.RefReadOnlyParameters", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Relational patterns.
/// </summary>

6
ILSpy/Properties/Resources.resx

@ -408,9 +408,6 @@ Are you sure you want to continue?</value> @@ -408,9 +408,6 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.Other" xml:space="preserve">
<value>Other</value>
</data>
<data name="DecompilerSettings.ParameterNullCheck" xml:space="preserve">
<value>Use parameter null checking</value>
</data>
<data name="DecompilerSettings.PatternCombinators" xml:space="preserve">
<value>Pattern combinators (and, or, not)</value>
</data>
@ -435,6 +432,9 @@ Are you sure you want to continue?</value> @@ -435,6 +432,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.RecursivePatternMatching" xml:space="preserve">
<value>Recursive pattern matching</value>
</data>
<data name="DecompilerSettings.RefReadOnlyParameters" xml:space="preserve">
<value>'ref readonly' parameters</value>
</data>
<data name="DecompilerSettings.RelationalPatterns" xml:space="preserve">
<value>Relational patterns</value>
</data>

3
ILSpy/Properties/Resources.zh-Hans.resx

@ -399,9 +399,6 @@ @@ -399,9 +399,6 @@
<data name="DecompilerSettings.Other" xml:space="preserve">
<value>其他</value>
</data>
<data name="DecompilerSettings.ParameterNullCheck" xml:space="preserve">
<value>使用方法参数非空校验</value>
</data>
<data name="DecompilerSettings.PatternMatching" xml:space="preserve">
<value>使用模式匹配表达式</value>
</data>

Loading…
Cancel
Save