diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 276b5bbdb..0c67e1035 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -105,6 +105,7 @@ namespace ICSharpCode.Decompiler.CSharp new SwitchOnStringTransform(), new SwitchOnNullableTransform(), new SplitVariables(), // split variables once again, because SwitchOnNullableTransform eliminates ldloca + new IntroduceRefReadOnlyModifierOnLocals(), new BlockILTransform { // per-block transforms PostOrderTransforms = { // Even though it's a post-order block-transform as most other transforms, diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index a3b5168c5..41600de3d 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -447,6 +447,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } else { type = context.TypeSystemAstBuilder.ConvertType(v.Type); } + if (v.ILVariable.IsRefReadOnly && type is ComposedType composedType && composedType.HasRefSpecifier) { + composedType.HasReadOnlySpecifier = true; + } var vds = new VariableDeclarationStatement(type, v.Name, assignment.Right.Detach()); var init = vds.Variables.Single(); init.AddAnnotation(assignment.Left.GetResolveResult()); diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 2b37941d5..9f2c10d1b 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -328,6 +328,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index f840dce25..cef0c023a 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -190,12 +190,15 @@ namespace ICSharpCode.Decompiler.IL // and needs to be converted into a normally usable type. declaringType = new ParameterizedType(declaringType, declaringType.TypeParameters); } - parameterVariables[paramIndex++] = CreateILVariable(-1, declaringType, "this"); + ILVariable ilVar = CreateILVariable(-1, declaringType, "this"); + ilVar.IsRefReadOnly = declaringType.GetDefinition()?.IsReadOnly == true; + parameterVariables[paramIndex++] = ilVar; } while (paramIndex < parameterVariables.Length) { - IType type = method.Parameters[paramIndex - offset].Type; - string name = method.Parameters[paramIndex - offset].Name; - parameterVariables[paramIndex] = CreateILVariable(paramIndex - offset, type, name); + IParameter parameter = method.Parameters[paramIndex - offset]; + ILVariable ilVar = CreateILVariable(paramIndex - offset, parameter.Type, parameter.Name); + ilVar.IsRefReadOnly = parameter.IsIn; + parameterVariables[paramIndex] = ilVar; paramIndex++; } Debug.Assert(paramIndex == parameterVariables.Length); diff --git a/ICSharpCode.Decompiler/IL/ILVariable.cs b/ICSharpCode.Decompiler/IL/ILVariable.cs index 4d3fca31d..60423625c 100644 --- a/ICSharpCode.Decompiler/IL/ILVariable.cs +++ b/ICSharpCode.Decompiler/IL/ILVariable.cs @@ -131,6 +131,11 @@ namespace ICSharpCode.Decompiler.IL type = value; } } + + /// + /// This variable is either a C# 7 'in' parameter or must be declared as 'ref readonly'. + /// + public bool IsRefReadOnly { get; internal set; } /// /// The index of the local variable or parameter (depending on Kind) @@ -371,6 +376,9 @@ namespace ICSharpCode.Decompiler.IL internal void WriteDefinitionTo(ITextOutput output) { + if (IsRefReadOnly) { + output.Write("readonly "); + } switch (Kind) { case VariableKind.Local: output.Write("local "); diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index db8c47fea..c1a5de160 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -329,7 +329,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms switch (inst.OpCode) { case OpCode.LdLoc: case OpCode.StLoc: - if (IsReadonlyRefLocal(((IInstructionWithVariableOperand)inst).Variable)) { + if (((IInstructionWithVariableOperand)inst).Variable.IsRefReadOnly) { return ExpressionClassification.ReadonlyLValue; } else { return ExpressionClassification.MutableLValue; @@ -363,7 +363,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } - private static bool IsReadonlyReference(ILInstruction addr) + internal static bool IsReadonlyReference(ILInstruction addr) { switch (addr) { case LdFlda ldflda: @@ -372,7 +372,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms case LdsFlda ldsflda: return ldsflda.Field.IsReadOnly; case LdLoc ldloc: - return IsReadonlyRefLocal(ldloc.Variable); + return ldloc.Variable.IsRefReadOnly; case Call call: return call.Method.ReturnTypeIsRefReadOnly; case AddressOf _: @@ -383,19 +383,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } - private static bool IsReadonlyRefLocal(ILVariable variable) - { - if (variable.Kind == VariableKind.Parameter) { - if (variable.Index == -1) { - // this parameter in readonly struct - return variable.Function.Method?.DeclaringTypeDefinition?.IsReadOnly == true; - } else { - return variable.Function.Parameters[variable.Index.Value].IsIn; - } - } - return false; - } - /// /// Determines whether a variable should be inlined in non-aggressive mode, even though it is not a generated variable. /// diff --git a/ICSharpCode.Decompiler/IL/Transforms/IntroduceRefReadOnlyModifierOnLocals.cs b/ICSharpCode.Decompiler/IL/Transforms/IntroduceRefReadOnlyModifierOnLocals.cs new file mode 100644 index 000000000..d75d3096b --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/IntroduceRefReadOnlyModifierOnLocals.cs @@ -0,0 +1,57 @@ +// Copyright (c) 2018 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. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ICSharpCode.Decompiler.IL.Transforms; +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.IL +{ + class IntroduceRefReadOnlyModifierOnLocals : IILTransform + { + public void Run(ILFunction function, ILTransformContext context) + { + foreach (var variable in function.Variables) { + if (variable.Type.Kind != TypeKind.ByReference || variable.Kind == VariableKind.Parameter) + continue; + // ref readonly + if (IsUsedAsRefReadonly(variable)) { + variable.IsRefReadOnly = true; + continue; + } + } + } + + /// + /// Infer ref readonly type from usage: + /// An ILVariable should be marked as readonly, + /// if it's a "by-ref-like" type and the initialized value is known to be readonly. + /// + bool IsUsedAsRefReadonly(ILVariable variable) + { + foreach (var store in variable.StoreInstructions.OfType()) { + if (ILInlining.IsReadonlyReference(store.Value)) + return true; + } + return false; + } + } +} diff --git a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs index ddd989831..66fdacbe6 100644 --- a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs +++ b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs @@ -194,7 +194,6 @@ namespace ICSharpCode.ILSpy case "event": case "extern": case "override": - case "readonly": case "sealed": case "static": case "virtual": @@ -203,6 +202,12 @@ namespace ICSharpCode.ILSpy case "partial": color = modifiersColor; break; + case "readonly": + if (role == ComposedType.ReadonlyRole) + color = parameterModifierColor; + else + color = modifiersColor; + break; case "checked": case "unchecked": color = checkedKeywordColor;