Browse Source

Merge branch 'master' into master

pull/3234/head
SlimeNull 1 year ago committed by GitHub
parent
commit
b5e24d0bf6
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. 8
      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
  68. 8
      README.md

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

8
Directory.Packages.props

@ -17,8 +17,8 @@ @@ -17,8 +17,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" />
@ -37,7 +37,7 @@ @@ -37,7 +37,7 @@
<PackageVersion Include="NSubstitute.Analyzers.CSharp" Version="1.0.17" />
<PackageVersion Include="NUnit" Version="4.1.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageVersion Include="NuGet.Protocol" Version="6.10.0" />
<PackageVersion Include="NuGet.Protocol" Version="6.10.1" />
<PackageVersion Include="PowerShellStandard.Library" Version="5.1.1" />
<PackageVersion Include="System.Collections.Immutable" Version="8.0.0" />
<PackageVersion Include="System.ComponentModel.Composition" Version="8.0.0" />
@ -47,7 +47,7 @@ @@ -47,7 +47,7 @@
<PackageVersion Include="System.Resources.Extensions" Version="8.0.0" />
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageVersion Include="TomsToolbox.Wpf" Version="2.15.0" />
<PackageVersion Include="TomsToolbox.Wpf.Styles" Version="2.14.0" />
<PackageVersion Include="TomsToolbox.Wpf.Styles" Version="2.15.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
</ItemGroup>
</Project>

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>

8
README.md

@ -45,14 +45,14 @@ How to build @@ -45,14 +45,14 @@ How to build
#### Windows:
- Make sure PowerShell (at least version) 5.0 is installed.
- Make sure Windows PowerShell (at least version) 5.0 or [PowerShell](https://github.com/PowerShell/PowerShell) 7+ is installed.
- Clone the ILSpy repository using git.
- Execute `git submodule update --init --recursive` to download the ILSpy-Tests submodule (used by some test cases).
- Install Visual Studio (documented version: 17.8). You can install the necessary components in one of 3 ways:
- Follow Microsoft's instructions for [importing a configuration](https://docs.microsoft.com/en-us/visualstudio/install/import-export-installation-configurations?view=vs-2022#import-a-configuration), and import the .vsconfig file located at the root of the solution.
- Alternatively, you can open the ILSpy solution (ILSpy.sln) and Visual Studio will [prompt you to install the missing components](https://docs.microsoft.com/en-us/visualstudio/install/import-export-installation-configurations?view=vs-2022#automatically-install-missing-components).
- Finally, you can manually install the necessary components via the Visual Studio Installer. The workloads/components are as follows:
- Workload ".NET Desktop Development". This workload includes the .NET Framework 4.8 SDK and the .NET Framework 4.7.2 targeting pack, as well as the [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) (ILSpy.csproj targets .NET 6.0, but we have net472 projects too). _Note: The optional components of this workload are not required for ILSpy_
- Workload ".NET Desktop Development". This workload includes the .NET Framework 4.8 SDK and the .NET Framework 4.7.2 targeting pack, as well as the [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) (ILSpy.csproj targets .NET 8.0, but we have net472 projects too). _Note: The optional components of this workload are not required for ILSpy_
- Workload "Visual Studio extension development" (ILSpy.sln contains a VS extension project) _Note: The optional components of this workload are not required for ILSpy_
- Individual Component "MSVC v143 - VS 2022 C++ x64/x86 build tools" (or similar)
- _The VC++ toolset is optional_; if present it is used for `editbin.exe` to modify the stack size used by ILSpy.exe from 1MB to 16MB, because the decompiler makes heavy use of recursion, where small stack sizes lead to problems in very complex methods.
@ -66,8 +66,8 @@ How to build @@ -66,8 +66,8 @@ How to build
- ILSpy.AddIn.slnf: for the Visual Studio plugin
**Note:** Visual Studio includes a version of the .NET SDK that is managed by the Visual Studio installer - once you update, it may get upgraded too.
Please note that ILSpy is only compatible with the .NET 6.0 SDK and Visual Studio will refuse to load some projects in the solution (and unit tests will fail).
If this problem occurs, please manually install the .NET 6.0 SDK from [here](https://dotnet.microsoft.com/download/dotnet/6.0).
Please note that ILSpy is only compatible with the .NET 8.0 SDK and Visual Studio will refuse to load some projects in the solution (and unit tests will fail).
If this problem occurs, please manually install the .NET 8.0 SDK from [here](https://dotnet.microsoft.com/download/dotnet/8.0).
#### Unix / Mac:

Loading…
Cancel
Save