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;