diff --git a/BuildTools/appveyor-install.ps1 b/BuildTools/appveyor-install.ps1 index db3950f43..0b922deb8 100644 --- a/BuildTools/appveyor-install.ps1 +++ b/BuildTools/appveyor-install.ps1 @@ -4,7 +4,7 @@ $baseCommit = "d779383cb85003d6dabeb976f0845631e07bf463"; $baseCommitRev = 1; # make sure this list matches artifacts-only branches list in appveyor.yml! -$masterBranches = @("master", "3.2.x"); +$masterBranches = @("master", "5.0.x"); $globalAssemblyInfoTemplateFile = "ILSpy/Properties/AssemblyInfo.template.cs"; diff --git a/BuildTools/pipelines-install.ps1 b/BuildTools/pipelines-install.ps1 index 31d5b634b..4e3dda35e 100644 --- a/BuildTools/pipelines-install.ps1 +++ b/BuildTools/pipelines-install.ps1 @@ -4,7 +4,7 @@ $baseCommit = "d779383cb85003d6dabeb976f0845631e07bf463"; $baseCommitRev = 1; # make sure this list matches artifacts-only branches list in azure-pipelines.yml! -$masterBranches = @("master", "3.2.x"); +$masterBranches = @("master", "5.0.x"); $globalAssemblyInfoTemplateFile = "ILSpy/Properties/AssemblyInfo.template.cs"; diff --git a/BuildTools/update-assemblyinfo.ps1 b/BuildTools/update-assemblyinfo.ps1 index d901e7fea..a716d15a7 100644 --- a/BuildTools/update-assemblyinfo.ps1 +++ b/BuildTools/update-assemblyinfo.ps1 @@ -4,7 +4,7 @@ $baseCommit = "d779383cb85003d6dabeb976f0845631e07bf463"; $baseCommitRev = 1; # make sure this list matches artifacts-only branches list in appveyor.yml! -$masterBranches = @("master", "3.2.x"); +$masterBranches = @("master", "5.0.x"); $globalAssemblyInfoTemplateFile = "ILSpy/Properties/AssemblyInfo.template.cs"; diff --git a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs index 6d75516da..4ad298c7c 100644 --- a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs @@ -280,6 +280,12 @@ namespace ICSharpCode.Decompiler.Tests RunCS(options: options); } + [Test] + public void StringConcat([ValueSource("defaultOptions")] CompilerOptions options) + { + RunCS(options: options); + } + [Test] public void MiniJSON([ValueSource("defaultOptions")] CompilerOptions options) { diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 250b2f1ef..f17a64a52 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -224,6 +224,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers preprocessorSymbols.Add("CS71"); preprocessorSymbols.Add("CS72"); preprocessorSymbols.Add("CS73"); + preprocessorSymbols.Add("CS80"); preprocessorSymbols.Add("VB11"); preprocessorSymbols.Add("VB14"); preprocessorSymbols.Add("VB15"); diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 1f7b27189..0d2436bf4 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -42,8 +42,8 @@ - - + + @@ -79,9 +79,12 @@ + + + diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/FloatingPointArithmetic.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/FloatingPointArithmetic.cs index f057f77bb..72c0bbc72 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/FloatingPointArithmetic.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/FloatingPointArithmetic.cs @@ -7,6 +7,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness public static int Main(string[] args) { Issue999(); + Issue1656(); return 0; } @@ -20,5 +21,21 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness { return 0.99f * v + 0.01f; } + + static void Issue1656() + { + double primary = 'B'; + CxAssert((++primary) == 'C'); + CxAssert((--primary) == 'B'); + CxAssert((primary++) == 'B'); + CxAssert((primary--) == 'C'); + } + + static void CxAssert(bool v) + { + if (!v) { + throw new InvalidOperationException(); + } + } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NullableTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NullableTests.cs index f4a3ed8d7..6659f1503 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NullableTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NullableTests.cs @@ -23,6 +23,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness class NullableTests { static void Main() + { + AvoidLifting(); + BitNot(); + } + + static void AvoidLifting() { Console.WriteLine("MayThrow:"); Console.WriteLine(MayThrow(10, 2, 3)); @@ -33,8 +39,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness Console.WriteLine(NotUsingAllInputs(5, null)); Console.WriteLine("UsingUntestedValue:"); - Console.WriteLine(NotUsingAllInputs(5, 3)); - Console.WriteLine(NotUsingAllInputs(5, null)); + Console.WriteLine(UsingUntestedValue(5, 3)); + Console.WriteLine(UsingUntestedValue(5, null)); } static int? MayThrow(int? a, int? b, int? c) @@ -54,5 +60,27 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness // cannot be lifted because the value differs if b == null return a.HasValue ? a.GetValueOrDefault() + b.GetValueOrDefault() : default(int?); } + + static void BitNot() + { + UInt32? value = 0; + Assert(~value == UInt32.MaxValue); + UInt64? value2 = 0; + Assert(~value2 == UInt64.MaxValue); + UInt16? value3 = 0; + Assert((UInt16)~value3 == (UInt16)UInt16.MaxValue); + UInt32 value4 = 0; + Assert(~value4 == UInt32.MaxValue); + UInt64 value5 = 0; + Assert(~value5 == UInt64.MaxValue); + UInt16 value6 = 0; + Assert((UInt16)~value6 == UInt16.MaxValue); + } + + static void Assert(bool b) + { + if (!b) + throw new InvalidOperationException(); + } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StringConcat.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StringConcat.cs new file mode 100644 index 000000000..ebe8de7ce --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StringConcat.cs @@ -0,0 +1,117 @@ +using System; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness +{ + class StringConcat + { + private class C + { + int i; + + public C(int i) + { + Console.WriteLine(" new C(" + i + ")"); + this.i = i; + } + + public override string ToString() + { + Console.WriteLine(" C(" + i + ").ToString()"); + return (i++).ToString(); + } + } + + private struct S + { + int i; + + public S(int i) + { + Console.WriteLine(" new C(" + i + ")"); + this.i = i; + } + + public override string ToString() + { + Console.WriteLine(" S(" + i + ").ToString()"); + return (i++).ToString(); + } + } + + static string Space() + { + Console.WriteLine(" Space()"); + return " "; + } + + static void TestClass() + { + Console.WriteLine("string + C:"); + Console.WriteLine(Space() + new C(1)); + + Console.WriteLine("C + string:"); + Console.WriteLine(new C(2) + Space()); + + Console.WriteLine("C + string + C:"); + Console.WriteLine(new C(3) + Space() + new C(4)); + + Console.WriteLine("string + C + C:"); + Console.WriteLine(Space() + new C(5) + new C(6)); + + Console.WriteLine("string.Concat(C, string, C):"); + Console.WriteLine(string.Concat(new C(10), Space(), new C(11))); + + Console.WriteLine("string.Concat(string.Concat(C, string), C):"); + Console.WriteLine(string.Concat(string.Concat(new C(15), Space()), new C(16))); + + Console.WriteLine("string.Concat(C, string.Concat(string, C)):"); + Console.WriteLine(string.Concat(new C(20), string.Concat(Space(), new C(21)))); + + Console.WriteLine("string.Concat(C, string) + C:"); + Console.WriteLine(string.Concat(new C(30), Space()) + new C(31)); + } + + static void TestStruct() + { + Console.WriteLine("string + S:"); + Console.WriteLine(Space() + new S(1)); + + Console.WriteLine("S + string:"); + Console.WriteLine(new S(2) + Space()); + + Console.WriteLine("S + string + S:"); + Console.WriteLine(new S(3) + Space() + new S(4)); + + Console.WriteLine("string + S + S:"); + Console.WriteLine(Space() + new S(5) + new S(6)); + + Console.WriteLine("string.Concat(S, string, S):"); + Console.WriteLine(string.Concat(new S(10), Space(), new S(11))); + + Console.WriteLine("string.Concat(string.Concat(S, string), S):"); + Console.WriteLine(string.Concat(string.Concat(new S(15), Space()), new S(16))); + + Console.WriteLine("string.Concat(S, string.Concat(string, S)):"); + Console.WriteLine(string.Concat(new S(20), string.Concat(Space(), new S(21)))); + + Console.WriteLine("string.Concat(S, string) + S:"); + Console.WriteLine(string.Concat(new S(30), Space()) + new S(31)); + } + + static void TestStructMutation() + { + Console.WriteLine("TestStructMutation:"); + S s = new S(0); + Console.WriteLine(Space() + s); + Console.WriteLine(Space() + s.ToString()); + Console.WriteLine(s); + } + + static void Main() + { + TestClass(); + TestStruct(); + TestStructMutation(); + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CS1xSwitch_Debug.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CS1xSwitch_Debug.cs index d927e3fd0..419acf191 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CS1xSwitch_Debug.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CS1xSwitch_Debug.cs @@ -270,7 +270,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static string SwitchOverBool(bool b) { - Console.WriteLine("SwitchOverBool: " + b.ToString()); + Console.WriteLine("SwitchOverBool: " + b); switch (b) { case true: return bool.TrueString; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CS1xSwitch_Release.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CS1xSwitch_Release.cs index d927e3fd0..419acf191 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CS1xSwitch_Release.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CS1xSwitch_Release.cs @@ -270,7 +270,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static string SwitchOverBool(bool b) { - Console.WriteLine("SwitchOverBool: " + b.ToString()); + Console.WriteLine("SwitchOverBool: " + b); switch (b) { case true: return bool.TrueString; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs index 914c94a4e..b4ab3c3d3 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs @@ -36,7 +36,7 @@ public static class FSharpUsingPatterns num = fileStream.ReadByte(); } int num2 = num; - Console.WriteLine("read:" + num2.ToString()); + Console.WriteLine("read:" + num2); } public static void sample5() diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs index 2a9e58dd9..355a18d58 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs @@ -36,7 +36,7 @@ public static class FSharpUsingPatterns num = fileStream.ReadByte(); } int num2 = num; - Console.WriteLine("read:" + num2.ToString()); + Console.WriteLine("read:" + num2); } public static void sample5() diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs index 9f6b5c58a..7d51bc732 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs @@ -167,6 +167,24 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty }; } + public static async Task AlwaysThrow() + { + throw null; + } + + public static async Task InfiniteLoop() + { + while (true) { + } + } + + public static async Task InfiniteLoopWithAwait() + { + while (true) { + await Task.Delay(10); + } + } + #if CS70 public static async Task AsyncLocalFunctions() { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs index 109831418..97ad7b41f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs @@ -4562,9 +4562,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty // same temporary. In order to inline the generated value-type temporary, we // need to split it, even though it has the address taken for the ToString() call. if (flag) { - strings[1] += chars[i].ToString(); + strings[1] += chars[i]; } else { - strings[0] += chars[i].ToString(); + strings[0] += chars[i]; } } #endif diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs index 65309ac46..80164a385 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs @@ -1,4 +1,6 @@ -using System; +#pragma warning disable 1998 + +using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs index e05e5bd91..0704d886b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs @@ -1026,6 +1026,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty }); } + public static void StringConcat() + { + Test>(null, (string a, object b) => a + b); + Test>(null, (string a, object b) => a + b.ToString()); + Test>(null, (string a, int b) => a + b); + Test>(null, (string a, int b) => a + b.ToString()); + } + public async Task Issue1524(string str) { await Task.Delay(100); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InterfaceTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InterfaceTests.cs index 2b4167c32..d26c1fb68 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InterfaceTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InterfaceTests.cs @@ -16,21 +16,101 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +// We can't test this because "error CS8701: Target runtime doesn't support default interface implementation." +#undef CS80 + +using System; + namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { internal class InterfaceTests { public interface IA { + int Property1 { + get; + } + int Property2 { + set; + } + int Property3 { + get; + set; + } + + event EventHandler MyEvent; + void Method(); + +#if CS80 + void DefaultMethod() + { + Method(); + PrivateMethod(); + } + + private void PrivateMethod() + { + Method(); + } + + internal void InternalMethod() + { + Method(); + } + + sealed void SealedMethod() + { + Method(); + } + + static void StaticMethod() + { + + } +#endif } public interface IA2 : IA { +#if CS80 + void IA.InternalMethod() + { + } +#endif } public interface IB { } public class C : IA2, IA, IB { + int IA.Property1 { + get { + throw new NotImplementedException(); + } + } + int IA.Property2 { + set { + throw new NotImplementedException(); + } + } + int IA.Property3 { + get { + throw new NotImplementedException(); + } + set { + throw new NotImplementedException(); + } + } + + event EventHandler IA.MyEvent { + add { + } + remove { + } + } + void IA.Method() + { + throw new NotImplementedException(); + } } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index 581f6eac6..91c58bacd 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -310,6 +310,17 @@ namespace LocalFunctions } } + public void WriteCapturedParameter(int i) + { + ParamWrite(); + Console.WriteLine(i); + + void ParamWrite() + { + i++; + } + } + //public static void LocalFunctionInUsing() //{ // using (MemoryStream memoryStream = new MemoryStream()) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs index 166db754e..220bd873d 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs @@ -401,7 +401,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty Console.WriteLine("MoveNext"); if (enumerator.MoveNext()) { object current = enumerator.Current; - Console.WriteLine("current: " + current); + Console.WriteLine("please don't inline 'current'"); + Console.WriteLine(current); } } finally { IDisposable disposable = enumerator as IDisposable; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs index 63712439a..a78bf77d3 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs @@ -63,17 +63,68 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { private readonly int dummy; + public int Property { + get { + return 1; + } + set { + } + } + +#if CS80 + public readonly int ReadOnlyProperty { + get { + return 1; + } + set { + } + } + + public int PropertyWithReadOnlyGetter { + readonly get { + return 1; + } + set { + } + } + + public int PropertyWithReadOnlySetter { + get { + return 1; + } + readonly set { + } + } + + public event EventHandler NormalEvent; + + public readonly event EventHandler ReadOnlyEvent { + add { + } + remove { + } + } +#endif public void Method() { } + +#if CS80 + public readonly void ReadOnlyMethod() + { + } +#endif } public readonly struct ReadOnlyStruct { - private readonly int dummy; + private readonly int Field; public void Method() { + ref readonly int field = ref Field; + Console.WriteLine("No inlining"); + Console.WriteLine(field.GetHashCode()); } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs index 1c99c6ed4..6878f19a9 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs @@ -366,7 +366,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty #if !ROSLYN public static string SwitchOverBool(bool b) { - Console.WriteLine("SwitchOverBool: " + b.ToString()); + Console.WriteLine("SwitchOverBool: " + b); switch (b) { case true: return bool.TrueString; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs index 9e3cdf345..0e242bb97 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs @@ -90,7 +90,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty private void UsingStatementOnNullableStruct(UsingStruct? us) { using (us) { - Console.WriteLine("using-body: " + us); + Console.WriteLine("using-body: " + us.ToString()); } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs index 322b34c95..00d27a38b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs @@ -184,7 +184,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void Issue56(int i, out string str) { str = "qq"; - str += i.ToString(); + str += i; } public static void CopyAroundAndModifyField(S s) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.Expected.cs b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.Expected.cs new file mode 100644 index 000000000..72d6c3b3e --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.Expected.cs @@ -0,0 +1,33 @@ +using System; +using System.Runtime.CompilerServices; + +[assembly: Extension] + +namespace ICSharpCode.Decompiler.Tests.TestCases.Ugly +{ + [Extension] + internal static class NoExtensionMethods + { + [Extension] + internal static Func AsFunc(T value) where T : class + { + return new Func(value, __ldftn(Return)); + } + + [Extension] + private static T Return(T value) + { + return value; + } + + internal static Func ExtensionMethodAsStaticFunc() + { + return Return; + } + + internal static Func ExtensionMethodBoundToNull() + { + return new Func(null, __ldftn(Return)); + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.cs b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.cs new file mode 100644 index 000000000..294b71938 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.cs @@ -0,0 +1,27 @@ +using System; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Ugly +{ + internal static class NoExtensionMethods + { + internal static Func AsFunc(this T value) where T : class + { + return new Func(value.Return); + } + + private static T Return(this T value) + { + return value; + } + + internal static Func ExtensionMethodAsStaticFunc() + { + return Return; + } + + internal static Func ExtensionMethodBoundToNull() + { + return ((object)null).Return; + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.opt.roslyn.il b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.opt.roslyn.il new file mode 100644 index 000000000..12d81400b --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.opt.roslyn.il @@ -0,0 +1,93 @@ + + + + +// Metadata version: v4.0.30319 +.assembly extern mscorlib +{ + .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. + .ver 4:0:0:0 +} +.assembly NoExtensionMethods +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) + .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx + 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows. + + // --- The following custom attribute is added automatically, do not uncomment ------- + // .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 02 00 00 00 00 00 ) + + .permissionset reqmin + = {[mscorlib]System.Security.Permissions.SecurityPermissionAttribute = {property bool 'SkipVerification' = bool(true)}} + .hash algorithm 0x00008004 + .ver 0:0:0:0 +} +.module NoExtensionMethods.dll +.custom instance void [mscorlib]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 ) +.imagebase 0x10000000 +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 // WINDOWS_CUI +.corflags 0x00000001 // ILONLY + + +// =============== CLASS MEMBERS DECLARATION =================== + +.class private abstract auto ansi sealed beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.Ugly.NoExtensionMethods + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .method assembly hidebysig static class [mscorlib]System.Func`1 + AsFunc(!!T 'value') cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 18 (0x12) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: box !!T + IL_0006: ldftn !!0 ICSharpCode.Decompiler.Tests.TestCases.Ugly.NoExtensionMethods::Return(!!0) + IL_000c: newobj instance void class [mscorlib]System.Func`1::.ctor(object, + native int) + IL_0011: ret + } // end of method NoExtensionMethods::AsFunc + + .method private hidebysig static !!T Return(!!T 'value') cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 2 (0x2) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ret + } // end of method NoExtensionMethods::Return + + .method assembly hidebysig static class [mscorlib]System.Func`2 + ExtensionMethodAsStaticFunc() cil managed + { + // Code size 13 (0xd) + .maxstack 8 + IL_0000: ldnull + IL_0001: ldftn !!0 ICSharpCode.Decompiler.Tests.TestCases.Ugly.NoExtensionMethods::Return(!!0) + IL_0007: newobj instance void class [mscorlib]System.Func`2::.ctor(object, + native int) + IL_000c: ret + } // end of method NoExtensionMethods::ExtensionMethodAsStaticFunc + + .method assembly hidebysig static class [mscorlib]System.Func`1 + ExtensionMethodBoundToNull() cil managed + { + // Code size 13 (0xd) + .maxstack 8 + IL_0000: ldnull + IL_0001: ldftn !!0 ICSharpCode.Decompiler.Tests.TestCases.Ugly.NoExtensionMethods::Return(!!0) + IL_0007: newobj instance void class [mscorlib]System.Func`1::.ctor(object, + native int) + IL_000c: ret + } // end of method NoExtensionMethods::ExtensionMethodBoundToNull + +} // end of class ICSharpCode.Decompiler.Tests.TestCases.Ugly.NoExtensionMethods + + +// ============================================================= + +// *********** DISASSEMBLY COMPLETE *********************** diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.roslyn.il b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.roslyn.il new file mode 100644 index 000000000..a740ada26 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoExtensionMethods.roslyn.il @@ -0,0 +1,117 @@ + + + + +// Metadata version: v4.0.30319 +.assembly extern mscorlib +{ + .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. + .ver 4:0:0:0 +} +.assembly NoExtensionMethods +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) + .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx + 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows. + + // --- The following custom attribute is added automatically, do not uncomment ------- + // .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 ) + + .permissionset reqmin + = {[mscorlib]System.Security.Permissions.SecurityPermissionAttribute = {property bool 'SkipVerification' = bool(true)}} + .hash algorithm 0x00008004 + .ver 0:0:0:0 +} +.module NoExtensionMethods.dll +.custom instance void [mscorlib]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 ) +.imagebase 0x10000000 +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 // WINDOWS_CUI +.corflags 0x00000001 // ILONLY + + +// =============== CLASS MEMBERS DECLARATION =================== + +.class private abstract auto ansi sealed beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.Ugly.NoExtensionMethods + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .method assembly hidebysig static class [mscorlib]System.Func`1 + AsFunc(!!T 'value') cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 23 (0x17) + .maxstack 2 + .locals init (class [mscorlib]System.Func`1 V_0) + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: box !!T + IL_0007: ldftn !!0 ICSharpCode.Decompiler.Tests.TestCases.Ugly.NoExtensionMethods::Return(!!0) + IL_000d: newobj instance void class [mscorlib]System.Func`1::.ctor(object, + native int) + IL_0012: stloc.0 + IL_0013: br.s IL_0015 + + IL_0015: ldloc.0 + IL_0016: ret + } // end of method NoExtensionMethods::AsFunc + + .method private hidebysig static !!T Return(!!T 'value') cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 7 (0x7) + .maxstack 1 + .locals init (!!T V_0) + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: stloc.0 + IL_0003: br.s IL_0005 + + IL_0005: ldloc.0 + IL_0006: ret + } // end of method NoExtensionMethods::Return + + .method assembly hidebysig static class [mscorlib]System.Func`2 + ExtensionMethodAsStaticFunc() cil managed + { + // Code size 18 (0x12) + .maxstack 2 + .locals init (class [mscorlib]System.Func`2 V_0) + IL_0000: nop + IL_0001: ldnull + IL_0002: ldftn !!0 ICSharpCode.Decompiler.Tests.TestCases.Ugly.NoExtensionMethods::Return(!!0) + IL_0008: newobj instance void class [mscorlib]System.Func`2::.ctor(object, + native int) + IL_000d: stloc.0 + IL_000e: br.s IL_0010 + + IL_0010: ldloc.0 + IL_0011: ret + } // end of method NoExtensionMethods::ExtensionMethodAsStaticFunc + + .method assembly hidebysig static class [mscorlib]System.Func`1 + ExtensionMethodBoundToNull() cil managed + { + // Code size 18 (0x12) + .maxstack 2 + .locals init (class [mscorlib]System.Func`1 V_0) + IL_0000: nop + IL_0001: ldnull + IL_0002: ldftn !!0 ICSharpCode.Decompiler.Tests.TestCases.Ugly.NoExtensionMethods::Return(!!0) + IL_0008: newobj instance void class [mscorlib]System.Func`1::.ctor(object, + native int) + IL_000d: stloc.0 + IL_000e: br.s IL_0010 + + IL_0010: ldloc.0 + IL_0011: ret + } // end of method NoExtensionMethods::ExtensionMethodBoundToNull + +} // end of class ICSharpCode.Decompiler.Tests.TestCases.Ugly.NoExtensionMethods + + +// ============================================================= + +// *********** DISASSEMBLY COMPLETE *********************** diff --git a/ICSharpCode.Decompiler.Tests/UglyTestRunner.cs b/ICSharpCode.Decompiler.Tests/UglyTestRunner.cs index dff8da418..63ebcf6dd 100644 --- a/ICSharpCode.Decompiler.Tests/UglyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/UglyTestRunner.cs @@ -83,6 +83,14 @@ namespace ICSharpCode.Decompiler.Tests }); } + [Test] + public void NoExtensionMethods([ValueSource("roslynOnlyOptions")] CompilerOptions cscOptions) + { + RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { + ExtensionMethods = false + }); + } + void RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, DecompilerSettings decompilerSettings = null) { Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, decompilerSettings); 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/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index ad581e53e..f7e2b5a67 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -1235,7 +1235,38 @@ namespace ICSharpCode.Decompiler.CSharp default: throw new ArgumentException($"Unknown instruction type: {func.OpCode}"); } - return HandleDelegateConstruction(inst.Method.DeclaringType, method, expectedTargetDetails, thisArg, inst); + if (CanUseDelegateConstruction(method, thisArg, inst.Method.DeclaringType.GetDelegateInvokeMethod())) { + return HandleDelegateConstruction(inst.Method.DeclaringType, method, expectedTargetDetails, thisArg, inst); + } else { + var argumentList = BuildArgumentList(expectedTargetDetails, null, inst.Method, + 0, inst.Arguments, null); + return HandleConstructorCall(new ExpectedTargetDetails { CallOpCode = OpCode.NewObj }, null, inst.Method, argumentList).WithILInstruction(inst); + } + } + + private bool CanUseDelegateConstruction(IMethod targetMethod, ILInstruction thisArg, IMethod invokeMethod) + { + if (targetMethod.IsStatic) { + // If the invoke method is known, we can compare the parameter counts to figure out whether the + // delegate is static or binds the first argument + if (invokeMethod != null) { + if (invokeMethod.Parameters.Count == targetMethod.Parameters.Count) { + return thisArg.MatchLdNull(); + } else if (targetMethod.IsExtensionMethod && invokeMethod.Parameters.Count == targetMethod.Parameters.Count - 1) { + return true; + } else { + return false; + } + } else { + // delegate type unknown: + return thisArg.MatchLdNull() || targetMethod.IsExtensionMethod; + } + } else { + // targetMethod is instance method + if (invokeMethod != null && invokeMethod.Parameters.Count != targetMethod.Parameters.Count) + return false; + return true; + } } internal TranslatedExpression Build(LdVirtDelegate inst) @@ -1243,9 +1274,15 @@ namespace ICSharpCode.Decompiler.CSharp return HandleDelegateConstruction(inst.Type, inst.Method, new ExpectedTargetDetails { CallOpCode = OpCode.CallVirt }, inst.Argument, inst); } - TranslatedExpression HandleDelegateConstruction(IType delegateType, IMethod method, ExpectedTargetDetails expectedTargetDetails, ILInstruction thisArg, ILInstruction inst) + internal ExpressionWithResolveResult BuildMethodReference(IMethod method, bool isVirtual) + { + var expr = BuildDelegateReference(method, invokeMethod: null, new ExpectedTargetDetails { CallOpCode = isVirtual ? OpCode.CallVirt : OpCode.Call }, thisArg: null); + expr.Expression.RemoveAnnotations(); + return expr.Expression.WithRR(new MemberResolveResult(null, method)); + } + + ExpressionWithResolveResult BuildDelegateReference(IMethod method, IMethod invokeMethod, ExpectedTargetDetails expectedTargetDetails, ILInstruction thisArg) { - var invokeMethod = delegateType.GetDelegateInvokeMethod(); TranslatedExpression target; IType targetType; bool requireTarget; @@ -1331,27 +1368,32 @@ namespace ICSharpCode.Decompiler.CSharp } } requireTarget = !method.IsLocalFunction && (step & 1) != 0; - Expression targetExpression; + ExpressionWithResolveResult targetExpression; Debug.Assert(result != null); if (requireTarget) { Debug.Assert(target.Expression != null); var mre = new MemberReferenceExpression(target, methodName); if ((step & 2) != 0) mre.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType)); - mre.WithRR(result); - targetExpression = mre; + targetExpression = mre.WithRR(result); } else { var ide = new IdentifierExpression(methodName); if ((step & 2) != 0) ide.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType)); - ide.WithRR(result); - targetExpression = ide; + targetExpression = ide.WithRR(result); } + return targetExpression; + } + + TranslatedExpression HandleDelegateConstruction(IType delegateType, IMethod method, ExpectedTargetDetails expectedTargetDetails, ILInstruction thisArg, ILInstruction inst) + { + var invokeMethod = delegateType.GetDelegateInvokeMethod(); + var targetExpression = BuildDelegateReference(method, invokeMethod, expectedTargetDetails, thisArg); var oce = new ObjectCreateExpression(expressionBuilder.ConvertType(delegateType), targetExpression) .WithILInstruction(inst) .WithRR(new ConversionResolveResult( delegateType, - result, + targetExpression.ResolveResult, Conversion.MethodGroupConversion(method, expectedTargetDetails.CallOpCode == OpCode.CallVirt, false))); return oce; } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 0d7d7b9c6..19f50310c 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1409,7 +1409,8 @@ namespace ICSharpCode.Decompiler.CSharp if (UserDefinedCompoundAssign.IsStringConcat(inst.Method)) { Debug.Assert(inst.Method.Parameters.Count == 2); var value = Translate(inst.Value).ConvertTo(inst.Method.Parameters[1].Type, this, allowImplicitConversion: true); - return new AssignmentExpression(target, AssignmentOperatorType.Add, value) + var valueExpr = ReplaceMethodCallsWithOperators.RemoveRedundantToStringInConcat(value, inst.Method, isLastArgument: true).Detach(); + 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 })); } else if (inst.Method.Parameters.Count == 2) { @@ -3134,6 +3135,22 @@ namespace ICSharpCode.Decompiler.CSharp .WithRR(new OperatorResolveResult(SpecialType.Dynamic, inst.Operation, new[] { target.ResolveResult, value.ResolveResult })); } + protected internal override TranslatedExpression VisitLdFtn(LdFtn inst, TranslationContext context) + { + ExpressionWithResolveResult delegateRef = new CallBuilder(this, typeSystem, settings).BuildMethodReference(inst.Method, isVirtual: false); + return new InvocationExpression(new IdentifierExpression("__ldftn"), delegateRef) + .WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.IntPtr))) + .WithILInstruction(inst); + } + + protected internal override TranslatedExpression VisitLdVirtFtn(LdVirtFtn inst, TranslationContext context) + { + ExpressionWithResolveResult delegateRef = new CallBuilder(this, typeSystem, settings).BuildMethodReference(inst.Method, isVirtual: true); + return new InvocationExpression(new IdentifierExpression("__ldvirtftn"), delegateRef) + .WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.IntPtr))) + .WithILInstruction(inst); + } + protected internal override TranslatedExpression VisitInvalidBranch(InvalidBranch inst, TranslationContext context) { string message = "Error"; diff --git a/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs b/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs index 849e0eee9..b652e6ebe 100644 --- a/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs @@ -300,7 +300,7 @@ namespace ICSharpCode.Decompiler.CSharp if (sequencePoint.Offset < pos) { // overlapping sequence point? // delete previous sequence points that are after sequencePoint.Offset - while (newList.Count > 0 && newList.Last().EndOffset > pos) { + while (newList.Count > 0 && newList.Last().EndOffset > sequencePoint.Offset) { var last = newList.Last(); if (last.Offset >= sequencePoint.Offset) { newList.RemoveAt(newList.Count - 1); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 589f1042c..64d677fb4 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -1522,6 +1522,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax Accessor decl = new Accessor(); if (this.ShowAccessibility && accessor.Accessibility != ownerAccessibility) decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility); + if (accessor.ThisIsRefReadOnly && accessor.DeclaringTypeDefinition?.IsReadOnly == false) + decl.Modifiers |= Modifiers.Readonly; if (ShowAttributes) { decl.Attributes.AddRange(ConvertAttributes(accessor.GetAttributes())); decl.Attributes.AddRange(ConvertAttributes(accessor.GetReturnTypeAttributes(), "return")); @@ -1551,9 +1553,18 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax decl.Getter = ConvertAccessor(property.Getter, property.Accessibility, false); decl.Setter = ConvertAccessor(property.Setter, property.Accessibility, true); decl.PrivateImplementationType = GetExplicitInterfaceType (property); + MergeReadOnlyModifiers(decl, decl.Getter, decl.Setter); return decl; } + static void MergeReadOnlyModifiers(EntityDeclaration decl, Accessor accessor1, Accessor accessor2) + { + if (accessor1.HasModifier(Modifiers.Readonly) && accessor2.HasModifier(Modifiers.Readonly)) { + accessor1.Modifiers &= ~Modifiers.Readonly; + accessor2.Modifiers &= ~Modifiers.Readonly; + decl.Modifiers |= Modifiers.Readonly; + } + } IndexerDeclaration ConvertIndexer(IProperty indexer) { IndexerDeclaration decl = new IndexerDeclaration(); @@ -1571,6 +1582,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax decl.Getter = ConvertAccessor(indexer.Getter, indexer.Accessibility, false); decl.Setter = ConvertAccessor(indexer.Setter, indexer.Accessibility, true); decl.PrivateImplementationType = GetExplicitInterfaceType (indexer); + MergeReadOnlyModifiers(decl, decl.Getter, decl.Setter); return decl; } @@ -1590,6 +1602,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax decl.AddAccessor = ConvertAccessor(ev.AddAccessor, ev.Accessibility, true); decl.RemoveAccessor = ConvertAccessor(ev.RemoveAccessor, ev.Accessibility, true); decl.PrivateImplementationType = GetExplicitInterfaceType (ev); + MergeReadOnlyModifiers(decl, decl.AddAccessor, decl.RemoveAccessor); return decl; } else { EventDeclaration decl = new EventDeclaration(); @@ -1728,8 +1741,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax bool NeedsAccessibility(IMember member) { var declaringType = member.DeclaringType; - if ((declaringType != null && declaringType.Kind == TypeKind.Interface) || member.IsExplicitInterfaceImplementation) + if (member.IsExplicitInterfaceImplementation) return false; + if (declaringType != null && declaringType.Kind == TypeKind.Interface) { + return member.Accessibility != Accessibility.Public; + } switch (member.SymbolKind) { case SymbolKind.Constructor: return !member.IsStatic; @@ -1751,14 +1767,21 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax m |= Modifiers.Static; } else { var declaringType = member.DeclaringType; - if (member.IsAbstract && declaringType != null && declaringType.Kind != TypeKind.Interface) - m |= Modifiers.Abstract; - if (member.IsOverride) + if (declaringType.Kind == TypeKind.Interface) { + if (!member.IsVirtual && !member.IsAbstract && !member.IsOverride && member.Accessibility != Accessibility.Private) + m |= Modifiers.Sealed; + } else { + if (member.IsAbstract) + m |= Modifiers.Abstract; + else if (member.IsVirtual && !member.IsOverride) + m |= Modifiers.Virtual; + } + if (member.IsOverride && !member.IsExplicitInterfaceImplementation) m |= Modifiers.Override; - if (member.IsVirtual && !member.IsAbstract && !member.IsOverride && declaringType.Kind != TypeKind.Interface) - m |= Modifiers.Virtual; - if (member.IsSealed) + if (member.IsSealed && !member.IsExplicitInterfaceImplementation) m |= Modifiers.Sealed; + if (member is IMethod method && method.ThisIsRefReadOnly && method.DeclaringTypeDefinition?.IsReadOnly == false) + m |= Modifiers.Readonly; } } return m; 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/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs index faa6a3163..8692bf678 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs @@ -571,11 +571,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } if (field == null) return null; + if (propertyDeclaration.Setter.HasModifier(Modifiers.Readonly)) + return null; if (field.IsCompilerGenerated() && field.DeclaringTypeDefinition == property.DeclaringTypeDefinition) { RemoveCompilerGeneratedAttribute(propertyDeclaration.Getter.Attributes); RemoveCompilerGeneratedAttribute(propertyDeclaration.Setter.Attributes); propertyDeclaration.Getter.Body = null; propertyDeclaration.Setter.Body = null; + propertyDeclaration.Getter.Modifiers &= ~Modifiers.Readonly; // Add C# 7.3 attributes on backing field: var attributes = field.GetAttributes() diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs index 9d9838d71..194c4f603 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs @@ -23,7 +23,9 @@ using System.Reflection; using System.Text; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; +using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.CSharp.Transforms { @@ -56,11 +58,19 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms var arguments = invocationExpression.Arguments.ToArray(); // Reduce "String.Concat(a, b)" to "a + b" - if (method.Name == "Concat" && method.DeclaringType.FullName == "System.String" && CheckArgumentsForStringConcat(arguments)) { - invocationExpression.Arguments.Clear(); // detach arguments from invocationExpression - Expression expr = arguments[0]; + if (IsStringConcat(method) && CheckArgumentsForStringConcat(arguments)) { + bool isInExpressionTree = invocationExpression.Ancestors.OfType().Any( + lambda => lambda.Annotation()?.Kind == IL.ILFunctionKind.ExpressionTree); + Expression expr = arguments[0].Detach(); + if (!isInExpressionTree) { + expr = RemoveRedundantToStringInConcat(expr, method, isLastArgument: false).Detach(); + } for (int i = 1; i < arguments.Length; i++) { - expr = new BinaryOperatorExpression(expr, BinaryOperatorType.Add, arguments[i].UnwrapInDirectionExpression()); + var arg = arguments[i].Detach(); + if (!isInExpressionTree) { + arg = RemoveRedundantToStringInConcat(arg, method, isLastArgument: i == arguments.Length - 1).Detach(); + } + expr = new BinaryOperatorExpression(expr, BinaryOperatorType.Add, arg); } expr.CopyAnnotationsFrom(invocationExpression); invocationExpression.ReplaceWith(expr); @@ -163,7 +173,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return; } - + bool IsInstantiableTypeParameter(IType type) { return type is ITypeParameter tp && tp.HasDefaultConstructorConstraint; @@ -174,9 +184,135 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (arguments.Length < 2) return false; - return !arguments.Any(arg => arg is NamedArgumentExpression) && - (arguments[0].GetResolveResult().Type.IsKnownType(KnownTypeCode.String) || - arguments[1].GetResolveResult().Type.IsKnownType(KnownTypeCode.String)); + if (arguments.Any(arg => arg is NamedArgumentExpression)) + return false; + + // The evaluation order when the object.ToString() calls happen is a mess: + // The C# spec says the evaluation for order for each individual string + should be: + // * evaluate left argument + // * evaluate right argument + // * call ToString() on object argument + // What actually happens pre-VS2019.3: + // * evaluate all arguments in chain of + operators from left to right + // * call ToString() on all object arguments from left to right + // What happens in VS2019.3: + // * for each argument in chain of + operators fom left to right: + // * evaluate argument + // * call ToString() on object argument + // See https://github.com/dotnet/roslyn/issues/38641 for details. + // To ensure the decompiled code's behavior matches the original IL behavior, + // no matter which compiler is used to recompile it, we require that all + // implicit ToString() calls except for the last are free of side effects. + foreach (var arg in arguments.SkipLast(1)) { + if (!ToStringIsKnownEffectFree(arg.GetResolveResult().Type)) { + return false; + } + } + foreach (var arg in arguments) { + if (arg.GetResolveResult() is InvocationResolveResult rr && IsStringConcat(rr.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 + // explicit string.Concat() call. + // This problem is avoided if the outer call remains string.Concat() as well. + return false; + } + } + + // One of the first two arguments must be string, otherwise the + operator + // won't resolve to a string concatenation. + return arguments[0].GetResolveResult().Type.IsKnownType(KnownTypeCode.String) + || arguments[1].GetResolveResult().Type.IsKnownType(KnownTypeCode.String); + } + + private bool IsStringConcat(IParameterizedMember member) + { + return member is IMethod method + && method.Name == "Concat" + && method.DeclaringType.IsKnownType(KnownTypeCode.String); + } + + static readonly Pattern ToStringCallPattern = new Choice { + // target.ToString() + new InvocationExpression(new MemberReferenceExpression(new AnyNode("target"), "ToString")).WithName("call"), + // target?.ToString() + new UnaryOperatorExpression( + UnaryOperatorType.NullConditionalRewrap, + new InvocationExpression( + new MemberReferenceExpression( + new UnaryOperatorExpression(UnaryOperatorType.NullConditional, new AnyNode("target")), + "ToString") + ).WithName("call") + ).WithName("nullConditional") + }; + + internal static Expression RemoveRedundantToStringInConcat(Expression expr, IMethod concatMethod, bool isLastArgument) + { + var m = ToStringCallPattern.Match(expr); + if (!m.Success) + return expr; + + if (!concatMethod.Parameters.All(IsStringParameter)) { + // If we're using a string.Concat() overload involving object parameters, + // string.Concat() itself already calls ToString() so the C# compiler shouldn't + // generate additional ToString() calls in this case. + return expr; + } + var toStringMethod = m.Get("call").Single().GetSymbol() as IMethod; + var target = m.Get("target").Single(); + var type = target.GetResolveResult().Type; + if (!(isLastArgument || ToStringIsKnownEffectFree(type))) { + // ToString() order of evaluation matters, see CheckArgumentsForStringConcat(). + return expr; + } + if (type.IsReferenceType != false && !m.Has("nullConditional")) { + // ToString() might throw NullReferenceException, but the builtin operator+ doesn't. + return expr; + } + if (!ToStringIsKnownEffectFree(type) && toStringMethod != null && IL.Transforms.ILInlining.MethodRequiresCopyForReadonlyLValue(toStringMethod)) { + // ToString() on a struct may mutate the struct. + // For operator+ the C# compiler creates a temporary copy before implicitly calling ToString(), + // whereas an explicit ToString() call would mutate the original lvalue. + // So we can't remove the compiler-generated ToString() call in cases where this might make a difference. + return expr; + } + + // All checks succeeded, we can eliminate the ToString() call. + // The C# compiler will generate an equivalent call if the code is recompiled. + return target; + + bool IsStringParameter(IParameter p) + { + IType ty = p.Type; + if (p.IsParams && ty.Kind == TypeKind.Array) + ty = ((ArrayType)ty).ElementType; + return ty.IsKnownType(KnownTypeCode.String); + } + } + + static bool ToStringIsKnownEffectFree(IType type) + { + type = NullableType.GetUnderlyingType(type); + switch (type.GetDefinition()?.KnownTypeCode) { + case KnownTypeCode.Boolean: + case KnownTypeCode.Char: + case KnownTypeCode.SByte: + case KnownTypeCode.Byte: + case KnownTypeCode.Int16: + case KnownTypeCode.UInt16: + case KnownTypeCode.Int32: + case KnownTypeCode.UInt32: + case KnownTypeCode.Int64: + case KnownTypeCode.UInt64: + case KnownTypeCode.Single: + case KnownTypeCode.Double: + case KnownTypeCode.Decimal: + case KnownTypeCode.IntPtr: + case KnownTypeCode.UIntPtr: + case KnownTypeCode.String: + return true; + default: + return false; + } } static BinaryOperatorType? GetBinaryOperatorTypeFromMetadataName(string name) diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index ff5b8838a..f65fe7d16 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -283,10 +283,9 @@ namespace ICSharpCode.Decompiler.CSharp UnwrapChild(uoe.Expression).ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion) ).WithRR(new ResolveResult(targetType)).WithoutILInstruction(); } - bool isLifted = type.IsKnownType(KnownTypeCode.NullableOfT) && targetType.IsKnownType(KnownTypeCode.NullableOfT); - IType utype = isLifted ? NullableType.GetUnderlyingType(type) : type; - IType targetUType = isLifted ? NullableType.GetUnderlyingType(targetType) : targetType; - if (type.IsKnownType(KnownTypeCode.Boolean) && targetType.GetStackType().IsIntegerType()) { + IType utype = NullableType.GetUnderlyingType(type); + IType targetUType = NullableType.GetUnderlyingType(targetType); + if (type.IsKnownType(KnownTypeCode.Boolean) && targetUType.GetStackType().IsIntegerType()) { // convert from boolean to integer (or enum) return new ConditionalExpression( this.Expression, @@ -318,7 +317,7 @@ namespace ICSharpCode.Decompiler.CSharp .ConvertTo(targetType, expressionBuilder, checkForOverflow); } } - if (targetType.IsKnownType(KnownTypeCode.IntPtr)) { // Conversion to IntPtr + if (targetUType.IsKnownType(KnownTypeCode.IntPtr)) { // Conversion to IntPtr if (type.IsKnownType(KnownTypeCode.Int32)) { // normal casts work for int (both in checked and unchecked context) } else if (checkForOverflow) { @@ -336,7 +335,7 @@ namespace ICSharpCode.Decompiler.CSharp .ConvertTo(targetType, expressionBuilder, checkForOverflow); } } - } else if (targetType.IsKnownType(KnownTypeCode.UIntPtr)) { // Conversion to UIntPtr + } else if (targetUType.IsKnownType(KnownTypeCode.UIntPtr)) { // Conversion to UIntPtr if (type.IsKnownType(KnownTypeCode.UInt32) || type.Kind == TypeKind.Pointer) { // normal casts work for uint and pointers (both in checked and unchecked context) } else if (checkForOverflow) { @@ -359,14 +358,14 @@ namespace ICSharpCode.Decompiler.CSharp // -> convert via underlying type return this.ConvertTo(type.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow) .ConvertTo(targetType, expressionBuilder, checkForOverflow); - } else if (targetType.Kind == TypeKind.Enum && type.Kind == TypeKind.Pointer) { + } else if (targetUType.Kind == TypeKind.Enum && type.Kind == TypeKind.Pointer) { // pointer to enum: C# doesn't allow such casts // -> convert via underlying type - return this.ConvertTo(targetType.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow) + return this.ConvertTo(targetUType.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow) .ConvertTo(targetType, expressionBuilder, checkForOverflow); } if (targetType.Kind == TypeKind.Pointer && type.IsKnownType(KnownTypeCode.Char) - || targetType.IsKnownType(KnownTypeCode.Char) && type.Kind == TypeKind.Pointer) { + || targetUType.IsKnownType(KnownTypeCode.Char) && type.Kind == TypeKind.Pointer) { // char <-> pointer: C# doesn't allow such casts // -> convert via ushort return this.ConvertTo(compilation.FindType(KnownTypeCode.UInt16), expressionBuilder, checkForOverflow) @@ -418,6 +417,17 @@ namespace ICSharpCode.Decompiler.CSharp .WithoutILInstruction() .WithRR(new ByReferenceResolveResult(elementRR, ReferenceKind.Ref)); } + if (this.ResolveResult.IsCompileTimeConstant && this.ResolveResult.ConstantValue != null + && NullableType.IsNullable(targetType) && !utype.Equals(targetUType)) + { + // Casts like `(uint?)-1` are only valid in an explicitly unchecked context, but we + // don't have logic to ensure such a context (usually we emit into an implicitly unchecked context). + // This only applies with constants as input (int->uint? is fine in implicitly unchecked context). + // We use an intermediate cast to the nullable's underlying type, which results + // in a constant conversion, so the final output will be something like `(uint?)uint.MaxValue` + return ConvertTo(targetUType, expressionBuilder, checkForOverflow, allowImplicitConversion: false) + .ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion); + } var rr = expressionBuilder.resolver.WithCheckForOverflow(checkForOverflow).ResolveCast(targetType, ResolveResult); if (rr.IsCompileTimeConstant && !rr.IsError) { return expressionBuilder.ConvertConstantValue(rr, allowImplicitConversion) diff --git a/ICSharpCode.Decompiler/DebugInfo/SequencePoint.cs b/ICSharpCode.Decompiler/DebugInfo/SequencePoint.cs index 441731f72..cb0392150 100644 --- a/ICSharpCode.Decompiler/DebugInfo/SequencePoint.cs +++ b/ICSharpCode.Decompiler/DebugInfo/SequencePoint.cs @@ -17,12 +17,14 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Diagnostics; namespace ICSharpCode.Decompiler.DebugInfo { /// /// A sequence point read from a PDB file or produced by the decompiler. /// + [DebuggerDisplay("SequencePoint IL_{Offset,h}-IL_{EndOffset,h}, {StartLine}:{StartColumn}-{EndLine}:{EndColumn}, IsHidden={IsHidden}")] public struct SequencePoint { /// diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 167961528..40aa62e4a 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -107,12 +107,13 @@ namespace ICSharpCode.Decompiler } if (languageVersion < CSharp.LanguageVersion.CSharp8_0) { nullableReferenceTypes = false; + readOnlyMethods = false; } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { - if (nullableReferenceTypes) + if (nullableReferenceTypes || readOnlyMethods) return CSharp.LanguageVersion.CSharp8_0; if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers) return CSharp.LanguageVersion.CSharp7_3; @@ -842,6 +843,20 @@ namespace ICSharpCode.Decompiler } } + bool readOnlyMethods = true; + + [Category("C# 8.0 / VS 2019")] + [Description("DecompilerSettings.IsReadOnlyAttributeShouldBeReplacedWithReadonlyInModifiersOnStructsParameters")] + public bool ReadOnlyMethods { + get { return readOnlyMethods; } + set { + if (readOnlyMethods != value) { + readOnlyMethods = value; + OnPropertyChanged(); + } + } + } + bool introduceUnmanagedConstraint = true; /// diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 45adfd61f..7480923ab 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -328,6 +328,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs index ec20ef25c..63610c57e 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs @@ -85,6 +85,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow TryCatch mainTryCatch; Block setResultAndExitBlock; // block that is jumped to for return statements int finalState; // final state after the setResultAndExitBlock + bool finalStateKnown; ILVariable resultVar; // the variable that gets returned by the setResultAndExitBlock ILVariable doFinallyBodies; @@ -352,7 +353,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow moveNextFunction = YieldReturnDecompiler.CreateILAst(moveNextMethod, context); if (!(moveNextFunction.Body is BlockContainer blockContainer)) throw new SymbolicAnalysisFailedException(); - if (blockContainer.Blocks.Count != 2) + if (blockContainer.Blocks.Count != 2 && blockContainer.Blocks.Count != 1) throw new SymbolicAnalysisFailedException(); if (blockContainer.EntryPoint.IncomingEdgeCount != 1) throw new SymbolicAnalysisFailedException(); @@ -389,7 +390,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow doFinallyBodies = initDoFinallyBodies.Variable; } - setResultAndExitBlock = blockContainer.Blocks[1]; + setResultAndExitBlock = blockContainer.Blocks.ElementAtOrDefault(1); + if (setResultAndExitBlock == null) { + // This block can be absent if the function never exits normally, + // but always throws an exception/loops infinitely. + resultVar = null; + finalStateKnown = false; // final state will be detected in ValidateCatchBlock() instead + return; + } // stobj System.Int32(ldflda [Field ICSharpCode.Decompiler.Tests.TestCases.Pretty.Async+d__7.<>1__state](ldloc this), ldc.i4 -2) // call SetResult(ldflda [Field ICSharpCode.Decompiler.Tests.TestCases.Pretty.Async+d__7.<>t__builder](ldloc this), ldloc result) // leave IL_0000 @@ -397,6 +405,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow throw new SymbolicAnalysisFailedException(); if (!MatchStateAssignment(setResultAndExitBlock.Instructions[0], out finalState)) throw new SymbolicAnalysisFailedException(); + finalStateKnown = true; if (!MatchCall(setResultAndExitBlock.Instructions[1], "SetResult", out var args)) throw new SymbolicAnalysisFailedException(); if (!IsBuilderFieldOnThis(args[0])) @@ -442,8 +451,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow if (!stloc.Value.MatchLdLoc(handler.Variable)) throw new SymbolicAnalysisFailedException(); // stfld <>1__state(ldloc this, ldc.i4 -2) - if (!MatchStateAssignment(catchBlock.Instructions[1], out int newState) || newState != finalState) + if (!MatchStateAssignment(catchBlock.Instructions[1], out int newState)) throw new SymbolicAnalysisFailedException(); + if (finalStateKnown) { + if (newState != finalState) + throw new SymbolicAnalysisFailedException(); + } else { + finalState = newState; + finalStateKnown = true; + } // call SetException(ldfld <>t__builder(ldloc this), ldloc exception) if (!MatchCall(catchBlock.Instructions[2], "SetException", out var args)) throw new SymbolicAnalysisFailedException(); diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index f840dce25..8cbb8a1ff 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 = method.ThisIsRefReadOnly; + 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/CopyPropagation.cs b/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs index 3ccf25e02..b96909efc 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs @@ -18,6 +18,7 @@ using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -31,7 +32,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// then we can replace the variable with the argument. /// 2) assignments of address-loading instructions to local variables /// - public class CopyPropagation : IBlockTransform, IILTransform + public class CopyPropagation : IILTransform { public static void Propagate(StLoc store, ILTransformContext context) { @@ -41,26 +42,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms DoPropagate(store.Variable, store.Value, block, ref i, context); } - public void Run(Block block, BlockTransformContext context) - { - RunOnBlock(block, context); - } - public void Run(ILFunction function, ILTransformContext context) { + var splitVariables = new HashSet(ILVariableEqualityComparer.Instance); + foreach (var g in function.Variables.GroupBy(v => v, ILVariableEqualityComparer.Instance)) { + if (g.Count() > 1) { + splitVariables.Add(g.Key); + } + } foreach (var block in function.Descendants.OfType()) { if (block.Kind != BlockKind.ControlFlow) continue; - RunOnBlock(block, context); + RunOnBlock(block, context, splitVariables); } } - static void RunOnBlock(Block block, ILTransformContext context) + static void RunOnBlock(Block block, ILTransformContext context, HashSet splitVariables = null) { for (int i = 0; i < block.Instructions.Count; i++) { - ILVariable v; - ILInstruction copiedExpr; - if (block.Instructions[i].MatchStLoc(out v, out copiedExpr)) { + if (block.Instructions[i].MatchStLoc(out ILVariable v, out ILInstruction copiedExpr)) { if (v.IsSingleDefinition && v.LoadCount == 0 && v.Kind == VariableKind.StackSlot) { // dead store to stack if (SemanticHelper.IsPure(copiedExpr.Flags)) { @@ -76,18 +76,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms copiedExpr.AddILRange(block.Instructions[i]); block.Instructions[i] = copiedExpr; } - } else if (v.IsSingleDefinition && CanPerformCopyPropagation(v, copiedExpr)) { + } else if (v.IsSingleDefinition && CanPerformCopyPropagation(v, copiedExpr, splitVariables)) { DoPropagate(v, copiedExpr, block, ref i, context); } } } } - static bool CanPerformCopyPropagation(ILVariable target, ILInstruction value) + static bool CanPerformCopyPropagation(ILVariable target, ILInstruction value, HashSet splitVariables) { Debug.Assert(target.StackType == value.ResultType); if (target.Type.IsSmallIntegerType()) return false; + if (splitVariables != null && splitVariables.Contains(target)) { + return false; // non-local code move might change semantics when there's split variables + } switch (value.OpCode) { case OpCode.LdLoca: // case OpCode.LdElema: @@ -100,6 +103,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; case OpCode.LdLoc: var v = ((LdLoc)value).Variable; + if (splitVariables != null && splitVariables.Contains(v)) { + return false; // non-local code move might change semantics when there's split variables + } switch (v.Kind) { case VariableKind.Parameter: // Parameters can be copied only if they aren't assigned to (directly or indirectly via ldarga) diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index 3f3a658d7..840c77e9b 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -249,7 +249,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms // 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)) + if (!IsUsedAsThisPointerInCall(loadInst, out var method)) return false; switch (ClassifyExpression(inlinedExpression)) { case ExpressionClassification.RValue: @@ -261,14 +261,30 @@ namespace ICSharpCode.Decompiler.IL.Transforms case ExpressionClassification.ReadonlyLValue: // For struct method calls on readonly lvalues, the C# compiler // only generates a temporary if it isn't a "readonly struct" - return !(v.Type.GetDefinition()?.IsReadOnly == true); + return MethodRequiresCopyForReadonlyLValue(method); default: throw new InvalidOperationException("invalid expression classification"); } } + internal static bool MethodRequiresCopyForReadonlyLValue(IMethod method) + { + var type = method.DeclaringType; + if (type.IsReferenceType == true) + return false; // reference types are never implicitly copied + if (method.ThisIsRefReadOnly) + return false; // no copies for calls on readonly structs + return true; + } + internal static bool IsUsedAsThisPointerInCall(LdLoca ldloca) { + return IsUsedAsThisPointerInCall(ldloca, out _); + } + + static bool IsUsedAsThisPointerInCall(LdLoca ldloca, out IMethod method) + { + method = null; if (ldloca.ChildIndex != 0) return false; if (ldloca.Variable.Type.IsReferenceType ?? false) @@ -280,7 +296,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms switch (inst.Parent.OpCode) { case OpCode.Call: case OpCode.CallVirt: - var method = ((CallInstruction)inst.Parent).Method; + method = ((CallInstruction)inst.Parent).Method; if (method.IsAccessor && method.AccessorKind != MethodSemanticsAttributes.Getter) { // C# doesn't allow calling setters on temporary structs return false; @@ -313,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; @@ -347,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: @@ -356,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 _: @@ -367,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/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs index d892b9c48..5a3109ce7 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs @@ -37,26 +37,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms public ILInstruction Initializer; public ILVariable Variable; public ITypeDefinition Definition; - public Dictionary Variables; + public Dictionary Variables; public BlockContainer CaptureScope; public ILFunction DeclaringFunction; } - struct DisplayClassVariable - { - public ILVariable Variable; - public ILInstruction Value; - } - ILTransformContext context; - ILFunction currentFunction; readonly Dictionary displayClasses = new Dictionary(); readonly List instructionsToRemove = new List(); public void Run(ILFunction function, ILTransformContext context) { try { - if (this.context != null || this.currentFunction != null) + if (this.context != null) throw new InvalidOperationException("Reentrancy in " + nameof(TransformDisplayClassUsage)); this.context = context; var decompilationContext = new SimpleTypeResolveContext(context.Function.Method); @@ -73,12 +66,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms AddOrUpdateDisplayClass(f, v, ((ByReferenceType)p.Type).ElementType.GetDefinition(), f.Body, localFunctionClosureParameter: true); } } - foreach (var displayClass in displayClasses.Values.OrderByDescending(d => d.Initializer.StartILOffset).ToArray()) { - context.Step($"Transform references to " + displayClass.Variable, displayClass.Initializer); - this.currentFunction = f; - VisitILFunction(f); - } } + VisitILFunction(function); if (instructionsToRemove.Count > 0) { context.Step($"Remove instructions", function); foreach (var store in instructionsToRemove) { @@ -91,7 +80,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms instructionsToRemove.Clear(); displayClasses.Clear(); this.context = null; - this.currentFunction = null; } } @@ -106,7 +94,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms Initializer = inst, Variable = v, Definition = closureType, - Variables = new Dictionary(), + Variables = new Dictionary(), CaptureScope = (isMono && IsMonoNestedCaptureScope(closureType)) || localFunctionClosureParameter ? null : v.CaptureScope, DeclaringFunction = localFunctionClosureParameter ? f.DeclarationScope.Ancestors.OfType().First() : f }); @@ -151,11 +139,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; } - bool IsOuterClosureReference(IField field) - { - return displayClasses.Values.Any(disp => disp.Definition == field.DeclaringTypeDefinition); - } - bool IsMonoNestedCaptureScope(ITypeDefinition closureType) { var decompilationContext = new SimpleTypeResolveContext(context.Function.Method); @@ -181,17 +164,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms Initializer = nestedFunction.Body, Variable = thisVariable, Definition = thisVariable.Type.GetDefinition(), - Variables = new Dictionary(), + Variables = new Dictionary(), CaptureScope = (BlockContainer)nestedFunction.Body }; displayClasses.Add(thisVariable, displayClass); foreach (var stateMachineVariable in nestedFunction.Variables) { if (stateMachineVariable.StateMachineField == null || displayClass.Variables.ContainsKey(stateMachineVariable.StateMachineField)) continue; - displayClass.Variables.Add(stateMachineVariable.StateMachineField, new DisplayClassVariable { - Variable = stateMachineVariable, - Value = new LdLoc(stateMachineVariable) - }); + displayClass.Variables.Add(stateMachineVariable.StateMachineField, stateMachineVariable); } if (!currentFunction.Method.IsStatic && FindThisField(out var thisField)) { var thisVar = currentFunction.Variables @@ -200,7 +180,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms thisVar = new ILVariable(VariableKind.Parameter, decompilationContext.CurrentTypeDefinition, -1) { Name = "this" }; currentFunction.Variables.Add(thisVar); } - displayClass.Variables.Add(thisField, new DisplayClassVariable { Variable = thisVar, Value = new LdLoc(thisVar) }); + displayClass.Variables.Add(thisField, thisVar); } return true; @@ -263,11 +243,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } - bool IsDisplayClassLoad(ILInstruction target, out ILVariable variable) + ILFunction currentFunction; + + protected internal override void VisitILFunction(ILFunction function) { - if (target.MatchLdLoc(out variable) || target.MatchLdLoca(out variable)) - return true; - return false; + var oldFunction = this.currentFunction; + try { + this.currentFunction = function; + base.VisitILFunction(function); + } finally { + this.currentFunction = oldFunction; + } } protected override void Default(ILInstruction inst) @@ -280,105 +266,102 @@ namespace ICSharpCode.Decompiler.IL.Transforms protected internal override void VisitStLoc(StLoc inst) { base.VisitStLoc(inst); - // Sometimes display class references are copied into other local variables. - // We remove the assignment and store the relationship between the display class and the variable in the - // displayClasses dictionary. - if (inst.Value.MatchLdLoc(out var closureVariable) && displayClasses.TryGetValue(closureVariable, out var displayClass)) { - displayClasses[inst.Variable] = displayClass; - instructionsToRemove.Add(inst); - } else if (inst.Variable.Kind == VariableKind.Local && inst.Variable.IsSingleDefinition && inst.Variable.LoadCount == 0 && inst.Value is StLoc) { + + if (inst.Variable.Kind == VariableKind.Local && inst.Variable.IsSingleDefinition && inst.Variable.LoadCount == 0 && inst.Value is StLoc) { + context.Step($"Remove unused variable assignment {inst.Variable.Name}", inst); inst.ReplaceWith(inst.Value); } } protected internal override void VisitStObj(StObj inst) { - base.VisitStObj(inst); - // This instruction has been marked deletable, do not transform it further - if (instructionsToRemove.Contains(inst)) - return; - // The target of the store instruction must be a field reference - if (!inst.Target.MatchLdFlda(out ILInstruction target, out IField field)) - return; - // Get display class info - if (!IsDisplayClassLoad(target, out var displayClassLoad) || !displayClasses.TryGetValue(displayClassLoad, out var displayClass)) - return; - field = (IField)field.MemberDefinition; - if (displayClass.Variables.TryGetValue(field, out DisplayClassVariable info)) { - // If the display class field was previously initialized, we use a simple assignment. - inst.ReplaceWith(new StLoc(info.Variable, inst.Value).WithILRange(inst)); + inst.Value.AcceptVisitor(this); + if (IsParameterAssignment(inst, out var displayClass, out var field, out var parameter)) { + context.Step($"Detected parameter assignment {parameter.Name}", inst); + displayClass.Variables.Add(field, parameter); + instructionsToRemove.Add(inst); + } else if (IsDisplayClassAssignment(inst, out displayClass, out field, out var variable)) { + context.Step($"Detected display-class assignment {variable.Name}", inst); + displayClass.Variables.Add(field, variable); + instructionsToRemove.Add(inst); } else { - // This is an uninitialized variable: - ILInstruction value; - if (inst.Value.MatchLdLoc(out var v) && v.Kind == VariableKind.Parameter && currentFunction == v.Function) { - // Special case for parameters: remove copies of parameter values. - instructionsToRemove.Add(inst); - value = inst.Value; - } else { - context.Step($"Introduce captured variable for {field.FullName}", inst); - Debug.Assert(displayClass.Definition == field.DeclaringTypeDefinition); - // Introduce a fresh variable for the display class field. - if (displayClass.IsMono && displayClass.CaptureScope == null && !IsOuterClosureReference(field)) { - displayClass.CaptureScope = BlockContainer.FindClosestContainer(inst); - } - v = displayClass.DeclaringFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name); - v.HasInitialValue = true; - v.CaptureScope = displayClass.CaptureScope; - inst.ReplaceWith(new StLoc(v, inst.Value).WithILRange(inst)); - value = new LdLoc(v); - } - displayClass.Variables.Add(field, new DisplayClassVariable { Value = value, Variable = v }); + inst.Target.AcceptVisitor(this); + EarlyExpressionTransforms.StObjToStLoc(inst, context); } } protected internal override void VisitLdObj(LdObj inst) { base.VisitLdObj(inst); - // The target of the store instruction must be a field reference - if (!inst.Target.MatchLdFlda(out var target, out IField field)) - return; - // Get display class info - if (!IsDisplayClassLoad(target, out var displayClassLoad) || !displayClasses.TryGetValue(displayClassLoad, out var displayClass)) - return; - // Get display class variable info - if (!displayClass.Variables.TryGetValue((IField)field.MemberDefinition, out DisplayClassVariable info)) - return; - // Replace usage of display class field with the variable. - var replacement = info.Value.Clone(); - replacement.SetILRange(inst); - inst.ReplaceWith(replacement); + EarlyExpressionTransforms.LdObjToLdLoc(inst, context); + } + + private bool IsDisplayClassLoad(ILInstruction target, out ILVariable variable) + { + // We cannot use MatchLdLocRef here because local functions use ref parameters + if (target.MatchLdLoc(out variable) || target.MatchLdLoca(out variable)) + return true; + return false; + } + + private bool IsDisplayClassAssignment(StObj inst, out DisplayClass displayClass, out IField field, out ILVariable variable) + { + variable = null; + if (!IsDisplayClassFieldAccess(inst.Target, out var displayClassVar, out displayClass, out field)) + return false; + if (!(inst.Value.MatchLdLoc(out var v) && displayClasses.ContainsKey(v))) + return false; + if (displayClassVar.Function != currentFunction) + return false; + variable = v; + return true; + } + + private bool IsParameterAssignment(StObj inst, out DisplayClass displayClass, out IField field, out ILVariable parameter) + { + parameter = null; + if (!IsDisplayClassFieldAccess(inst.Target, out var displayClassVar, out displayClass, out field)) + return false; + if (!(inst.Value.MatchLdLoc(out var v) && v.Kind == VariableKind.Parameter && v.Function == currentFunction)) + return false; + if (displayClass.Variables.ContainsKey(field)) + return false; + if (displayClassVar.Function != currentFunction) + return false; + parameter = v; + return true; + } + + private bool IsDisplayClassFieldAccess(ILInstruction inst, out ILVariable displayClassVar, out DisplayClass displayClass, out IField field) + { + displayClass = null; + displayClassVar = null; + field = null; + if (!(inst is LdFlda ldflda)) + return false; + field = (IField)ldflda.Field.MemberDefinition; + return IsDisplayClassLoad(ldflda.Target, out displayClassVar) + && displayClasses.TryGetValue(displayClassVar, out displayClass); } protected internal override void VisitLdFlda(LdFlda inst) { base.VisitLdFlda(inst); - // TODO : Figure out why this was added in https://github.com/icsharpcode/ILSpy/pull/1303 - if (inst.Target.MatchLdThis() && inst.Field.Name == "$this" - && inst.Field.MemberDefinition.ReflectionName.Contains("c__Iterator")) { - //Debug.Assert(false, "This should not be executed!"); - var variable = currentFunction.Variables.First((f) => f.Index == -1); - inst.ReplaceWith(new LdLoca(variable).WithILRange(inst)); - } - // Skip stfld/ldfld - if (inst.Parent is LdObj || inst.Parent is StObj) - return; // Get display class info - if (!IsDisplayClassLoad(inst.Target, out var displayClassLoad) || !displayClasses.TryGetValue(displayClassLoad, out var displayClass)) + if (!IsDisplayClassFieldAccess(inst, out _, out DisplayClass displayClass, out IField field)) return; - var field = (IField)inst.Field.MemberDefinition; - if (!displayClass.Variables.TryGetValue(field, out DisplayClassVariable info)) { + if (!displayClass.Variables.TryGetValue(field, out var v)) { context.Step($"Introduce captured variable for {field.FullName}", inst); // Introduce a fresh variable for the display class field. Debug.Assert(displayClass.Definition == field.DeclaringTypeDefinition); - var v = displayClass.DeclaringFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name); + v = displayClass.DeclaringFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name); v.HasInitialValue = true; v.CaptureScope = displayClass.CaptureScope; inst.ReplaceWith(new LdLoca(v).WithILRange(inst)); - displayClass.Variables.Add(field, new DisplayClassVariable { Value = new LdLoc(v), Variable = v }); - } else if (info.Value is LdLoc l) { - inst.ReplaceWith(new LdLoca(l.Variable).WithILRange(inst)); + displayClass.Variables.Add(field, v); } else { - Debug.Fail("LdFlda pattern not supported!"); + context.Step($"Reuse captured variable {v.Name} for {field.FullName}", inst); + inst.ReplaceWith(new LdLoca(v).WithILRange(inst)); } } } diff --git a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs index be76244ed..91937943b 100644 --- a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs +++ b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs @@ -53,6 +53,13 @@ namespace ICSharpCode.Decompiler.Metadata Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages") }; + static readonly string[] RuntimePacks = new[] { + "Microsoft.NETCore.App", + "Microsoft.WindowsDesktop.App", + "Microsoft.AspNetCore.App", + "Microsoft.AspNetCore.All" + }; + readonly Dictionary packages; ISet packageBasePaths = new HashSet(StringComparer.Ordinal); readonly string assemblyName; @@ -124,13 +131,16 @@ namespace ICSharpCode.Decompiler.Metadata string FallbackToDotNetSharedDirectory(IAssemblyReference name, Version version) { - if (dotnetBasePath == null) return null; - var basePath = Path.Combine(dotnetBasePath, "shared", "Microsoft.NETCore.App"); - var closestVersion = GetClosestVersionFolder(basePath, version); - if (File.Exists(Path.Combine(basePath, closestVersion, name.Name + ".dll"))) { - return Path.Combine(basePath, closestVersion, name.Name + ".dll"); - } else if (File.Exists(Path.Combine(basePath, closestVersion, name.Name + ".exe"))) { - return Path.Combine(basePath, closestVersion, name.Name + ".exe"); + if (dotnetBasePath == null) + return null; + var basePaths = RuntimePacks.Select(pack => Path.Combine(dotnetBasePath, "shared", pack)); + foreach (var basePath in basePaths) { + var closestVersion = GetClosestVersionFolder(basePath, version); + if (File.Exists(Path.Combine(basePath, closestVersion, name.Name + ".dll"))) { + return Path.Combine(basePath, closestVersion, name.Name + ".dll"); + } else if (File.Exists(Path.Combine(basePath, closestVersion, name.Name + ".exe"))) { + return Path.Combine(basePath, closestVersion, name.Name + ".exe"); + } } return null; } diff --git a/ICSharpCode.Decompiler/Metadata/MetadataExtensions.cs b/ICSharpCode.Decompiler/Metadata/MetadataExtensions.cs index 561c25704..7d9d2ca5b 100644 --- a/ICSharpCode.Decompiler/Metadata/MetadataExtensions.cs +++ b/ICSharpCode.Decompiler/Metadata/MetadataExtensions.cs @@ -49,7 +49,7 @@ namespace ICSharpCode.Decompiler.Metadata return publicKeyTokenBytes.TakeLast(8).Reverse().ToHexString(8); } - public static string GetFullAssemblyName(this MetadataReader reader) + public static string GetPublicKeyToken(this MetadataReader reader) { if (!reader.IsAssembly) return string.Empty; @@ -59,6 +59,15 @@ namespace ICSharpCode.Decompiler.Metadata // AssemblyFlags.PublicKey does not apply to assembly definitions publicKey = CalculatePublicKeyToken(asm.PublicKey, reader); } + return publicKey; + } + + public static string GetFullAssemblyName(this MetadataReader reader) + { + if (!reader.IsAssembly) + return string.Empty; + var asm = reader.GetAssemblyDefinition(); + string publicKey = reader.GetPublicKeyToken(); return $"{reader.GetString(asm.Name)}, " + $"Version={asm.Version}, " + $"Culture={(asm.Culture.IsNil ? "neutral" : reader.GetString(asm.Culture))}, " + diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs index 04ce3ef8f..4ba6622bf 100644 --- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs +++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs @@ -84,7 +84,8 @@ namespace ICSharpCode.Decompiler.TypeSystem /// KeepModifiers = 0x40, /// - /// If this option is active, [IsReadOnlyAttribute] is removed and parameters are marked as in, structs as readonly. + /// If this option is active, [IsReadOnlyAttribute] on parameters+structs is removed + /// and parameters are marked as in, structs as readonly. /// Otherwise, the attribute is preserved but the parameters and structs are not marked. /// ReadOnlyStructsAndParameters = 0x80, @@ -104,10 +105,15 @@ namespace ICSharpCode.Decompiler.TypeSystem /// NullabilityAnnotations = 0x400, /// + /// If this option is active, [IsReadOnlyAttribute] on methods is removed + /// and the method marked as ThisIsRefReadOnly. + /// + ReadOnlyMethods = 0x800, + /// /// Default settings: typical options for the decompiler, with all C# languages features enabled. /// Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters - | RefStructs | UnmanagedConstraints | NullabilityAnnotations + | RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods } /// @@ -137,6 +143,8 @@ namespace ICSharpCode.Decompiler.TypeSystem typeSystemOptions |= TypeSystemOptions.UnmanagedConstraints; if (settings.NullableReferenceTypes) typeSystemOptions |= TypeSystemOptions.NullabilityAnnotations; + if (settings.ReadOnlyMethods) + typeSystemOptions |= TypeSystemOptions.ReadOnlyMethods; return typeSystemOptions; } diff --git a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs index c53d1cc60..ad1c177d6 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs @@ -40,6 +40,12 @@ namespace ICSharpCode.Decompiler.TypeSystem /// bool ReturnTypeIsRefReadOnly { get; } + /// + /// Gets whether the method accepts the 'this' reference as ref readonly. + /// This can be either because the method is C# 8.0 'readonly', or because it is within a C# 7.2 'readonly struct' + /// + bool ThisIsRefReadOnly { get; } + /// /// Gets the type parameters of this method; or an empty list if the method is not generic. /// diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs index 1bc076ad9..920106c37 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs @@ -201,7 +201,16 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation case "DecimalConstantAttribute": return (options & TypeSystemOptions.DecimalConstants) != 0 && (target == SymbolKind.Field || target == SymbolKind.Parameter); case "IsReadOnlyAttribute": - return (options & TypeSystemOptions.ReadOnlyStructsAndParameters) != 0; + switch (target) { + case SymbolKind.TypeDefinition: + case SymbolKind.Parameter: + return (options & TypeSystemOptions.ReadOnlyStructsAndParameters) != 0; + case SymbolKind.Method: + case SymbolKind.Accessor: + return (options & TypeSystemOptions.ReadOnlyMethods) != 0; + default: + return false; + } case "IsByRefLikeAttribute": return (options & TypeSystemOptions.RefStructs) != 0 && target == SymbolKind.TypeDefinition; case "IsUnmanagedAttribute": diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs index 02d439f84..2a11babee 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs @@ -133,6 +133,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation IEnumerable IMethod.GetReturnTypeAttributes() => EmptyList.Instance; bool IMethod.ReturnTypeIsRefReadOnly => false; + bool IMethod.ThisIsRefReadOnly => false; public IReadOnlyList TypeParameters { get; set; } = EmptyList.Instance; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs index 6a42b4364..cb53d51a6 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs @@ -117,6 +117,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation IEnumerable IEntity.GetAttributes() => baseMethod.GetAttributes(); IEnumerable IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes(); bool IMethod.ReturnTypeIsRefReadOnly => baseMethod.ReturnTypeIsRefReadOnly; + bool IMethod.ThisIsRefReadOnly => baseMethod.ThisIsRefReadOnly; /// /// We consider local functions as always static, because they do not have a "this parameter". /// Even local functions in instance methods capture this. diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs index d97382984..881933acd 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs @@ -49,6 +49,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation IParameter[] parameters; IType returnType; byte returnTypeIsRefReadonly = ThreeState.Unknown; + byte thisIsRefReadonly = ThreeState.Unknown; internal MetadataMethod(MetadataModule module, MethodDefinitionHandle handle) { @@ -424,6 +425,23 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation return hasReadOnlyAttr; } } + + public bool ThisIsRefReadOnly { + get { + if (thisIsRefReadonly != ThreeState.Unknown) { + return thisIsRefReadonly == ThreeState.True; + } + var metadata = module.metadata; + var methodDefinition = metadata.GetMethodDefinition(handle); + bool hasReadOnlyAttr = DeclaringTypeDefinition?.IsReadOnly ?? false; + if ((module.TypeSystemOptions & TypeSystemOptions.ReadOnlyMethods) != 0) { + hasReadOnlyAttr |= methodDefinition.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.IsReadOnly); + } + this.thisIsRefReadonly = ThreeState.From(hasReadOnlyAttr); + return hasReadOnlyAttr; + } + } + #endregion public Accessibility Accessibility => GetAccessibility(attributes); diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs index 311395fc4..c25ba6013 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs @@ -97,6 +97,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public IEnumerable GetReturnTypeAttributes() => methodDefinition.GetReturnTypeAttributes(); public bool ReturnTypeIsRefReadOnly => methodDefinition.ReturnTypeIsRefReadOnly; + bool IMethod.ThisIsRefReadOnly => methodDefinition.ThisIsRefReadOnly; + public IReadOnlyList TypeParameters { get { return specializedTypeParameters ?? methodDefinition.TypeParameters; diff --git a/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs b/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs index a151d1464..ecc0cbf22 100644 --- a/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs @@ -114,6 +114,7 @@ namespace ICSharpCode.Decompiler.TypeSystem IEnumerable IEntity.GetAttributes() => baseMethod.GetAttributes(); IEnumerable IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes(); bool IMethod.ReturnTypeIsRefReadOnly => baseMethod.ReturnTypeIsRefReadOnly; + bool IMethod.ThisIsRefReadOnly => baseMethod.ThisIsRefReadOnly; public IReadOnlyList TypeParameters { get { return baseMethod.TypeParameters; } diff --git a/ILSpy.AddIn/AssemblyFileFinder.cs b/ILSpy.AddIn/AssemblyFileFinder.cs index ee43801a6..66846a6cc 100644 --- a/ILSpy.AddIn/AssemblyFileFinder.cs +++ b/ILSpy.AddIn/AssemblyFileFinder.cs @@ -15,35 +15,13 @@ namespace ICSharpCode.ILSpy.AddIn { public static string FindAssemblyFile(AssemblyDefinition assemblyDefinition, string assemblyFile) { - var assemblyName = assemblyDefinition.Name; - - var detectedTargetFramework = DetectTargetFrameworkId(assemblyDefinition, assemblyFile); - if (string.IsNullOrEmpty(detectedTargetFramework)) { - // Without a target framework id it makes no sense to continue - return null; - } - - var targetFramework = detectedTargetFramework.Split(new[] { ",Version=v" }, StringSplitOptions.None); - string file = null; - switch (targetFramework[0]) { - case ".NETCoreApp": - case ".NETStandard": - if (targetFramework.Length != 2) - return FindAssemblyFromGAC(assemblyDefinition); - var version = targetFramework[1].Length == 3 ? new Version(targetFramework[1] + ".0") : new Version(targetFramework[1]); - var dotNetCorePathFinder = new DotNetCorePathFinder(assemblyFile, detectedTargetFramework, version); - file = dotNetCorePathFinder.TryResolveDotNetCore(Decompiler.Metadata.AssemblyNameReference.Parse(assemblyName.FullName)); - if (file != null) - return file; - return FindAssemblyFromGAC(assemblyDefinition); - default: - return FindAssemblyFromGAC(assemblyDefinition); + var assemblyResolver = new UniversalAssemblyResolver(assemblyFile, false, + DetectTargetFrameworkId(assemblyDefinition, assemblyFile)); + if (IsReferenceAssembly(assemblyDefinition, assemblyFile)) { + assemblyResolver.RemoveSearchDirectory(Path.GetDirectoryName(assemblyFile)); } - } - - static string FindAssemblyFromGAC(AssemblyDefinition assemblyDefinition) - { - return GacInterop.FindAssemblyInNetGac(Decompiler.Metadata.AssemblyNameReference.Parse(assemblyDefinition.Name.FullName)); + return assemblyResolver.FindAssemblyFile( + ICSharpCode.Decompiler.Metadata.AssemblyNameReference.Parse(assemblyDefinition.Name.FullName)); } static readonly string RefPathPattern = @"NuGetFallbackFolder[/\\][^/\\]+[/\\][^/\\]+[/\\]ref[/\\]"; @@ -60,7 +38,7 @@ namespace ICSharpCode.ILSpy.AddIn static readonly string DetectTargetFrameworkIdRefPathPattern = @"(Reference Assemblies[/\\]Microsoft[/\\]Framework[/\\](?<1>.NETFramework)[/\\]v(?<2>[^/\\]+)[/\\])" + - @"|(NuGetFallbackFolder[/\\](?<1>[^/\\]+)\\(?<2>[^/\\]+)([/\\].*)?[/\\]ref[/\\])"; + @"|((NuGetFallbackFolder|packs)[/\\](?<1>[^/\\]+)\\(?<2>[^/\\]+)([/\\].*)?[/\\]ref[/\\])"; public static string DetectTargetFrameworkId(AssemblyDefinition assembly, string assemblyPath = null) { @@ -85,6 +63,7 @@ namespace ICSharpCode.ILSpy.AddIn * * - .NETFramework -> C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\mscorlib.dll * - .NETCore -> C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.netcore.app\2.1.0\ref\netcoreapp2.1\System.Console.dll + * -> C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.0.0\ref\netcoreapp3.0\System.Runtime.Extensions.dll * - .NETStandard -> C:\Program Files\dotnet\sdk\NuGetFallbackFolder\netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll */ var pathMatch = Regex.Match(assemblyPath, DetectTargetFrameworkIdRefPathPattern, @@ -95,9 +74,9 @@ namespace ICSharpCode.ILSpy.AddIn if (type == ".NETFramework") { return $".NETFramework,Version=v{version}"; - } else if (type.Contains("netcore")) { + } else if (type.ToLower().Contains("netcore")) { return $".NETCoreApp,Version=v{version}"; - } else if (type.Contains("netstandard")) { + } else if (type.ToLower().Contains("netstandard")) { return $".NETStandard,Version=v{version}"; } } diff --git a/ILSpy.AddIn/Commands/NuGetReferenceForILSpy.cs b/ILSpy.AddIn/Commands/NuGetReferenceForILSpy.cs index bcbaa82a3..84ee74af5 100644 --- a/ILSpy.AddIn/Commands/NuGetReferenceForILSpy.cs +++ b/ILSpy.AddIn/Commands/NuGetReferenceForILSpy.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using EnvDTE; +using Microsoft.VisualStudio.Shell; namespace ICSharpCode.ILSpy.AddIn.Commands { @@ -26,6 +27,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands /// instance or null, if item is not a supported project. public static NuGetReferenceForILSpy Detect(object itemData) { + ThreadHelper.ThrowIfNotOnUIThread(); + if (itemData is ProjectItem projectItem) { var properties = Utils.GetProperties(projectItem.Properties, "Type"); if ((properties[0] as string) == "Package") { @@ -42,6 +45,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands /// Parameters object or null, if not applicable. public ILSpyParameters GetILSpyParameters() { + ThreadHelper.ThrowIfNotOnUIThread(); + var properties = Utils.GetProperties(projectItem.Properties, "Name", "Version", "Path"); if (properties[0] != null && properties[1] != null && properties[2] != null) { return new ILSpyParameters(new[] { $"{properties[2]}\\{properties[0]}.{properties[1]}.nupkg" }); diff --git a/ILSpy.AddIn/Commands/OpenCodeItemCommand.cs b/ILSpy.AddIn/Commands/OpenCodeItemCommand.cs index 0f26fa2d6..c98481363 100644 --- a/ILSpy.AddIn/Commands/OpenCodeItemCommand.cs +++ b/ILSpy.AddIn/Commands/OpenCodeItemCommand.cs @@ -23,6 +23,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands protected override void OnBeforeQueryStatus(object sender, EventArgs e) { + ThreadHelper.ThrowIfNotOnUIThread(); + if (sender is OleMenuCommand menuItem) { menuItem.Visible = false; @@ -46,6 +48,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands Document GetRoslynDocument() { + ThreadHelper.ThrowIfNotOnUIThread(); + var document = owner.DTE.ActiveDocument; var selection = (EnvDTE.TextPoint)((EnvDTE.TextSelection)document.Selection).ActivePoint; var id = owner.Workspace.CurrentSolution.GetDocumentIdsWithFilePath(document.FullName).FirstOrDefault(); @@ -57,18 +61,26 @@ namespace ICSharpCode.ILSpy.AddIn.Commands EnvDTE.TextPoint GetEditorSelection() { + ThreadHelper.ThrowIfNotOnUIThread(); + var document = owner.DTE.ActiveDocument; return ((EnvDTE.TextSelection)document.Selection).ActivePoint; } protected override async void OnExecute(object sender, EventArgs e) { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var textView = Utils.GetCurrentViewHost(owner)?.TextView; if (textView == null) return; SnapshotPoint caretPosition = textView.Caret.Position.BufferPosition; var roslynDocument = GetRoslynDocument(); + if (roslynDocument == null) { + owner.ShowMessage("This element is not analyzable in current view."); + return; + } var ast = await roslynDocument.GetSyntaxRootAsync().ConfigureAwait(false); var model = await roslynDocument.GetSemanticModelAsync().ConfigureAwait(false); var node = ast.FindNode(new TextSpan(caretPosition.Position, 0), false, true); @@ -89,6 +101,7 @@ namespace ICSharpCode.ILSpy.AddIn.Commands // Add our own project as well (not among references) var project = FindProject(owner.DTE.Solution.Projects.OfType(), roslynProject.FilePath); + if (project == null) { owner.ShowMessage(OLEMSGICON.OLEMSGICON_WARNING, "Can't show ILSpy for this code element!"); return; diff --git a/ILSpy.AddIn/Commands/OpenILSpyCommand.cs b/ILSpy.AddIn/Commands/OpenILSpyCommand.cs index 898393005..1d569193a 100644 --- a/ILSpy.AddIn/Commands/OpenILSpyCommand.cs +++ b/ILSpy.AddIn/Commands/OpenILSpyCommand.cs @@ -55,6 +55,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands protected void OpenAssembliesInILSpy(ILSpyParameters parameters) { + ThreadHelper.ThrowIfNotOnUIThread(); + if (parameters == null) return; @@ -71,25 +73,22 @@ namespace ICSharpCode.ILSpy.AddIn.Commands protected Dictionary GetReferences(Microsoft.CodeAnalysis.Project parentProject) { + ThreadHelper.ThrowIfNotOnUIThread(); + var dict = new Dictionary(); foreach (var reference in parentProject.MetadataReferences) { using (var assemblyDef = AssemblyDefinition.ReadAssembly(reference.Display)) { string assemblyName = assemblyDef.Name.Name; - if (AssemblyFileFinder.IsReferenceAssembly(assemblyDef, reference.Display)) { - string resolvedAssemblyFile = AssemblyFileFinder.FindAssemblyFile(assemblyDef, reference.Display); - dict.Add(assemblyName, - new DetectedReference(assemblyName, resolvedAssemblyFile, false)); - } else { - dict.Add(assemblyName, - new DetectedReference(assemblyName, reference.Display, false)); - } + string resolvedAssemblyFile = AssemblyFileFinder.FindAssemblyFile(assemblyDef, reference.Display); + dict.Add(assemblyName, + new DetectedReference(assemblyName, resolvedAssemblyFile, false)); } } foreach (var projectReference in parentProject.ProjectReferences) { var roslynProject = owner.Workspace.CurrentSolution.GetProject(projectReference.ProjectId); var project = FindProject(owner.DTE.Solution.Projects.OfType(), roslynProject.FilePath); if (roslynProject != null && project != null) - dict.Add(roslynProject.AssemblyName, + dict.Add(roslynProject.AssemblyName, new DetectedReference(roslynProject.AssemblyName, Utils.GetProjectOutputAssembly(project, roslynProject), true)); } return dict; @@ -97,17 +96,28 @@ namespace ICSharpCode.ILSpy.AddIn.Commands protected EnvDTE.Project FindProject(IEnumerable projects, string projectFile) { + ThreadHelper.ThrowIfNotOnUIThread(); + foreach (var project in projects) { - if (project.Kind == DTEConstants.vsProjectKindSolutionItems) { - // This is a solution folder -> search in sub-projects - var subProject = FindProject( - project.ProjectItems.OfType().Select(pi => pi.SubProject).OfType(), - projectFile); - if (subProject != null) - return subProject; - } else { - if (project.FileName == projectFile) - return project; + switch (project.Kind) { + case DTEConstants.vsProjectKindSolutionItems: + // This is a solution folder -> search in sub-projects + var subProject = FindProject( + project.ProjectItems.OfType().Select(pi => pi.SubProject).OfType(), + projectFile); + if (subProject != null) + return subProject; + break; + + case DTEConstants.vsProjectKindUnmodeled: + // Skip unloaded projects completely + break; + + default: + // Match by project's file name + if (project.FileName == projectFile) + return project; + break; } } diff --git a/ILSpy.AddIn/Commands/OpenProjectOutputCommand.cs b/ILSpy.AddIn/Commands/OpenProjectOutputCommand.cs index 8fd06a50c..937d1601a 100644 --- a/ILSpy.AddIn/Commands/OpenProjectOutputCommand.cs +++ b/ILSpy.AddIn/Commands/OpenProjectOutputCommand.cs @@ -17,6 +17,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands protected override void OnBeforeQueryStatus(object sender, EventArgs e) { + ThreadHelper.ThrowIfNotOnUIThread(); + if (sender is OleMenuCommand menuItem) { menuItem.Visible = false; @@ -27,6 +29,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands protected override void OnExecute(object sender, EventArgs e) { + ThreadHelper.ThrowIfNotOnUIThread(); + if (owner.DTE.SelectedItems.Count != 1) return; var projectItemWrapper = ProjectItemForILSpy.Detect(owner, owner.DTE.SelectedItems.Item(1)); diff --git a/ILSpy.AddIn/Commands/OpenReferenceCommand.cs b/ILSpy.AddIn/Commands/OpenReferenceCommand.cs index dd6ab3581..e97a2af35 100644 --- a/ILSpy.AddIn/Commands/OpenReferenceCommand.cs +++ b/ILSpy.AddIn/Commands/OpenReferenceCommand.cs @@ -22,6 +22,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands protected override void OnBeforeQueryStatus(object sender, EventArgs e) { + ThreadHelper.ThrowIfNotOnUIThread(); + if (sender is OleMenuCommand menuItem) { menuItem.Visible = false; @@ -44,6 +46,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands protected override void OnExecute(object sender, EventArgs e) { + ThreadHelper.ThrowIfNotOnUIThread(); + var itemObject = owner.GetSelectedItemsData().FirstOrDefault(); if (itemObject == null) return; diff --git a/ILSpy.AddIn/Commands/ProjectItemForILSpy.cs b/ILSpy.AddIn/Commands/ProjectItemForILSpy.cs index 9cc73346a..182565b35 100644 --- a/ILSpy.AddIn/Commands/ProjectItemForILSpy.cs +++ b/ILSpy.AddIn/Commands/ProjectItemForILSpy.cs @@ -1,6 +1,7 @@ using System.IO; using System.Linq; using EnvDTE; +using Microsoft.VisualStudio.Shell; namespace ICSharpCode.ILSpy.AddIn.Commands { @@ -27,6 +28,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands /// instance or null, if item is not a supported project. public static ProjectItemForILSpy Detect(ILSpyAddInPackage package, SelectedItem item) { + ThreadHelper.ThrowIfNotOnUIThread(); + var project = item.Project; var roslynProject = package.Workspace.CurrentSolution.Projects.FirstOrDefault(p => p.FilePath == project.FileName); if (roslynProject == null) @@ -42,6 +45,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands /// Parameters object or null, if not applicable. public ILSpyParameters GetILSpyParameters(ILSpyAddInPackage package) { + ThreadHelper.ThrowIfNotOnUIThread(); + return new ILSpyParameters(new[] { Utils.GetProjectOutputAssembly(project, roslynProject) }); } } diff --git a/ILSpy.AddIn/Commands/ProjectReferenceForILSpy.cs b/ILSpy.AddIn/Commands/ProjectReferenceForILSpy.cs index 743f29292..57f13d8a8 100644 --- a/ILSpy.AddIn/Commands/ProjectReferenceForILSpy.cs +++ b/ILSpy.AddIn/Commands/ProjectReferenceForILSpy.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using EnvDTE; +using Microsoft.VisualStudio.Shell; using Mono.Cecil; namespace ICSharpCode.ILSpy.AddIn.Commands @@ -31,6 +32,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands /// instance or null, if item is not a supported project. public static ProjectReferenceForILSpy Detect(object itemData) { + ThreadHelper.ThrowIfNotOnUIThread(); + if (itemData is ProjectItem projectItem) { var properties = Utils.GetProperties(projectItem.Properties, "FusionName", "ResolvedPath"); string fusionName = properties[0] as string; @@ -50,6 +53,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands /// Parameters object or null, if not applicable. public ILSpyParameters GetILSpyParameters(Dictionary projectReferences) { + ThreadHelper.ThrowIfNotOnUIThread(); + string fileName = projectItem.ContainingProject?.FileName; if (!string.IsNullOrEmpty(fileName)) { if (projectReferences.TryGetValue(projectItem.Name, out DetectedReference path)) { diff --git a/ILSpy.AddIn/ILSpy.AddIn.csproj b/ILSpy.AddIn/ILSpy.AddIn.csproj index c0d961a09..ca1bc52c7 100644 --- a/ILSpy.AddIn/ILSpy.AddIn.csproj +++ b/ILSpy.AddIn/ILSpy.AddIn.csproj @@ -44,27 +44,15 @@ - - + - - - - - - - - - - - @@ -129,8 +117,9 @@ - + + diff --git a/ILSpy.AddIn/ILSpyAddInPackage.cs b/ILSpy.AddIn/ILSpyAddInPackage.cs index aedbda510..533a78bdd 100644 --- a/ILSpy.AddIn/ILSpyAddInPackage.cs +++ b/ILSpy.AddIn/ILSpyAddInPackage.cs @@ -113,6 +113,10 @@ namespace ICSharpCode.ILSpy.AddIn ThreadHelper.ThrowIfNotOnUIThread(); IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell)); + if (uiShell == null) { + return 0; + } + Guid clsid = Guid.Empty; int result; Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure( @@ -136,6 +140,8 @@ namespace ICSharpCode.ILSpy.AddIn public IEnumerable GetSelectedItemsData() { + ThreadHelper.ThrowIfNotOnUIThread(); + if (DTE.ToolWindows.SolutionExplorer.SelectedItems is IEnumerable hierarchyItems) { foreach (var item in hierarchyItems) { if (item.Object is T typedItem) { diff --git a/ILSpy.AddIn/ILSpyInstance.cs b/ILSpy.AddIn/ILSpyInstance.cs index 0cc23f773..adf922328 100644 --- a/ILSpy.AddIn/ILSpyInstance.cs +++ b/ILSpy.AddIn/ILSpyInstance.cs @@ -42,7 +42,8 @@ namespace ICSharpCode.ILSpy.AddIn var process = new Process() { StartInfo = new ProcessStartInfo() { FileName = GetILSpyPath(), - UseShellExecute = false + UseShellExecute = false, + Arguments = "/navigateTo:none" } }; process.Start(); @@ -53,6 +54,7 @@ namespace ICSharpCode.ILSpy.AddIn } } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD110:Observe result of async calls", Justification = "")] void SendMessage(Process ilspyProcess, string message, bool activate) { // We wait asynchronously until target window can be found and try to find it multiple times diff --git a/ILSpy.AddIn/README.md b/ILSpy.AddIn/README.md new file mode 100644 index 000000000..7c363f311 --- /dev/null +++ b/ILSpy.AddIn/README.md @@ -0,0 +1,3 @@ +Test cases for various types of projects as well as corresponding test plans are located in a separate repository. + +[https://github.com/icsharpcode/ILSpy-Addin-tests](https://github.com/icsharpcode/ILSpy-Addin-tests) \ No newline at end of file diff --git a/ILSpy.AddIn/Utils.cs b/ILSpy.AddIn/Utils.cs index 20fa53e0b..74af2d797 100644 --- a/ILSpy.AddIn/Utils.cs +++ b/ILSpy.AddIn/Utils.cs @@ -8,6 +8,7 @@ using System.Text; using EnvDTE; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.LanguageServices; +using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.TextManager.Interop; @@ -148,6 +149,8 @@ namespace ICSharpCode.ILSpy.AddIn public static object[] GetProperties(EnvDTE.Properties properties, params string[] names) { + ThreadHelper.ThrowIfNotOnUIThread(); + var values = new object[names.Length]; foreach (object p in properties) { try { @@ -168,6 +171,8 @@ namespace ICSharpCode.ILSpy.AddIn public static List<(string, object)> GetAllProperties(EnvDTE.Properties properties) { + ThreadHelper.ThrowIfNotOnUIThread(); + var result = new List<(string, object)>(); for (int i = 0; i < properties.Count; i++) { try { @@ -207,6 +212,10 @@ namespace ICSharpCode.ILSpy.AddIn public static IWpfTextViewHost GetCurrentViewHost(IServiceProvider serviceProvider) { IVsTextManager txtMgr = (IVsTextManager)serviceProvider.GetService(typeof(SVsTextManager)); + if (txtMgr == null) { + return null; + } + IVsTextView vTextView = null; int mustHaveFocus = 1; txtMgr.GetActiveView(mustHaveFocus, null, out vTextView); @@ -235,6 +244,8 @@ namespace ICSharpCode.ILSpy.AddIn public static string GetProjectOutputAssembly(Project project, Microsoft.CodeAnalysis.Project roslynProject) { + ThreadHelper.ThrowIfNotOnUIThread(); + string outputFileName = Path.GetFileName(roslynProject.OutputFilePath); // Get the directory path based on the project file. diff --git a/ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj b/ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj index ee81f61fd..afaff06d1 100644 --- a/ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj +++ b/ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj @@ -32,12 +32,12 @@ - - + + diff --git a/ILSpy.Tests/ILSpy.Tests.csproj b/ILSpy.Tests/ILSpy.Tests.csproj index e2543cffc..32c81dd25 100644 --- a/ILSpy.Tests/ILSpy.Tests.csproj +++ b/ILSpy.Tests/ILSpy.Tests.csproj @@ -42,8 +42,8 @@ - - + + diff --git a/ILSpy/AboutPage.cs b/ILSpy/AboutPage.cs index ca85a1c0d..85d2b794a 100644 --- a/ILSpy/AboutPage.cs +++ b/ILSpy/AboutPage.cs @@ -19,7 +19,6 @@ using System; using System.ComponentModel; using System.ComponentModel.Composition; -using System.Diagnostics; using System.IO; using System.Linq; using System.Net; @@ -44,7 +43,7 @@ namespace ICSharpCode.ILSpy { [Import] DecompilerTextView decompilerTextView = null; - + public override void Execute(object parameter) { MainWindow.Instance.UnselectAll(); @@ -129,20 +128,19 @@ namespace ICSharpCode.ILSpy button.Cursor = Cursors.Arrow; stackPanel.Children.Add(button); - button.Click += delegate { + button.Click += async delegate { button.Content = Resources.Checking; button.IsEnabled = false; - GetLatestVersionAsync().ContinueWith( - delegate (Task task) { - try { - stackPanel.Children.Clear(); - ShowAvailableVersion(task.Result, stackPanel); - } catch (Exception ex) { - AvalonEditTextOutput exceptionOutput = new AvalonEditTextOutput(); - exceptionOutput.WriteLine(ex.ToString()); - textView.ShowText(exceptionOutput); - } - }, TaskScheduler.FromCurrentSynchronizationContext()); + + try { + AvailableVersionInfo vInfo = await GetLatestVersionAsync(); + stackPanel.Children.Clear(); + ShowAvailableVersion(vInfo, stackPanel); + } catch (Exception ex) { + AvalonEditTextOutput exceptionOutput = new AvalonEditTextOutput(); + exceptionOutput.WriteLine(ex.ToString()); + textView.ShowText(exceptionOutput); + } }; } @@ -183,36 +181,25 @@ namespace ICSharpCode.ILSpy } } - static Task GetLatestVersionAsync() + static async Task GetLatestVersionAsync() { - var tcs = new TaskCompletionSource(); - new Action(() => { - WebClient wc = new WebClient(); - IWebProxy systemWebProxy = WebRequest.GetSystemWebProxy(); - systemWebProxy.Credentials = CredentialCache.DefaultCredentials; - wc.Proxy = systemWebProxy; - wc.DownloadDataCompleted += delegate(object sender, DownloadDataCompletedEventArgs e) { - if (e.Error != null) { - tcs.SetException(e.Error); - } else { - try { - XDocument doc = XDocument.Load(new MemoryStream(e.Result)); - var bands = doc.Root.Elements("band"); - var currentBand = bands.FirstOrDefault(b => (string)b.Attribute("id") == band) ?? bands.First(); - Version version = new Version((string)currentBand.Element("latestVersion")); - string url = (string)currentBand.Element("downloadUrl"); - if (!(url.StartsWith("http://", StringComparison.Ordinal) || url.StartsWith("https://", StringComparison.Ordinal))) - url = null; // don't accept non-urls - latestAvailableVersion = new AvailableVersionInfo { Version = version, DownloadUrl = url }; - tcs.SetResult(latestAvailableVersion); - } catch (Exception ex) { - tcs.SetException(ex); - } - } - }; - wc.DownloadDataAsync(UpdateUrl); - }).BeginInvoke(null, null); - return tcs.Task; + WebClient wc = new WebClient(); + IWebProxy systemWebProxy = WebRequest.GetSystemWebProxy(); + systemWebProxy.Credentials = CredentialCache.DefaultCredentials; + wc.Proxy = systemWebProxy; + + string data = await wc.DownloadStringTaskAsync(UpdateUrl); + + XDocument doc = XDocument.Load(new StringReader(data)); + var bands = doc.Root.Elements("band"); + var currentBand = bands.FirstOrDefault(b => (string)b.Attribute("id") == band) ?? bands.First(); + Version version = new Version((string)currentBand.Element("latestVersion")); + string url = (string)currentBand.Element("downloadUrl"); + if (!(url.StartsWith("http://", StringComparison.Ordinal) || url.StartsWith("https://", StringComparison.Ordinal))) + url = null; // don't accept non-urls + + latestAvailableVersion = new AvailableVersionInfo { Version = version, DownloadUrl = url }; + return latestAvailableVersion; } sealed class AvailableVersionInfo @@ -274,9 +261,7 @@ namespace ICSharpCode.ILSpy void OnPropertyChanged(string propertyName) { - if (PropertyChanged != null) { - PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); - } + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } @@ -285,10 +270,10 @@ namespace ICSharpCode.ILSpy /// Returns the download URL if an update is available. /// Returns null if no update is available, or if no check was performed. /// - public static Task CheckForUpdatesIfEnabledAsync(ILSpySettings spySettings) + public static async Task CheckForUpdatesIfEnabledAsync(ILSpySettings spySettings) { - var tcs = new TaskCompletionSource(); UpdateSettings s = new UpdateSettings(spySettings); + // If we're in an MSIX package, updates work differently if (s.AutomaticUpdateCheckEnabled && !WindowsVersionHelper.HasPackageIdentity) { // perform update check if we never did one before; @@ -297,40 +282,34 @@ namespace ICSharpCode.ILSpy || s.LastSuccessfulUpdateCheck < DateTime.UtcNow.AddDays(-7) || s.LastSuccessfulUpdateCheck > DateTime.UtcNow) { - CheckForUpdateInternal(tcs, s); + return await CheckForUpdateInternal(s); } else { - tcs.SetResult(null); + return null; } } else { - tcs.SetResult(null); + return null; } - return tcs.Task; } public static Task CheckForUpdatesAsync(ILSpySettings spySettings) { - var tcs = new TaskCompletionSource(); UpdateSettings s = new UpdateSettings(spySettings); - CheckForUpdateInternal(tcs, s); - return tcs.Task; + return CheckForUpdateInternal(s); } - static void CheckForUpdateInternal(TaskCompletionSource tcs, UpdateSettings s) + static async Task CheckForUpdateInternal(UpdateSettings s) { - GetLatestVersionAsync().ContinueWith( - delegate (Task task) { - try { - s.LastSuccessfulUpdateCheck = DateTime.UtcNow; - AvailableVersionInfo v = task.Result; - if (v.Version > currentVersion) - tcs.SetResult(v.DownloadUrl); - else - tcs.SetResult(null); - } catch (AggregateException) { - // ignore errors getting the version info - tcs.SetResult(null); - } - }); + try { + var v = await GetLatestVersionAsync(); + s.LastSuccessfulUpdateCheck = DateTime.UtcNow; + if (v.Version > currentVersion) + return v.DownloadUrl; + else + return null; + } catch (Exception) { + // ignore errors getting the version info + return null; + } } } diff --git a/ILSpy/Analyzers/AnalyzerTreeView.cs b/ILSpy/Analyzers/AnalyzerTreeView.cs index 54256935e..13b29bd3f 100644 --- a/ILSpy/Analyzers/AnalyzerTreeView.cs +++ b/ILSpy/Analyzers/AnalyzerTreeView.cs @@ -73,7 +73,7 @@ namespace ICSharpCode.ILSpy.Analyzers public void Show() { if (!IsVisible) - MainWindow.Instance.ShowInBottomPane("Analyzer", this); + MainWindow.Instance.ShowInNewPane("Analyzer", this, PanePosition.Bottom); } public void Show(AnalyzerTreeNode node) diff --git a/ILSpy/Analyzers/RemoveAnalyzeContextMenuEntry.cs b/ILSpy/Analyzers/RemoveAnalyzeContextMenuEntry.cs index 177dcc7c6..48a2d592e 100644 --- a/ILSpy/Analyzers/RemoveAnalyzeContextMenuEntry.cs +++ b/ILSpy/Analyzers/RemoveAnalyzeContextMenuEntry.cs @@ -20,7 +20,7 @@ using System.Linq; namespace ICSharpCode.ILSpy.Analyzers { - [ExportContextMenuEntry(Header = "Remove", Icon = "images/Delete.png", Category = "Analyze", Order = 200)] + [ExportContextMenuEntry(Header = "Remove", Icon = "images/Delete", Category = "Analyze", Order = 200)] internal sealed class RemoveAnalyzeContextMenuEntry : IContextMenuEntry { public bool IsVisible(TextViewContext context) diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs index 6ff97a8ab..1417af930 100644 --- a/ILSpy/App.xaml.cs +++ b/ILSpy/App.xaml.cs @@ -94,7 +94,7 @@ namespace ICSharpCode.ILSpy var name = Path.GetFileNameWithoutExtension(plugin); try { var asm = Assembly.Load(name); - var parts = discovery.CreatePartsAsync(asm).Result; + var parts = discovery.CreatePartsAsync(asm).GetAwaiter().GetResult(); catalog = catalog.AddParts(parts); } catch (Exception ex) { StartupExceptions.Add(new ExceptionData { Exception = ex, PluginName = name }); @@ -102,7 +102,7 @@ namespace ICSharpCode.ILSpy } } // Add the built-in parts - catalog = catalog.AddParts(discovery.CreatePartsAsync(Assembly.GetExecutingAssembly()).Result); + catalog = catalog.AddParts(discovery.CreatePartsAsync(Assembly.GetExecutingAssembly()).GetAwaiter().GetResult()); // If/When the project switches to .NET Standard/Core, this will be needed to allow metadata interfaces (as opposed // to metadata classes). When running on .NET Framework, it's automatic. // catalog.WithDesktopSupport(); diff --git a/ILSpy/Commands/CheckForUpdatesCommand.cs b/ILSpy/Commands/CheckForUpdatesCommand.cs index 7cae660d9..00480cc9f 100644 --- a/ILSpy/Commands/CheckForUpdatesCommand.cs +++ b/ILSpy/Commands/CheckForUpdatesCommand.cs @@ -34,9 +34,9 @@ namespace ICSharpCode.ILSpy return base.CanExecute(parameter); } - public override void Execute(object parameter) + public override async void Execute(object parameter) { - MainWindow.Instance.ShowMessageIfUpdatesAvailableAsync(ILSpySettings.Load(), forceCheck: true); + await MainWindow.Instance.ShowMessageIfUpdatesAvailableAsync(ILSpySettings.Load(), forceCheck: true); } } } diff --git a/ILSpy/Commands/DecompileInNewViewCommand.cs b/ILSpy/Commands/DecompileInNewViewCommand.cs new file mode 100644 index 000000000..3b335743d --- /dev/null +++ b/ILSpy/Commands/DecompileInNewViewCommand.cs @@ -0,0 +1,52 @@ +// 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 System.Linq; +using ICSharpCode.ILSpy.Properties; +using ICSharpCode.ILSpy.TextView; +using ICSharpCode.ILSpy.TreeNodes; + +namespace ICSharpCode.ILSpy.Commands +{ + // [ExportContextMenuEntry(Header = nameof(Resources.DecompileToNewPanel), Icon = "images/Search", Category = nameof(Resources.Analyze), Order = 90)] + internal sealed class DecompileInNewViewCommand : IContextMenuEntry + { + public bool IsVisible(TextViewContext context) + { + if (context.SelectedTreeNodes == null) + return false; + return true; + } + + public bool IsEnabled(TextViewContext context) + { + if (context.SelectedTreeNodes == null) + return false; + return true; + } + + public async void Execute(TextViewContext context) + { + var dtv = new DecompilerTextView(); + var nodes = context.SelectedTreeNodes.Cast().ToArray(); + var title = string.Join(", ", nodes.Select(x => x.ToString())); + MainWindow.Instance.ShowInNewPane(title, dtv, PanePosition.Document); + await dtv.DecompileAsync(MainWindow.Instance.CurrentLanguage, nodes, new DecompilationOptions()); + } + } +} diff --git a/ILSpy/ContextMenuEntry.cs b/ILSpy/ContextMenuEntry.cs index ce1f104d6..d04155f43 100644 --- a/ILSpy/ContextMenuEntry.cs +++ b/ILSpy/ContextMenuEntry.cs @@ -78,7 +78,7 @@ namespace ICSharpCode.ILSpy if (textView != null) reference = textView.GetReferenceSegmentAtMousePosition(); else if (listBox?.SelectedItem is SearchResult result) - reference = new ReferenceSegment { Reference = result.Member }; + reference = new ReferenceSegment { Reference = result.Reference }; else reference = null; var position = textView != null ? textView.GetPositionFromMousePosition() : null; @@ -222,15 +222,11 @@ namespace ICSharpCode.ILSpy menuItem.Header = MainWindow.GetResourceString( entryPair.Metadata.Header); menuItem.InputGestureText = entryPair.Metadata.InputGestureText; if (!string.IsNullOrEmpty(entryPair.Metadata.Icon)) { - object image = Images.Load(entryPair.Value, entryPair.Metadata.Icon); - if (!(image is Viewbox)) { - image = new Image { - Width = 16, - Height = 16, - Source = (ImageSource)image - }; - } - menuItem.Icon = image; + menuItem.Icon = new Image { + Width = 16, + Height = 16, + Source = Images.Load(entryPair.Value, entryPair.Metadata.Icon) + }; } if (entryPair.Value.IsEnabled(context)) { menuItem.Click += delegate { entry.Execute(context); }; diff --git a/ILSpy/Controls/DockedPane.cs b/ILSpy/Controls/DockedPane.cs deleted file mode 100644 index 6f370ce95..000000000 --- a/ILSpy/Controls/DockedPane.cs +++ /dev/null @@ -1,76 +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 System; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; - -namespace ICSharpCode.ILSpy.Controls -{ - class DockedPane : Control - { - static DockedPane() - { - DefaultStyleKeyProperty.OverrideMetadata(typeof(DockedPane), new FrameworkPropertyMetadata(typeof(DockedPane))); - } - - public static readonly DependencyProperty TitleProperty = - DependencyProperty.Register("Title", typeof(string), typeof(DockedPane)); - - public string Title { - get { return (string)GetValue(TitleProperty); } - set { SetValue(TitleProperty, value); } - } - - public static readonly DependencyProperty ContentProperty = - DependencyProperty.Register("Content", typeof(object), typeof(DockedPane)); - - public object Content { - get { return GetValue(ContentProperty); } - set { SetValue(ContentProperty, value); } - } - - public override void OnApplyTemplate() - { - base.OnApplyTemplate(); - Button closeButton = (Button)this.Template.FindName("PART_Close", this); - if (closeButton != null) { - closeButton.Click += closeButton_Click; - } - } - - void closeButton_Click(object sender, RoutedEventArgs e) - { - if (CloseButtonClicked != null) - CloseButtonClicked(this, e); - } - - public event EventHandler CloseButtonClicked; - - protected override void OnKeyDown(KeyEventArgs e) - { - base.OnKeyDown(e); - if (e.Key == Key.F4 && e.KeyboardDevice.Modifiers == ModifierKeys.Control || e.Key == Key.Escape) { - if (CloseButtonClicked != null) - CloseButtonClicked(this, e); - e.Handled = true; - } - } - } -} diff --git a/ILSpy/DebugSteps.xaml.cs b/ILSpy/DebugSteps.xaml.cs index 131530740..ba13f1d84 100644 --- a/ILSpy/DebugSteps.xaml.cs +++ b/ILSpy/DebugSteps.xaml.cs @@ -79,7 +79,7 @@ namespace ICSharpCode.ILSpy public static void Show() { - MainWindow.Instance.ShowInTopPane(Properties.Resources.DebugSteps, new DebugSteps()); + MainWindow.Instance.ShowInNewPane(Properties.Resources.DebugSteps, new DebugSteps(), PanePosition.Top); } void IPane.Closed() diff --git a/ILSpy/Docking/DockLayoutSettings.cs b/ILSpy/Docking/DockLayoutSettings.cs new file mode 100644 index 000000000..737910f6f --- /dev/null +++ b/ILSpy/Docking/DockLayoutSettings.cs @@ -0,0 +1,50 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using Xceed.Wpf.AvalonDock.Layout.Serialization; + +namespace ICSharpCode.ILSpy.Docking +{ + public class DockLayoutSettings + { + private string rawSettings; + + public bool Valid => rawSettings != null; + + public DockLayoutSettings(XElement element) + { + if ((element != null) && element.HasElements) { + rawSettings = element.Elements().FirstOrDefault()?.ToString(); + } + } + + public XElement SaveAsXml() + { + try { + return XElement.Parse(rawSettings); + } catch (Exception) { + return null; + } + } + + public void Deserialize(XmlLayoutSerializer serializer) + { + if (!Valid) + return; + + using (StringReader reader = new StringReader(rawSettings)) { + serializer.Deserialize(reader); + } + } + + public void Serialize(XmlLayoutSerializer serializer) + { + using (StringWriter fs = new StringWriter()) { + serializer.Serialize(fs); + rawSettings = fs.ToString(); + } + } + } +} diff --git a/ILSpy/Docking/DockingHelper.cs b/ILSpy/Docking/DockingHelper.cs new file mode 100644 index 000000000..157837a6c --- /dev/null +++ b/ILSpy/Docking/DockingHelper.cs @@ -0,0 +1,32 @@ +using System.Windows; +using System.Windows.Controls; +using Xceed.Wpf.AvalonDock.Layout; + +namespace ICSharpCode.ILSpy.Docking +{ + public static class DockingHelper + { + public static void DockHorizontal(LayoutContent layoutContent, ILayoutElement paneRelativeTo, GridLength dockHeight, bool dockBefore = false) + { + if (paneRelativeTo is ILayoutDocumentPane parentDocumentPane) { + var parentDocumentGroup = paneRelativeTo.FindParent(); + if (parentDocumentGroup == null) { + var grandParent = parentDocumentPane.Parent as ILayoutContainer; + parentDocumentGroup = new LayoutDocumentPaneGroup() { Orientation = System.Windows.Controls.Orientation.Vertical }; + grandParent.ReplaceChild(paneRelativeTo, parentDocumentGroup); + parentDocumentGroup.Children.Add(parentDocumentPane); + } + parentDocumentGroup.Orientation = System.Windows.Controls.Orientation.Vertical; + int indexOfParentPane = parentDocumentGroup.IndexOfChild(parentDocumentPane); + var layoutDocumentPane = new LayoutDocumentPane(layoutContent) { DockHeight = dockHeight }; + parentDocumentGroup.InsertChildAt(dockBefore ? indexOfParentPane : indexOfParentPane + 1, layoutDocumentPane); + layoutContent.IsActive = true; + layoutContent.Root.CollectGarbage(); + Application.Current.MainWindow.Dispatcher.Invoke(() => { + + layoutDocumentPane.DockHeight = dockHeight; + }, System.Windows.Threading.DispatcherPriority.Loaded); + } + } + } +} diff --git a/ILSpy/Docking/PanePosition.cs b/ILSpy/Docking/PanePosition.cs new file mode 100644 index 000000000..d162d5d7b --- /dev/null +++ b/ILSpy/Docking/PanePosition.cs @@ -0,0 +1,9 @@ +namespace ICSharpCode.ILSpy +{ + public enum PanePosition + { + Top, + Bottom, + Document + } +} diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index ad27aafa4..29b7469e5 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -49,8 +49,10 @@ - - + + + + @@ -100,6 +102,7 @@ + @@ -123,7 +126,6 @@ ResourceObjectTable.xaml - @@ -137,6 +139,8 @@ DebugSteps.xaml + + @@ -163,11 +167,14 @@ + True True Resources.resx + + @@ -205,11 +212,13 @@ + SearchPane.xaml + diff --git a/ILSpy/Images/Copy.xaml b/ILSpy/Images/Copy.xaml index 155cead10..1ec44e951 100644 --- a/ILSpy/Images/Copy.xaml +++ b/ILSpy/Images/Copy.xaml @@ -1,20 +1,10 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + diff --git a/ILSpy/Images/README.md b/ILSpy/Images/README.md index 0bf5fadf4..e258db488 100644 --- a/ILSpy/Images/README.md +++ b/ILSpy/Images/README.md @@ -14,7 +14,7 @@ Icons used in ILSpy: | Constructor | x | x | based on VS 2017 Icon Pack (Method) using a different colour | | | Copy | x | x | VS 2017 Icon Pack (Copy) | | | Delegate | x | x | VS 2017 Icon Pack (Delegate) | | -| Delete | x | x | | | +| Delete | x | x | VS 2017 Icon Pack (Remove_color) | | | Enum | x | x | VS 2017 Icon Pack (Enumerator) | | | EnumValue | x | x | VS 2017 Icon Pack (EnumItem) | | | Event | x | x | VS 2017 Icon Pack (Event) | | @@ -45,10 +45,10 @@ Icons used in ILSpy: | Property | x | x | VS 2017 Icon Pack (Property) | | | ReferenceFolder | x | x | combined VS 2017 Icon Pack (Reference) two times | | | Refresh | x | x | VS 2017 Icon Pack (Refresh) | | -| Resource | x | x | | | -| ResourceImage | x | x | | | -| ResourceResourcesFile | x | x | | | -| ResourceXml | x | x | | | +| Resource | x | x | VS 2017 Icon Pack (Document) | | +| ResourceImage | x | x | VS 2017 Icon Pack (Image) | | +| ResourceResourcesFile | x | x | VS 2017 Icon Pack (LocalResources) | | +| ResourceXml | x | x | VS 2017 Icon Pack (XMLFile) | | | ResourceXsd | x | x | combined VS 2017 Icon Pack (XMLSchema) with the "file symbol in ResourceXslt | | | ResourceXsl | x | x | VS 2017 Icon Pack (XMLTransformation) | | | ResourceXslt | x | x | VS 2017 Icon Pack (XSLTTemplate) | | @@ -57,12 +57,12 @@ Icons used in ILSpy: | SearchMsdn | x | x | based on VS 2017 Icon Pack (Search) + Microsoft Logo | | | ShowAll | x | x | combined PublicOnly, OverlayPrivate, OverlayProtected, OverlayInternal | | | ShowPrivateInternal | x | x | combined OverlayPrivate and OverlayInternal | | -| ShowPublicOnly | x | x | | | +| ShowPublicOnly | x | x | VS 2017 Icon Pack (Type) | | | Sort | x | x | VS 2017 Icon Pack (SortAscending) | | | Struct | x | x | VS 2017 Icon Pack (Structure) | | -| SubTypes | x | x | | | -| SuperTypes | x | x | | | -| ViewCode | x | x | | | +| SubTypes | x | x | based on VS 2017 Icon Pack (BaseType) rotated +90° | | +| SuperTypes | x | x | based on VS 2017 Icon Pack (BaseType) rotated -90° | | +| ViewCode | x | x | VS 2017 Icon Pack (GoToSourceCode) | | | VirtualMethod | x | x | combined VS 2017 Icon Pack (Method) two times | | | Warning | x | x | VS 2017 Icon Pack (StatusWarning) | | 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; diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs index af67109cd..d4d697f89 100644 --- a/ILSpy/LoadedAssembly.cs +++ b/ILSpy/LoadedAssembly.cs @@ -87,7 +87,7 @@ namespace ICSharpCode.ILSpy public PEFile GetPEFileOrNull() { try { - return GetPEFileAsync().Result; + return GetPEFileAsync().GetAwaiter().GetResult(); } catch (Exception ex) { System.Diagnostics.Trace.TraceError(ex.ToString()); return null; diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index c8846031e..e34781457 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -4,7 +4,7 @@ x:ClassModifier="public" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:tv="clr-namespace:ICSharpCode.TreeView;assembly=ICSharpCode.TreeView" xmlns:local="clr-namespace:ICSharpCode.ILSpy" - xmlns:textView="clr-namespace:ICSharpCode.ILSpy.TextView" + xmlns:avalondock="http://schemas.xceed.com/wpf/xaml/avalondock" xmlns:controls="clr-namespace:ICSharpCode.ILSpy.Controls" xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties" Title="ILSpy" @@ -51,7 +51,8 @@ - + + @@ -96,6 +97,15 @@ ItemsSource="{Binding SelectedItem.LanguageVersions, ElementName=languageComboBox, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding FilterSettings.LanguageVersion, UpdateSourceTrigger=PropertyChanged}"/> + + + + + + - - - - - - - - - - - - - \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index c3efe5a38..bfb05a141 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,6 @@ configuration: image: Visual Studio 2019 install: -- cmd: choco install dotnetcore-sdk --pre - git submodule update --init --recursive - ps: .\BuildTools\appveyor-install.ps1 @@ -43,7 +42,7 @@ for: - branches: only: - master - - 3.2.x + - 5.0.x artifacts: - path: ILSpy_binaries.zip name: ILSpy %APPVEYOR_REPO_BRANCH% %ILSPY_VERSION_NUMBER% binaries diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e14de2b42..3c1755b9a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,11 +1,10 @@ trigger: - master -- msix -- 3.2.x +- 5.0.x pr: - master -- 3.2.x +- 5.0.x variables: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true @@ -40,7 +39,7 @@ jobs: - task: DotNetCoreInstaller@0 inputs: - version: '3.0.100-preview9-014004' + version: '3.0.100' - powershell: .\BuildTools\pipelines-install.ps1 displayName: Install diff --git a/global.json b/global.json index 05c896433..6ba836210 100644 --- a/global.json +++ b/global.json @@ -3,6 +3,6 @@ "MSBuild.Sdk.Extras": "2.0.24" }, "sdk": { - "version": "3.0.100-rc1" + "version": "3.0.100" } }