From 4693fba4179487e81e6d45bd2300fa53ec44f965 Mon Sep 17 00:00:00 2001 From: Christoph Wille Date: Tue, 16 Jul 2024 12:22:31 +0200 Subject: [PATCH 01/30] Update NuGet packages --- Directory.Packages.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 324ee40f21..f70e32fbcd 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -29,14 +29,14 @@ - + - + @@ -45,7 +45,7 @@ - + \ No newline at end of file From d48df11133d6deeeaecc61f8a5aeb56097771d79 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 19 Jul 2024 09:23:17 +0200 Subject: [PATCH 02/30] Remove unused transforms: DecimalConstantTransform and ParameterNullCheckTransform --- .../CSharp/CSharpDecompiler.cs | 2 - .../Transforms/DecimalConstantTransform.cs | 62 ------------- ICSharpCode.Decompiler/DecompilerSettings.cs | 23 +---- .../Transforms/ParameterNullCheckTransform.cs | 90 ------------------- 4 files changed, 1 insertion(+), 176 deletions(-) delete mode 100644 ICSharpCode.Decompiler/CSharp/Transforms/DecimalConstantTransform.cs delete mode 100644 ICSharpCode.Decompiler/IL/Transforms/ParameterNullCheckTransform.cs diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 9d44a14a35..53bf7942b3 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -91,7 +91,6 @@ public static List GetILTransforms() 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 @@ public static List GetAstTransforms() 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 diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DecimalConstantTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DecimalConstantTransform.cs deleted file mode 100644 index ea9d78ff9f..0000000000 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DecimalConstantTransform.cs +++ /dev/null @@ -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 -{ - /// - /// Transforms decimal constant fields. - /// - 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); - } - } -} diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 1032093b39..533eb1ed3f 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -152,7 +152,6 @@ public void SetLanguageVersion(CSharp.LanguageVersion languageVersion) } if (languageVersion < CSharp.LanguageVersion.CSharp11_0) { - parameterNullCheck = false; scopedRef = false; requiredMembers = false; numericIntPtr = false; @@ -164,7 +163,7 @@ public void SetLanguageVersion(CSharp.LanguageVersion languageVersion) public CSharp.LanguageVersion GetMinimumRequiredVersion() { - if (parameterNullCheck || scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators) + if (scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators) return CSharp.LanguageVersion.CSharp11_0; if (fileScopedNamespaces || recordStructs) return CSharp.LanguageVersion.CSharp10_0; @@ -443,26 +442,6 @@ public bool FileScopedNamespaces { } } - bool parameterNullCheck = false; - - /// - /// Use C# 11 preview parameter null-checking (string param!!). - /// - [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; /// diff --git a/ICSharpCode.Decompiler/IL/Transforms/ParameterNullCheckTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/ParameterNullCheckTransform.cs deleted file mode 100644 index 7deb3d9eaf..0000000000 --- a/ICSharpCode.Decompiler/IL/Transforms/ParameterNullCheckTransform.cs +++ /dev/null @@ -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 -{ - /// - /// Implements transforming <PrivateImplementationDetails>.ThrowIfNull(name, "name"); - /// - 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 .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("", "ThrowIfNull")) - return false; - if (!(call.Arguments[0].MatchLdLoc(out parameterVariable) && parameterVariable.Kind == VariableKind.Parameter)) - return false; - return true; - } - } -} From ce8fb83c52bbcb42e9b9d2a3cb807b9b2a5eb169 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 19 Jul 2024 09:39:59 +0200 Subject: [PATCH 03/30] Forgot to commit some files --- ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj | 2 -- ILSpy/Properties/Resources.resx | 3 --- ILSpy/Properties/Resources.zh-Hans.resx | 3 --- 3 files changed, 8 deletions(-) diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 7fe6df907a..3fdc756e72 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -135,7 +135,6 @@ - @@ -336,7 +335,6 @@ - diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index cc08fca35e..19b1cc2b82 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -408,9 +408,6 @@ Are you sure you want to continue? Other - - Use parameter null checking - Pattern combinators (and, or, not) diff --git a/ILSpy/Properties/Resources.zh-Hans.resx b/ILSpy/Properties/Resources.zh-Hans.resx index 0304ce4468..8e46de32ef 100644 --- a/ILSpy/Properties/Resources.zh-Hans.resx +++ b/ILSpy/Properties/Resources.zh-Hans.resx @@ -399,9 +399,6 @@ 其他 - - 使用方法参数非空校验 - 使用模式匹配表达式 From 28b7280741dd5f1a3fef39b34232614bb5e067f8 Mon Sep 17 00:00:00 2001 From: Christoph Wille Date: Fri, 19 Jul 2024 16:27:57 +0200 Subject: [PATCH 04/30] Fix dotnet-format to v8 via transport feed for dotnet8. See also PR #2747 --- .github/workflows/build-ilspy.yml | 2 +- BuildTools/pre-commit | 4 +- .../Analyzers/AnalyzerContext.cs | 76 +++++++++---------- 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/.github/workflows/build-ilspy.yml b/.github/workflows/build-ilspy.yml index 444d22b3c6..10e5d8e9de 100644 --- a/.github/workflows/build-ilspy.yml +++ b/.github/workflows/build-ilspy.yml @@ -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 diff --git a/BuildTools/pre-commit b/BuildTools/pre-commit index a55e3bdc49..4f2cfb4466 100644 --- a/BuildTools/pre-commit +++ b/BuildTools/pre-commit @@ -5,10 +5,10 @@ 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" + 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 diff --git a/ICSharpCode.ILSpyX/Analyzers/AnalyzerContext.cs b/ICSharpCode.ILSpyX/Analyzers/AnalyzerContext.cs index 09112056f4..1c339ef0ae 100644 --- a/ICSharpCode.ILSpyX/Analyzers/AnalyzerContext.cs +++ b/ICSharpCode.ILSpyX/Analyzers/AnalyzerContext.cs @@ -34,49 +34,49 @@ public class AnalyzerContext { public required AssemblyList AssemblyList { get; init; } - /// - /// CancellationToken. Currently Analyzers do not support cancellation from the UI, but it should be checked nonetheless. - /// - public CancellationToken CancellationToken { get; init; } + /// + /// CancellationToken. Currently Analyzers do not support cancellation from the UI, but it should be checked nonetheless. + /// + public CancellationToken CancellationToken { get; init; } - /// - /// Currently used language. - /// - public required ILanguage Language { get; init; } + /// + /// Currently used language. + /// + public required ILanguage Language { get; init; } -/// -/// Allows the analyzer to control whether the tree nodes will be sorted. -/// Must be set within -/// before the results are enumerated. -/// -public bool SortResults { get; set; } + /// + /// Allows the analyzer to control whether the tree nodes will be sorted. + /// Must be set within + /// before the results are enumerated. + /// + 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 typeSystemCache = new(); + readonly ConcurrentDictionary 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())); + } } } From da24b7d126600c46c6c9f76f2a479f696f7b5164 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 19 Jul 2024 17:00:44 +0200 Subject: [PATCH 05/30] Add BuildTools/format.bat --- BuildTools/format.bat | 3 +++ BuildTools/pre-commit | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 BuildTools/format.bat diff --git a/BuildTools/format.bat b/BuildTools/format.bat new file mode 100644 index 0000000000..be49dc68ce --- /dev/null +++ b/BuildTools/format.bat @@ -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 \ No newline at end of file diff --git a/BuildTools/pre-commit b/BuildTools/pre-commit index 4f2cfb4466..eee675c87f 100644 --- a/BuildTools/pre-commit +++ b/BuildTools/pre-commit @@ -8,13 +8,18 @@ set -eu DOTNET_FORMAT_VERSION=8.0.453106 DOTNET_PATH="$LOCALAPPDATA/ICSharpCode/ILSpy/dotnet-format-$DOTNET_FORMAT_VERSION" if [ ! -d "$DOTNET_PATH" ]; then + 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 From 4bf9487ecdbf1324b2eead930059fb8191a7c436 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 19 Jul 2024 15:03:26 +0200 Subject: [PATCH 06/30] Remove IsRef, IsOut and IsIn flags from IParameter and Replace ParameterModifiers with ReferenceKind. --- .../TypeSystem/TypeSystemLoaderTests.cs | 8 +-- .../CSharp/CSharpDecompiler.cs | 13 +++-- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 2 +- .../CSharp/ExpressionBuilder.cs | 2 +- .../CSharp/OutputVisitor/CSharpAmbience.cs | 4 +- .../OutputVisitor/CSharpOutputVisitor.cs | 20 +++++--- .../CSharp/RecordDecompiler.cs | 6 +-- .../CSharp/Resolver/CSharpConversions.cs | 4 +- .../TypeMembers/ParameterDeclaration.cs | 50 ++++--------------- .../CSharp/Syntax/TypeSystemAstBuilder.cs | 30 +++-------- .../Transforms/IntroduceQueryExpressions.cs | 2 +- .../FlowAnalysis/DefiniteAssignmentVisitor.cs | 4 +- ICSharpCode.Decompiler/IL/ILReader.cs | 2 +- .../IL/Instructions/MatchInstruction.cs | 2 +- .../IL/Transforms/LocalFunctionDecompiler.cs | 4 +- .../TypeSystem/ApplyAttributeTypeVisitor.cs | 1 + .../TypeSystem/DecompilerTypeSystem.cs | 10 +++- .../TypeSystem/IParameter.cs | 18 +------ .../Implementation/DefaultParameter.cs | 17 +++---- .../Implementation/MetadataParameter.cs | 15 ++---- .../Implementation/SpecializedParameter.cs | 3 -- 21 files changed, 81 insertions(+), 136 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs index b27a1ecc5c..2059f6c428 100644 --- a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs +++ b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs @@ -1390,18 +1390,18 @@ public void ExplicitGenericInterfaceImplementation() { ITypeDefinition impl = GetTypeDefinition(typeof(ExplicitGenericInterfaceImplementation)); IType genericInterfaceOfString = compilation.FindType(typeof(IGenericInterface)); - 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] diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 53bf7942b3..b4597e4eb6 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1167,7 +1167,7 @@ IEnumerable AddInterfaceImplHelpers( 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)) { @@ -1185,12 +1185,17 @@ Expression ForwardParameter(ParameterDeclaration p) { 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(); } } diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index d34ec1cfd1..40778477e8 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -78,7 +78,7 @@ public IList GetArgumentResolveResults(int skipCount = 0) 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; } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index e20b6d8181..ce410ab491 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -2409,7 +2409,7 @@ internal ExpressionWithResolveResult TranslateFunction(IType delegateType, ILFun // 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); diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs index 8e6c5df31a..26474c8c87 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs @@ -135,7 +135,9 @@ public void ConvertSymbol(ISymbol symbol, TokenWriter writer, CSharpFormattingOp { if ((ConversionFlags & ConversionFlags.ShowParameterModifiers) == 0) { - param.ParameterModifier = ParameterModifier.None; + param.ParameterModifier = ReferenceKind.None; + param.IsScopedRef = false; + param.IsParams = false; } if ((ConversionFlags & ConversionFlags.ShowParameterDefaultValues) == 0) { diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 66e6a2acfa..e624a8db28 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -1077,7 +1077,7 @@ protected bool LambdaNeedsParenthesis(LambdaExpression lambdaExpression) 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 @@ public virtual void VisitParameterDeclaration(ParameterDeclaration parameterDecl WriteKeyword(ParameterDeclaration.ThisModifierRole); Space(); } + if (parameterDeclaration.IsParams) + { + WriteKeyword(ParameterDeclaration.ParamsModifierRole); + Space(); + } if (parameterDeclaration.IsScopedRef) { WriteKeyword(ParameterDeclaration.ScopedRefRole); @@ -2615,19 +2620,20 @@ public virtual void VisitParameterDeclaration(ParameterDeclaration parameterDecl } 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; diff --git a/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs b/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs index 8abe102f2c..15d6a6d340 100644 --- a/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs @@ -196,7 +196,7 @@ bool IsPrimaryConstructor(IMethod method, IMethod unspecializedMethod) 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 @@ bool IsGeneratedDeconstruct(IMethod method) 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; diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs index 86be39f383..51ea711448 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs @@ -1128,7 +1128,7 @@ List GetApplicableConversionOperators(ResolveResult fromResult, IT 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 @@ Conversion AnonymousFunctionConversion(ResolveResult resolveResult, IType toType // 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; } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs index a86945d665..617f0427a8 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs @@ -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 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 @@ public AstNodeCollection Attributes { } bool hasThisModifier; + bool isParams; bool isScopedRef; public CSharpTokenNode ThisKeyword { @@ -127,16 +115,15 @@ public bool HasThisModifier { } } - 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 @@ public bool IsRefScoped { } } - [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 @@ public ParameterDeclaration() { } - 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(); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 799e790354..c269983037 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -372,14 +372,9 @@ AstType ConvertTypeHelper(IType type) 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 @@ public ParameterDeclaration ConvertParameter(IParameter parameter) 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 @@ public ParameterDeclaration ConvertParameter(IParameter parameter) { 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 { diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs index 6d804cf09b..8e5e55368b 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs @@ -389,7 +389,7 @@ bool MatchSimpleLambda(Expression expr, out ParameterDeclaration parameter, out 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; } } } diff --git a/ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs b/ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs index c12175f0be..c12aa95791 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs @@ -262,7 +262,7 @@ void HandleCall(CallInstruction call) 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 @@ void HandleCall(CallInstruction call) { 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); } diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index ca1ed07c19..46038f2e4b 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -285,7 +285,7 @@ void InitParameterVariables() { 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++; } diff --git a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs index af9074f123..6ed9b37605 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -267,7 +267,7 @@ internal static bool IsDeconstructMethod(IMethod? method) for (int i = firstOutParam; i < method.Parameters.Count; i++) { - if (!method.Parameters[i].IsOut) + if (method.Parameters[i].ReferenceKind != ReferenceKind.Out) return false; } diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index bc0e209c4c..f00fc48b0e 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -556,9 +556,9 @@ static T FindCommonAncestorInstruction(ILInstruction a, ILInstruction b) 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); diff --git a/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs index 0ad2985139..6734096b60 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs @@ -343,6 +343,7 @@ public override IType VisitFunctionPointerType(FunctionPointerType type) 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); diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs index 536b00cacd..5906c97ea5 100644 --- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs +++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs @@ -124,8 +124,6 @@ public enum TypeSystemOptions /// will be reported as custom attribute. /// ScopedRef = 0x4000, - [Obsolete("Use ScopedRef instead")] - LifetimeAnnotations = ScopedRef, /// /// 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 @@ public enum TypeSystemOptions /// NativeIntegersWithoutAttribute = 0x8000, /// + /// 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. + /// + RefReadOnlyParameters = 0x10000, + /// /// Default settings: typical options for the decompiler, with all C# languages features enabled. /// Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters | RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods | NativeIntegers | FunctionPointers | ScopedRef | NativeIntegersWithoutAttribute + | RefReadOnlyParameters } /// diff --git a/ICSharpCode.Decompiler/TypeSystem/IParameter.cs b/ICSharpCode.Decompiler/TypeSystem/IParameter.cs index aee0078a93..4816133183 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IParameter.cs @@ -32,7 +32,8 @@ public enum ReferenceKind : byte None, Out, Ref, - In + In, + RefReadOnly, } public struct LifetimeAnnotation @@ -71,21 +72,6 @@ public interface IParameter : IVariable /// LifetimeAnnotation Lifetime { get; } - /// - /// Gets whether this parameter is a C# 'ref' parameter. - /// - bool IsRef { get; } - - /// - /// Gets whether this parameter is a C# 'out' parameter. - /// - bool IsOut { get; } - - /// - /// Gets whether this parameter is a C# 'in' parameter. - /// - bool IsIn { get; } - /// /// Gets whether this parameter is a C# 'params' parameter. /// diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs index e3a1a8a256..4fa30a8180 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs @@ -76,9 +76,6 @@ public IParameterizedMember Owner { public IEnumerable 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 @@ public override string ToString() 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); diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs index c2cba9824e..294d8d1240 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs @@ -21,7 +21,6 @@ using System.Reflection; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; -using System.Text; using ICSharpCode.Decompiler.Util; @@ -76,13 +75,10 @@ public IEnumerable GetAttributes() 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 @@ public IEnumerable GetAttributes() 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; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs index a05340d3a3..0a68894a67 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs @@ -37,9 +37,6 @@ public SpecializedParameter(IParameter baseParameter, IType newType, IParameteri IEnumerable 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; From 3b2affa13bece1806f7d56b941e02148a6d7aee9 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 19 Jul 2024 15:13:37 +0200 Subject: [PATCH 07/30] Add new language versions to DefineConstants in Decompiler.Tests --- .../ICSharpCode.Decompiler.Tests.csproj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 7970f62653..09f5092f0f 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -17,6 +17,7 @@ True 1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632;626;8618;8714;8602;8981 + ROSLYN;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120 False False @@ -40,11 +41,11 @@ - TRACE;DEBUG;ROSLYN;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100 + TRACE;DEBUG;$(DefineConstants) - TRACE;ROSLYN;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100 + TRACE;$(DefineConstants) From 5d36732fcf7435975223b36d3303bbd1082dbd37 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 19 Jul 2024 15:19:21 +0200 Subject: [PATCH 08/30] Remove ApplyPdbLocalTypeInfoTypeVisitor (merge it into ApplyAttributeTypeVisitor) --- .../ICSharpCode.Decompiler.csproj | 1 - .../IL/ApplyPdbLocalTypeInfoTypeVisitor.cs | 159 ------------------ ICSharpCode.Decompiler/IL/ILReader.cs | 2 +- .../TypeSystem/ApplyAttributeTypeVisitor.cs | 8 + 4 files changed, 9 insertions(+), 161 deletions(-) delete mode 100644 ICSharpCode.Decompiler/IL/ApplyPdbLocalTypeInfoTypeVisitor.cs diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 3fdc756e72..ba3a308e85 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -97,7 +97,6 @@ - diff --git a/ICSharpCode.Decompiler/IL/ApplyPdbLocalTypeInfoTypeVisitor.cs b/ICSharpCode.Decompiler/IL/ApplyPdbLocalTypeInfoTypeVisitor.cs deleted file mode 100644 index 1d8796c1e7..0000000000 --- a/ICSharpCode.Decompiler/IL/ApplyPdbLocalTypeInfoTypeVisitor.cs +++ /dev/null @@ -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 -{ - /// - /// Heavily based on - /// - 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; - } - } -} diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index 46038f2e4b..2ec9da40ac 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -308,7 +308,7 @@ ILVariable CreateILVariable(int index, IType type) 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); diff --git a/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs index 6734096b60..76418f3b25 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs @@ -21,6 +21,7 @@ using System.Diagnostics; using System.Linq; +using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.Util; @@ -163,6 +164,13 @@ void ProcessAttribute(SRM.CustomAttributeHandle attrHandle) } } + 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; From 02d2a8c1f875cc2dd4c15a0e3f1079658d9ef76e Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 19 Jul 2024 16:02:32 +0200 Subject: [PATCH 09/30] Add metadata processing for C# 12 'ref readonly' parameters --- .../Helpers/Tester.cs | 2 +- .../TestCases/Pretty/RefLocalsAndReturns.cs | 18 ++++++++++++++ .../CSharp/CSharpLanguageVersion.cs | 1 + .../CSharp/ExpressionBuilder.cs | 8 ++++++- .../CSharp/Transforms/DeclareVariables.cs | 6 ++++- .../Transforms/EscapeInvalidIdentifiers.cs | 1 + ICSharpCode.Decompiler/DecompilerSettings.cs | 24 +++++++++++++++++++ .../TypeSystem/DecompilerTypeSystem.cs | 2 ++ .../Implementation/AttributeListBuilder.cs | 3 +++ .../Implementation/KnownAttributes.cs | 2 ++ .../Implementation/MetadataParameter.cs | 8 +++++++ ILSpy/Languages/CSharpLanguage.cs | 1 + ILSpy/Properties/Resources.Designer.cs | 18 +++++++------- ILSpy/Properties/Resources.resx | 3 +++ 14 files changed, 85 insertions(+), 12 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 92eb339f6a..709bf4cae3 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -596,7 +596,7 @@ internal static DecompilerSettings GetSettings(CompilerOptions cscOptions) 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 diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs index f4c0f1187d..d3e2f75c63 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs @@ -320,5 +320,23 @@ public static void Main(string[] args) 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 +} } diff --git a/ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs b/ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs index 87314b0c40..019774580c 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs @@ -34,6 +34,7 @@ public enum LanguageVersion CSharp9_0 = 900, CSharp10_0 = 1000, CSharp11_0 = 1100, + CSharp12_0 = 1200, Preview = 1100, Latest = 0x7FFFFFFF } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index ce410ab491..a5127c6fb8 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4188,7 +4188,13 @@ internal static TranslatedExpression ChangeDirectionExpressionTo(TranslatedExpre { if (!(input.Expression is DirectionExpression dirExpr && input.ResolveResult is ByReferenceResolveResult brrr)) return input; - dirExpr.FieldDirection = (FieldDirection)kind; + 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(); if (brrr.ElementResult == null) brrr = new ByReferenceResolveResult(brrr.ElementType, kind); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index b17960e36f..c84d9e81a5 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -205,7 +205,11 @@ void EnsureExpressionStatementsAreValid(AstNode rootNode) { foreach (var stmt in rootNode.DescendantsAndSelf.OfType()) { - 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()).First(f => f.Parent == null); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs b/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs index 2b1dc7f1d7..828d220af0 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs @@ -168,6 +168,7 @@ public class RemoveEmbeddedAttributes : DepthFirstAstVisitor, IAstTransform "System.Runtime.CompilerServices.NativeIntegerAttribute", "System.Runtime.CompilerServices.RefSafetyRulesAttribute", "System.Runtime.CompilerServices.ScopedRefAttribute", + "System.Runtime.CompilerServices.RequiresLocationAttribute", "Microsoft.CodeAnalysis.EmbeddedAttribute", }; diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 533eb1ed3f..c9d2e77ab6 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -159,10 +159,16 @@ public void SetLanguageVersion(CSharp.LanguageVersion languageVersion) unsignedRightShift = false; checkedOperators = false; } + if (languageVersion < CSharp.LanguageVersion.CSharp12_0) + { + refReadOnlyParameters = false; + } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { + if (refReadOnlyParameters) + return CSharp.LanguageVersion.CSharp12_0; if (scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators) return CSharp.LanguageVersion.CSharp11_0; if (fileScopedNamespaces || recordStructs) @@ -1991,6 +1997,24 @@ public bool DoWhileStatement { } } + bool refReadOnlyParameters = true; + + /// + /// Gets/sets whether RequiresLocationAttribute on parameters should be replaced with 'ref readonly' modifiers. + /// + [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; /// diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs index 5906c97ea5..896f9e802d 100644 --- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs +++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs @@ -183,6 +183,8 @@ public static TypeSystemOptions GetOptions(DecompilerSettings settings) typeSystemOptions |= TypeSystemOptions.ScopedRef; if (settings.NumericIntPtr) typeSystemOptions |= TypeSystemOptions.NativeIntegersWithoutAttribute; + if (settings.RefReadOnlyParameters) + typeSystemOptions |= TypeSystemOptions.RefReadOnlyParameters; return typeSystemOptions; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs index cea8132506..66d77a7357 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs @@ -255,6 +255,9 @@ internal bool IgnoreAttribute(TopLevelTypeName attributeType, SymbolKind target) case "ScopedRefAttribute": return (options & TypeSystemOptions.ScopedRef) != 0 && (target == SymbolKind.Parameter); + case "RequiresLocationAttribute": + return (options & TypeSystemOptions.RefReadOnlyParameters) != 0 + && (target == SymbolKind.Parameter); default: return false; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs index 59dfc5863a..59d2c19eb4 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs @@ -95,6 +95,7 @@ public enum KnownAttribute CallerFilePath, CallerLineNumber, ScopedRef, + RequiresLocation, // Type parameter attributes: IsUnmanaged, @@ -173,6 +174,7 @@ public static class KnownAttributes 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: diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs index 294d8d1240..40b4e1f061 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs @@ -105,6 +105,14 @@ ReferenceKind DetectRefKind() 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; } diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index a7fc1cb554..212d4f870e 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -114,6 +114,7 @@ public override IReadOnlyList LanguageVersions { 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; diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 1866a7b293..54ec14ad55 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1154,15 +1154,6 @@ public static string DecompilerSettings_Other { } } - /// - /// Looks up a localized string similar to Use parameter null checking. - /// - public static string DecompilerSettings_ParameterNullCheck { - get { - return ResourceManager.GetString("DecompilerSettings.ParameterNullCheck", resourceCulture); - } - } - /// /// Looks up a localized string similar to Pattern combinators (and, or, not). /// @@ -1235,6 +1226,15 @@ public static string DecompilerSettings_RecursivePatternMatching { } } + /// + /// Looks up a localized string similar to 'ref readonly' parameters. + /// + public static string DecompilerSettings_RefReadOnlyParameters { + get { + return ResourceManager.GetString("DecompilerSettings.RefReadOnlyParameters", resourceCulture); + } + } + /// /// Looks up a localized string similar to Relational patterns. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index 19b1cc2b82..096c9d74b5 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -432,6 +432,9 @@ Are you sure you want to continue? Recursive pattern matching + + 'ref readonly' parameters + Relational patterns From 783c934bfd2d965dad09bdad79e40d48e0ffc205 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 19 Jul 2024 16:53:25 +0200 Subject: [PATCH 10/30] Transform RequiresLocationAttribute to 'ref readonly' on function pointers. --- .../ICSharpCode.Decompiler.Tests.csproj | 2 +- .../TestCases/Pretty/DynamicTests.cs | 9 +++++++++ .../TestCases/Pretty/FunctionPointers.cs | 11 +++++++---- .../TestCases/Pretty/RefLocalsAndReturns.cs | 15 ++++++++------- .../TypeSystem/FunctionPointerType.cs | 15 ++++++++++----- 5 files changed, 35 insertions(+), 17 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 09f5092f0f..697ca18d97 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -153,7 +153,7 @@ - + diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs index 01edd50d21..7b90338f10 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicTests.cs @@ -1,4 +1,7 @@ using System; +#if CS120 +using System.Collections.Generic; +#endif namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { @@ -74,6 +77,12 @@ private static void CallWithIn(in dynamic d) } #endif +#if CS120 + private static void CallWithRefReadonly(ref readonly Dictionary d) + { + } +#endif + private static void CallWithRef(ref dynamic d) { } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs index 9423c42542..aa49e2b792 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs @@ -95,14 +95,17 @@ public class B public unsafe delegate* F12; public unsafe delegate* F13; public unsafe delegate* F14; - public unsafe D[], dynamic> F15; - public unsafe delegate*.B> F16; +#if CS120 + public unsafe delegate* F15; +#endif + public unsafe D[], dynamic> F16; + public unsafe delegate*.B> F17; } internal class FunctionPointersWithNativeIntegerTypes { public unsafe delegate* F1; - #if !(CS110 && NET70) +#if !(CS110 && NET70) public unsafe delegate* F2; public unsafe delegate* F3; public unsafe delegate* F4; @@ -111,7 +114,7 @@ internal class FunctionPointersWithNativeIntegerTypes public unsafe delegate*, IntPtr> F7; public unsafe delegate*> F8; public unsafe delegate*> F9; - #endif +#endif } internal class FunctionPointersWithRefParams diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs index d3e2f75c63..7a65ff10ac 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs @@ -326,17 +326,18 @@ 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); - } + public void Test() + { + int x = 32; + M(in x); + M2(in x); + } #endif -} + } } diff --git a/ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs b/ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs index c2589db4df..5d71478fdd 100644 --- a/ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/FunctionPointerType.cs @@ -58,17 +58,22 @@ public static FunctionPointerType FromSignature(MethodSignature signature { 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) From a5ed5ec5cb9023e6e4b2d1b7c3585a63930d2022 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 19 Jul 2024 19:34:32 +0200 Subject: [PATCH 11/30] Support types that provide DisposeAsync without implementing IAsyncDisposable. --- .../IL/Transforms/UsingTransform.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs index 8a61d7931c..4315464f11 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs @@ -367,7 +367,9 @@ bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isRefere } disposeCall = cv; } - if (disposeCall.Method.FullName != disposeMethodFullName) + if (disposeCall.Method.IsStatic) + return false; + if (disposeCall.Method.Name != "DisposeAsync") return false; if (disposeCall.Method.Parameters.Count > 0) return false; @@ -505,9 +507,14 @@ bool UnwrapAwait(ref ILInstruction awaitInstruction) 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; } } From f2437bb0465ed1c7ed7ae25c715680123fbffde6 Mon Sep 17 00:00:00 2001 From: Andreas Weizel Date: Sat, 20 Jul 2024 00:34:14 +0200 Subject: [PATCH 12/30] Allow to collect analyzers annotated with ExportAnalyzerAttribute without MEF This helps users of ILSpyX not using MEF. Currently only ILSpyX assembly itself is searched for annotated analyzer types. --- .../Analyzers/ExportAnalyzerAttribute.cs | 15 +++++++ .../Analyzers/ExportAnalyzerAttributeTests.cs | 41 +++++++++++++++++++ ILSpy.Tests/ILSpy.Tests.csproj | 1 + 3 files changed, 57 insertions(+) create mode 100644 ILSpy.Tests/Analyzers/ExportAnalyzerAttributeTests.cs diff --git a/ICSharpCode.ILSpyX/Analyzers/ExportAnalyzerAttribute.cs b/ICSharpCode.ILSpyX/Analyzers/ExportAnalyzerAttribute.cs index fbd4b3a724..2f07859211 100644 --- a/ICSharpCode.ILSpyX/Analyzers/ExportAnalyzerAttribute.cs +++ b/ICSharpCode.ILSpyX/Analyzers/ExportAnalyzerAttribute.cs @@ -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,17 @@ public ExportAnalyzerAttribute() : base("Analyzer", typeof(IAnalyzer)) 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()) + { + var exportAnalyzerAttribute = type.GetCustomAttribute(typeof(ExportAnalyzerAttribute), false) as ExportAnalyzerAttribute; + if (exportAnalyzerAttribute is not null) + { + yield return (exportAnalyzerAttribute, type); + } + } + } } } diff --git a/ILSpy.Tests/Analyzers/ExportAnalyzerAttributeTests.cs b/ILSpy.Tests/Analyzers/ExportAnalyzerAttributeTests.cs new file mode 100644 index 0000000000..6c800da9de --- /dev/null +++ b/ILSpy.Tests/Analyzers/ExportAnalyzerAttributeTests.cs @@ -0,0 +1,41 @@ +// 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); + 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")); + } + } +} diff --git a/ILSpy.Tests/ILSpy.Tests.csproj b/ILSpy.Tests/ILSpy.Tests.csproj index 17337e7b24..0f44621f67 100644 --- a/ILSpy.Tests/ILSpy.Tests.csproj +++ b/ILSpy.Tests/ILSpy.Tests.csproj @@ -34,6 +34,7 @@ + From dab256ceb09cee2db975998a9081c76bff11b03d Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 20 Jul 2024 07:03:14 +0200 Subject: [PATCH 13/30] Update stackalloc initializer patterns for Roslyn 4.10.0. --- .../Transforms/TransformArrayInitializers.cs | 114 +++++++++++++++--- 1 file changed, 95 insertions(+), 19 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs index cc0027b0a9..7ddec2e564 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs @@ -243,7 +243,7 @@ bool DoTransformStackAllocInitializer(Block body, int pos) 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 @@ bool DoTransformStackAllocInitializer(Block body, int pos) 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 @@ bool HandleCpblkInitializer(Block block, int pos, ILVariable v, long length, out 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 @@ bool HandleCpblkInitializer(Block block, int pos, ILVariable v, long length, out 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 @@ bool HandleSequentialLocAllocInitializer(Block block, int pos, ILVariable store, 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().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 @@ ILInstruction RewrapStore(ILVariable target, StObj storeInstruction, IType type) 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 @@ bool MatchInitializeArrayCall(ILInstruction instruction, out ILInstruction array 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; From 427fbcd8c0d33f0b3fedbd87ba0358c337ec3d89 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 20 Jul 2024 09:10:05 +0200 Subject: [PATCH 14/30] Fix general using statement pattern --- .../IL/Transforms/UsingTransform.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs index 4315464f11..96fe3d13d6 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs @@ -369,8 +369,17 @@ bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isRefere } if (disposeCall.Method.IsStatic) return false; - if (disposeCall.Method.Name != "DisposeAsync") - 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) From fe879869c3a676ebbb9d1968f9ed385eae038a25 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 20 Jul 2024 09:15:49 +0200 Subject: [PATCH 15/30] ExportAnalyzerAttribute: Simplify pattern matching and avoid multiple enumeration --- ICSharpCode.ILSpyX/Analyzers/ExportAnalyzerAttribute.cs | 3 +-- ILSpy.Tests/Analyzers/ExportAnalyzerAttributeTests.cs | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.ILSpyX/Analyzers/ExportAnalyzerAttribute.cs b/ICSharpCode.ILSpyX/Analyzers/ExportAnalyzerAttribute.cs index 2f07859211..628b41bc8f 100644 --- a/ICSharpCode.ILSpyX/Analyzers/ExportAnalyzerAttribute.cs +++ b/ICSharpCode.ILSpyX/Analyzers/ExportAnalyzerAttribute.cs @@ -39,8 +39,7 @@ public ExportAnalyzerAttribute() : base("Analyzer", typeof(IAnalyzer)) { foreach (var type in typeof(ExportAnalyzerAttribute).Assembly.GetTypes()) { - var exportAnalyzerAttribute = type.GetCustomAttribute(typeof(ExportAnalyzerAttribute), false) as ExportAnalyzerAttribute; - if (exportAnalyzerAttribute is not null) + if (type.GetCustomAttribute(typeof(ExportAnalyzerAttribute), false) is ExportAnalyzerAttribute exportAnalyzerAttribute) { yield return (exportAnalyzerAttribute, type); } diff --git a/ILSpy.Tests/Analyzers/ExportAnalyzerAttributeTests.cs b/ILSpy.Tests/Analyzers/ExportAnalyzerAttributeTests.cs index 6c800da9de..c8e3af599f 100644 --- a/ILSpy.Tests/Analyzers/ExportAnalyzerAttributeTests.cs +++ b/ILSpy.Tests/Analyzers/ExportAnalyzerAttributeTests.cs @@ -30,7 +30,9 @@ public class ExportAnalyzerAttributeTests [Test] public void CollectAnalyzers() { - var analyzerNames = ExportAnalyzerAttribute.GetAnnotatedAnalyzers().Select(analyzer => analyzer.AnalyzerType.Name); + 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")); From cf5f10067e74fe688c0c2a47e50c17c66b68a1c3 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 20 Jul 2024 09:28:31 +0200 Subject: [PATCH 16/30] CallBuilder: Extract handling of interpolated string pattern into separate method. --- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 135 ++++++++++--------- 1 file changed, 73 insertions(+), 62 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 40778477e8..3ceeeaf60d 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -345,69 +345,11 @@ public ExpressionWithResolveResult Build(OpCode callOpCode, IMethod method, 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(); - - 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); @@ -482,6 +424,75 @@ void UnpackSingleElementArray(ref TranslatedExpression argument) 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(); + + 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)); + } + /// /// Converts a call to an Add method to a collection initializer expression. /// From 9d22e3ab41f5817c98262a6120b04eb7c41c7a68 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 20 Jul 2024 10:40:31 +0200 Subject: [PATCH 17/30] NullableLiftingTransform: Undo new compiler optimization (omitting get_HasValue for comparisions with constants) --- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 14 ++++++++++ .../Transforms/PatternStatementTransform.cs | 11 ++++++++ .../IL/Transforms/ExpressionTransforms.cs | 4 +-- .../IL/Transforms/NullableLiftingTransform.cs | 27 +++++++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 3ceeeaf60d..39cdba5381 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -373,6 +373,20 @@ public ExpressionWithResolveResult Build(OpCode callOpCode, IMethod method, 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); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs index f9a6050123..3605d968d7 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs @@ -1167,6 +1167,17 @@ public override AstNode VisitBinaryOperatorExpression(BinaryOperatorExpression e } 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) diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 5385bbaae6..b9b99ad427 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -96,8 +96,7 @@ protected internal override void VisitComp(Comp inst) 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,7 @@ protected internal override void VisitComp(Comp inst) inst.Left.AcceptVisitor(this); return; } + new NullableLiftingTransform(context).Run(inst); base.VisitComp(inst); if (inst.IsLifted) diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs index 4e297b00d9..3d517e4633 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs @@ -85,6 +85,33 @@ public bool Run(BinaryNumericInstruction bni) return false; } + /// + /// VS2022.10 / Roslyn 4.10.0 adds an optimization that turns + /// a == 42 into a.GetValueOrDefault() == 42 without any HasValue check. + /// + 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.: From 28aa88bfb8085ef075cbfad8ae1b4aa6627c0f5a Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 20 Jul 2024 11:58:00 +0200 Subject: [PATCH 18/30] Make NullableLiftingTransform handle the case where ExpressionTransforms.VisitComp already lifted a nullable comparison with constant. --- .../IL/Transforms/ExpressionTransforms.cs | 5 ++- .../IL/Transforms/NullableLiftingTransform.cs | 31 ++++++++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index b9b99ad427..c3b464399e 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -106,7 +106,10 @@ protected internal override void VisitComp(Comp inst) inst.Left.AcceptVisitor(this); return; } - new NullableLiftingTransform(context).Run(inst); + if (context.Settings.LiftNullables) + { + new NullableLiftingTransform(context).Run(inst); + } base.VisitComp(inst); if (inst.IsLifted) diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs index 3d517e4633..111cac3ce5 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs @@ -225,14 +225,14 @@ ILInstruction Lift(ILInstruction ifInst, ILInstruction condition, ILInstruction 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() : default(T) // => Activator.CreateInstance() return trueInst; } } - else + else if (!comp.IsLifted) { // Not (in)equality, but one of < <= > >=. // Returns false unless all HasValue bits are true. @@ -401,11 +401,12 @@ static bool MatchCompOrDecimal(ILInstruction inst, out CompOrDecimal result) { 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) @@ -449,6 +450,7 @@ struct CompOrDecimal public ComparisonKind Kind; public ILInstruction Left; public ILInstruction Right; + public bool IsLifted; public IType LeftExpectedType { get { @@ -528,6 +530,8 @@ ILInstruction LiftCSharpEqualityComparison(CompOrDecimal valueComp, ComparisonKi // 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; @@ -576,6 +580,24 @@ ILInstruction LiftCSharpEqualityComparison(CompOrDecimal valueComp, ComparisonKi /// 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()) @@ -611,7 +633,8 @@ ILInstruction LiftCSharpUserEqualityComparison(CompOrDecimal hasValueComp, Compa // 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)) From e6004e5f26fe4bbb2e92873ad2c8fa3d9754e67b Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 20 Jul 2024 15:17:10 +0200 Subject: [PATCH 19/30] Handle ReferenceKind.RefReadOnly in a few more cases --- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 3 +-- ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs | 4 ++-- ICSharpCode.Decompiler/Semantics/ByReferenceResolveResult.cs | 3 --- .../TypeSystem/Implementation/MetadataParameter.cs | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 39cdba5381..c389a0c796 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -134,8 +134,7 @@ Expression AddAnnotations(Expression expression) { 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); } diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs b/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs index 09fd186f75..bfeb1bbd25 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs @@ -671,8 +671,8 @@ void CheckApplicability(Candidate candidate) { // 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 diff --git a/ICSharpCode.Decompiler/Semantics/ByReferenceResolveResult.cs b/ICSharpCode.Decompiler/Semantics/ByReferenceResolveResult.cs index 215aaa3691..3c4828d4fc 100644 --- a/ICSharpCode.Decompiler/Semantics/ByReferenceResolveResult.cs +++ b/ICSharpCode.Decompiler/Semantics/ByReferenceResolveResult.cs @@ -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; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs index 40b4e1f061..14bee08dc5 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs @@ -63,7 +63,7 @@ public IEnumerable GetAttributes() 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)) { From fd1de09489ae92c596e0b5d90f574871b6a1f043 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 20 Jul 2024 15:20:53 +0200 Subject: [PATCH 20/30] Avoid using operator + for string concatenation when ref-like types are involved. --- .../CSharp/Transforms/ReplaceMethodCallsWithOperators.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs index cf9970232f..36624535b5 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs @@ -274,7 +274,8 @@ bool CheckArgumentsForStringConcat(Expression[] arguments) } 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 @@ bool CheckArgumentsForStringConcat(Expression[] arguments) // 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 From 03a20f3494b7eec7871c1da09e74b7b6e576bfea Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 20 Jul 2024 17:51:45 +0200 Subject: [PATCH 21/30] Support ILInlining for in parameters --- .../TestCases/Pretty/ValueTypes.cs | 19 +++++++++++++++++++ ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 17 +++++++++++++---- .../CSharp/ExpressionBuilder.cs | 10 +++++++--- .../IL/Transforms/ILInlining.cs | 17 ++++++++++++++--- 4 files changed, 53 insertions(+), 10 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs index c40e55f4c0..87f54d52f7 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs @@ -291,5 +291,24 @@ public static void InliningDefaultValue() 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 } } \ No newline at end of file diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index c389a0c796..042ca5bc8d 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -867,7 +867,7 @@ private ArgumentList BuildArgumentList(ExpectedTargetDetails expectedTargetDetai if (parameter.ReferenceKind != ReferenceKind.None) { - arg = ExpressionBuilder.ChangeDirectionExpressionTo(arg, parameter.ReferenceKind); + arg = ExpressionBuilder.ChangeDirectionExpressionTo(arg, parameter.ReferenceKind, callArguments[i] is AddressOf); } arguments.Add(arg); @@ -1179,14 +1179,20 @@ private void CastArguments(IList arguments, IList FieldDirection.Ref, ReferenceKind.Out => FieldDirection.Out, @@ -4454,7 +4458,7 @@ protected internal override TranslatedExpression VisitCallIndirect(CallIndirect 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); } diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index fc15ebc3ae..416972039a 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -244,7 +244,7 @@ static bool DoInline(ILVariable v, ILInstruction inlinedExpression, ILInstructio 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 @@ static bool DoInline(ILVariable v, ILInstruction inlinedExpression, ILInstructio /// /// The load instruction (a descendant within 'next') /// The variable being inlined. - 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)) @@ -301,7 +301,7 @@ static bool IsGeneratedValueTypeTemporary(LdLoca loadInst, ILVariable v, ILInstr // Thus, we have to ensure we're operating on an r-value. // Additionally, we cannot inline in cases where the C# compiler prohibits the direct use // of the rvalue (e.g. M(ref (MyStruct)obj); is invalid). - if (IsUsedAsThisPointerInCall(loadInst, out var method, out var constrainedTo)) + if (IsUsedAsThisPointerInCall(loadInst, out var method, out var constrainedTo) || IsPassedToInParameter(loadInst, out method)) { if (options.HasFlag(InliningOptions.Aggressive)) { @@ -415,6 +415,17 @@ static bool IsUsedAsThisPointerInFieldRead(LdLoca ldloca) return inst != ldloca && inst.Parent is LdObj; } + static bool IsPassedToInParameter(LdLoca ldloca, out IMethod method) + { + method = null; + if (ldloca.Parent is not CallInstruction call) + { + return false; + } + method = call.Method; + return call.GetParameter(ldloca.ChildIndex)?.ReferenceKind is ReferenceKind.In; + } + /// /// Gets whether the instruction, when converted into C#, turns into an l-value that can /// be used to mutate a value-type. From 9548a11d371d743df6a6bf48e39a55b9876fc308 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 20 Jul 2024 20:24:31 +0200 Subject: [PATCH 22/30] Use ILSpyHelper_AsRefReadOnly to ensure that overload resolution can pick the correct overload using 'in'. --- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 37 ++++++++++++++++++- .../CSharp/StatementBuilder.cs | 28 ++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 042ca5bc8d..adc8afa716 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -971,7 +971,11 @@ enum CallTransformation RequireTarget = 1, RequireTypeArguments = 2, NoOptionalArgumentAllowed = 4, - All = 7 + /// + /// Add calls to AsRefReadOnly for in parameters that did not have an explicit DirectionExpression yet. + /// + EnforceExplicitIn = 8, + All = 0xf, } private CallTransformation GetRequiredTransformationsForCall(ExpectedTargetDetails expectedTargetDetails, IMethod method, @@ -1122,6 +1126,11 @@ private CallTransformation GetRequiredTransformationsForCall(ExpectedTargetDetai requireTypeArguments = true; typeArguments = method.TypeArguments.ToArray(); } + else if ((allowedTransforms & CallTransformation.EnforceExplicitIn) != 0) + { + EnforceExplicitIn(argumentList.Arguments, argumentList.ExpectedParameters); + allowedTransforms &= ~CallTransformation.EnforceExplicitIn; + } else { break; @@ -1141,6 +1150,32 @@ private CallTransformation GetRequiredTransformationsForCall(ExpectedTargetDetai 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 arguments) { return method.IsExtensionMethod && arguments.Count > 0 && arguments[0].Expression is NullReferenceExpression; diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index daa2cbe7fc..4dfc5f6583 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -47,6 +47,8 @@ sealed class StatementBuilder : ILVisitor 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 @@ BlockStatement ConvertBlockContainer(BlockContainer container, bool isLoop) { 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; } From 1616d155722f693d5820eae3cd0ff9b02c297aa2 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 20 Jul 2024 17:13:31 +0200 Subject: [PATCH 23/30] Move roundtrip tests to their own namespace. This makes it easier to run the fast non-roundtrip tests. --- ICSharpCode.Decompiler.Tests/Helpers/Tester.cs | 2 +- ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 709bf4cae3..bdea4570fb 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -528,7 +528,7 @@ public static async Task CompileCSharp(string sourceFileName, C { 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 + diff --git a/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs b/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs index 3043c92466..24d61e0811 100644 --- a/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs +++ b/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs @@ -33,7 +33,7 @@ using NUnit.Framework; -namespace ICSharpCode.Decompiler.Tests +namespace ICSharpCode.Decompiler.Tests.Roundtrip { [TestFixture, Parallelizable(ParallelScope.All)] public class RoundtripAssembly From 1c71f6ad46b333a06ae3f9e88597afec20d144b7 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 21 Jul 2024 10:33:09 +0200 Subject: [PATCH 24/30] Support concatenation involving arguments of type ReadOnlySpan. --- .../RoundtripAssembly.cs | 3 +- .../TestCases/Pretty/StringInterpolation.cs | 25 +++++++ ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 66 +++++++++++++++++++ .../IL/Transforms/ILInlining.cs | 58 ++++++++++++++-- 4 files changed, 147 insertions(+), 5 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs b/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs index 24d61e0811..de1639cf03 100644 --- a/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs +++ b/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs @@ -29,11 +29,12 @@ 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.Roundtrip +namespace ICSharpCode.Decompiler.Roundtrip { [TestFixture, Parallelizable(ParallelScope.All)] public class RoundtripAssembly diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StringInterpolation.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StringInterpolation.cs index fc667d2750..0dbbc9064b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StringInterpolation.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StringInterpolation.cs @@ -117,5 +117,30 @@ public void RequiresCast(FormattableString value) 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; + } } } diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index adc8afa716..1926a74dc4 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -223,10 +223,76 @@ public TranslatedExpression Build(CallInstruction inst, IType typeHint = null) 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; + } + + private bool IsSpanBasedStringConcat(CallInstruction call, out List<(ILInstruction, KnownTypeCode)> operands) + { + operands = null; + + if (call.Method is not { Name: "Concat", IsStatic: true }) + { + return false; + } + if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.String)) + { + 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; + } + + private 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 callArguments, IReadOnlyList argumentToParameterMap = null, diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index 416972039a..49ff62cc27 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -301,7 +301,7 @@ static bool IsGeneratedTemporaryForAddressOf(LdLoca loadInst, ILVariable v, ILIn // Thus, we have to ensure we're operating on an r-value. // Additionally, we cannot inline in cases where the C# compiler prohibits the direct use // of the rvalue (e.g. M(ref (MyStruct)obj); is invalid). - if (IsUsedAsThisPointerInCall(loadInst, out var method, out var constrainedTo) || IsPassedToInParameter(loadInst, out method)) + if (IsUsedAsThisPointerInCall(loadInst, out var method, out var constrainedTo)) { if (options.HasFlag(InliningOptions.Aggressive)) { @@ -326,6 +326,39 @@ static bool IsGeneratedTemporaryForAddressOf(LdLoca loadInst, ILVariable v, ILIn 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,17 +448,34 @@ static bool IsUsedAsThisPointerInFieldRead(LdLoca ldloca) return inst != ldloca && inst.Parent is LdObj; } - static bool IsPassedToInParameter(LdLoca ldloca, out IMethod method) + static bool IsPassedToInParameter(LdLoca ldloca) { - method = null; if (ldloca.Parent is not CallInstruction call) { return false; } - method = call.Method; 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); + } + /// /// Gets whether the instruction, when converted into C#, turns into an l-value that can /// be used to mutate a value-type. From 7b1f8a305cb3815a0193845cf1474d65088985b2 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 21 Jul 2024 11:18:13 +0200 Subject: [PATCH 25/30] Implemented support for string concatenation compound assignments involving ReadOnlySpan. --- .../Pretty/CompoundAssignmentTest.cs | 4 ++- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 32 +++++++++++++++---- .../CSharp/ExpressionBuilder.cs | 31 +++++++++++++++--- .../IL/Transforms/TransformAssignment.cs | 13 +++++++- 4 files changed, 67 insertions(+), 13 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs index 1cc74865c3..34275f2523 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs @@ -4719,12 +4719,14 @@ private static void Issue1082(string[] strings, List chars, bool flag, int } #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) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 1926a74dc4..56f420e076 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -247,15 +247,11 @@ private ExpressionWithResolveResult BuildStringConcat(IMethod method, List<(ILIn return result; } - private bool IsSpanBasedStringConcat(CallInstruction call, out List<(ILInstruction, KnownTypeCode)> operands) + static bool IsSpanBasedStringConcat(CallInstruction call, out List<(ILInstruction, KnownTypeCode)> operands) { operands = null; - if (call.Method is not { Name: "Concat", IsStatic: true }) - { - return false; - } - if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.String)) + if (!IsSpanBasedStringConcat(call.Method)) { return false; } @@ -283,7 +279,29 @@ private bool IsSpanBasedStringConcat(CallInstruction call, out List<(ILInstructi return call.Arguments.Count >= 2 && firstStringArgumentIndex <= 1; } - private bool IsStringToReadOnlySpanCharImplicitConversion(IMethod method) + 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" diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index e8df339dac..0f629f38e0 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1799,7 +1799,17 @@ TranslatedExpression HandleShift(BinaryNumericInstruction inst, BinaryOperatorTy 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 @@ protected internal override TranslatedExpression VisitUserDefinedCompoundAssign( 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) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs index a24783d214..b5530fb70d 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs @@ -21,6 +21,7 @@ using System.Linq; using System.Linq.Expressions; +using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; @@ -452,7 +453,17 @@ static ExpressionType ToCompound(ExpressionType from) 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); From 67a0bfb051ad16c1af76d32686b8f82026ffa5ba Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 21 Jul 2024 11:19:22 +0200 Subject: [PATCH 26/30] Update to Roslyn 4.10.0 --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index f70e32fbcd..a99f48fccb 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -16,8 +16,8 @@ - - + + From 82256c47fb9bbab59314e12c4b8a000a925d5059 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 21 Jul 2024 11:36:01 +0200 Subject: [PATCH 27/30] Add test case for C# 12 optional parameters in lambdas. --- .../TestCases/Pretty/OptionalArguments.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs index f68b896d09..82cda462be 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs @@ -24,6 +24,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { internal class OptionalArguments : List { + public delegate int D(int p = 10); + public enum MyEnum { A, @@ -283,5 +285,18 @@ public static void Issue2920p(in int x = 3) { } #endif + +#if CS120 + public static D LambdaWithOptionalParameter() + { + return (int x = 10) => x; + } + + public static void Use(D d) + { + d(); + d(42); + } +#endif } } From 5a665185816413b3ae1a0f655c45c16f9b205350 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 21 Jul 2024 11:51:55 +0200 Subject: [PATCH 28/30] Add more tests for C# 12 ref readonly parameters --- .../Correctness/OverloadResolution.cs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs index e206872a5f..50925bddda 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs @@ -36,6 +36,7 @@ static void Main() Issue1747(); CallAmbiguousOutParam(); CallWithInParam(); + CallWithRefReadOnlyParam(); #if CS90 NativeIntTests(new IntPtr(1), 2); #endif @@ -277,6 +278,73 @@ static void CallAmbiguousOutParam() } #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(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 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() { From aa914058ceefa36c3a2bfcb293128e5be6b155a7 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 21 Jul 2024 12:32:58 +0200 Subject: [PATCH 29/30] Fix #3237: Use `ref readonly` locals for `readonly.ldelema` --- ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs | 3 +++ .../Transforms/IntroduceRefReadOnlyModifierOnLocals.cs | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index 49ff62cc27..7b38b2e907 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -537,6 +537,9 @@ internal static ExpressionClassification ClassifyExpression(ILInstruction inst) } } + /// + /// Gets whether the ILInstruction will turn into a C# expresion that is considered readonly by the C# compiler. + /// internal static bool IsReadonlyReference(ILInstruction addr) { switch (addr) diff --git a/ICSharpCode.Decompiler/IL/Transforms/IntroduceRefReadOnlyModifierOnLocals.cs b/ICSharpCode.Decompiler/IL/Transforms/IntroduceRefReadOnlyModifierOnLocals.cs index 946f153635..aba272b1f0 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/IntroduceRefReadOnlyModifierOnLocals.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/IntroduceRefReadOnlyModifierOnLocals.cs @@ -52,8 +52,18 @@ bool IsUsedAsRefReadonly(ILVariable variable) { foreach (var store in variable.StoreInstructions.OfType()) { + // 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; } From f037eb6ac4b39956c5864d51a2c3f426ec906c9a Mon Sep 17 00:00:00 2001 From: Christoph Wille Date: Mon, 22 Jul 2024 18:51:34 +0200 Subject: [PATCH 30/30] Fix .NET version mixup in instructions for building on Windows --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index adb3b2c8e7..2374149636 100644 --- a/README.md +++ b/README.md @@ -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 - 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: