diff --git a/BuildTools/sort-resx.ps1 b/BuildTools/sort-resx.ps1 new file mode 100644 index 000000000..d85d46849 --- /dev/null +++ b/BuildTools/sort-resx.ps1 @@ -0,0 +1,18 @@ +$ErrorActionPreference = "Stop"; + +[Reflection.Assembly]::LoadWithPartialName("System.Xml.Linq") | Out-Null + +Write-Host "Sorting .resx files..."; + +Get-ChildItem -Include *.resx -Recurse | foreach ($_) { + Write-Host $_.FullName; + + $doc = [System.Xml.Linq.XDocument]::Load($_.FullName); + $descendants = [System.Linq.Enumerable]::ToArray($doc.Descendants("data")); + + [System.Xml.Linq.Extensions]::Remove($descendants); + $ordered = [System.Linq.Enumerable]::OrderBy($descendants, [System.Func[System.Xml.Linq.XElement,string]] { param ($e) $e.Attribute("name").Value }, [System.StringComparer]::Ordinal); + $doc.Root.Add($ordered); + $doc.Save($_.FullName); +} + diff --git a/DecompilerNuGetDemos.workbook b/DecompilerNuGetDemos.workbook index d5ccd0ea1..a71208905 100644 --- a/DecompilerNuGetDemos.workbook +++ b/DecompilerNuGetDemos.workbook @@ -6,7 +6,7 @@ platforms: - DotNetCore packages: - id: ICSharpCode.Decompiler - version: 6.0.0.5836 + version: 6.1.0.5902 --- Setup: load the references required to work with the decompiler diff --git a/ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj b/ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj index f4e9b1467..bf463c7db 100644 --- a/ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj +++ b/ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj @@ -8,7 +8,7 @@ true ilspycmd ilspycmd - 6.0.0.5836 + 6.1.0.5902 Command-line decompiler using the ILSpy decompilation engine Copyright 2011-2020 AlphaSierraPapa https://github.com/icsharpcode/ILSpy/ @@ -16,8 +16,8 @@ ILSpyCmdNuGetPackageIcon.png https://github.com/icsharpcode/ILSpy/ - 6.0.0.0 - 6.0.0.0 + 6.1.0.0 + 6.1.0.0 true ILSpy Team @@ -42,7 +42,7 @@ - + diff --git a/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj b/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj index 1db847e93..5028997c6 100644 --- a/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj +++ b/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj @@ -17,7 +17,7 @@ - + diff --git a/ICSharpCode.Decompiler.Tests/DataFlowTest.cs b/ICSharpCode.Decompiler.Tests/DataFlowTest.cs index 3c599ccc2..8c49c3949 100644 --- a/ICSharpCode.Decompiler.Tests/DataFlowTest.cs +++ b/ICSharpCode.Decompiler.Tests/DataFlowTest.cs @@ -55,10 +55,14 @@ namespace ICSharpCode.Decompiler.Tests public void TryFinallyWithAssignmentInFinally() { ILVariable v = new ILVariable(VariableKind.Local, SpecialType.UnknownType, 0); - ILFunction f = new ILFunction((IMethod)null, 0, new GenericContext(), new TryFinally( - new Nop(), - new StLoc(v, new LdcI4(0)) - )); + ILFunction f = new ILFunction( + returnType: SpecialType.UnknownType, + parameters: new IParameter[0], + genericContext: new GenericContext(), + body: new TryFinally( + new Nop(), + new StLoc(v, new LdcI4(0)) + )); f.AddRef(); f.Variables.Add(v); f.Body.AcceptVisitor(new RDTest(f, v)); diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 1dc72a59f..6bc3c6a9f 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -185,7 +185,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers return Regex.Replace(il, @"'\{[0-9A-F-]+\}'", "''"); } - static readonly string coreRefAsmPath = new DotNetCorePathFinder(new Version(3, 1)).GetReferenceAssemblyPath(".NETCoreApp, Version = v3.1"); + static readonly string coreRefAsmPath = new DotNetCorePathFinder(TargetFrameworkIdentifier.NETCoreApp, new Version(3, 1)).GetReferenceAssemblyPath(".NETCoreApp, Version = v3.1"); static readonly string refAsmPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2"); @@ -251,6 +251,10 @@ namespace ICSharpCode.Decompiler.Tests.Helpers preprocessorSymbols.Add("VB11"); preprocessorSymbols.Add("VB14"); preprocessorSymbols.Add("VB15"); + + if (flags.HasFlag(CompilerOptions.Preview)) { + preprocessorSymbols.Add("CS90"); + } } else if (flags.HasFlag(CompilerOptions.UseMcs)) { preprocessorSymbols.Add("MCS"); } else { diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index c8e8d2721..62b9b5535 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -42,8 +42,8 @@ - - + + @@ -92,6 +92,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index d9a9e2ede..cd864eab9 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -127,13 +127,13 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void CS1xSwitch_Debug() { - Run(); + Run(settings: new DecompilerSettings { SwitchExpressions = false }); } [Test] public void CS1xSwitch_Release() { - Run(); + Run(settings: new DecompilerSettings { SwitchExpressions = false }); } [Test] diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index c5687aa85..fa6fc7ccf 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -135,10 +135,17 @@ namespace ICSharpCode.Decompiler.Tests { RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { // legacy csc generates a dead store in debug builds - RemoveDeadStores = (cscOptions == CompilerOptions.None) + RemoveDeadStores = (cscOptions == CompilerOptions.None), + SwitchExpressions = false, }); } + [Test] + public void SwitchExpressions([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) + { + RunForLibrary(cscOptions: cscOptions); + } + [Test] public void ReduceNesting([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { @@ -148,7 +155,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void DelegateConstruction([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) { - RunForLibrary(cscOptions: cscOptions); + RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); } [Test] @@ -199,7 +206,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void LocalFunctions([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) { - RunForLibrary(cscOptions: cscOptions); + RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); } [Test] diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs index 777dcedd1..4bac5c052 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs @@ -228,7 +228,7 @@ namespace System.Runtime.CompilerServices [MethodImpl(MethodImplOptions.AggressiveInlining)] public static IntPtr ByteOffset(ref T origin, ref T target) { - return Unsafe.ByteOffset(ref target, ref origin); + return Unsafe.ByteOffset(target: ref target, origin: ref origin); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs index a73252ee0..8d5f6fdab 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs @@ -428,6 +428,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return () => valueTuple.RenamedString; } #endif + + public static Func Identity() + { + return (T _) => _; + } + +#if CS90 + public static Func LambdaParameterDiscard() + { + return (int _, int _, int _) => 0; + } +#endif } public class Issue1867 diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index 1f73795e2..c38a81c17 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -23,6 +23,12 @@ namespace LocalFunctions { internal class LocalFunctions { + [AttributeUsage(AttributeTargets.All)] + internal class MyAttribute : Attribute + { + + } + public class Generic where T1 : struct, ICloneable, IConvertible { public int MixedLocalFunction() where T2 : ICloneable, IConvertible @@ -31,15 +37,21 @@ namespace LocalFunctions object z = this; for (int j = 0; j < 10; j++) { int i = 0; - i += NonStaticMethod6(); - int NonStaticMethod6() + i += NonStaticMethod6(0); +#if CS90 + [My] + [return: My] + int NonStaticMethod6<[My] T3>([My] int unused) +#else + int NonStaticMethod6(int unused) +#endif { t2 = default(T2); int l = 0; return NonStaticMethod6_1() + NonStaticMethod6_1() + z.GetHashCode(); int NonStaticMethod6_1() { - return i + l + NonStaticMethod6() + StaticMethod1(); + return i + l + NonStaticMethod6(0) + StaticMethod1(); } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/SwitchExpressions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/SwitchExpressions.cs new file mode 100644 index 000000000..37b086cbc --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/SwitchExpressions.cs @@ -0,0 +1,143 @@ +// Copyright (c) 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; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + public static class SwitchExpressions + { + public enum State + { + False, + True, + Null + } + + public static bool? SwitchOverNullableEnum(State? state) + { + return state switch { + State.False => false, + State.True => true, + State.Null => null, + _ => throw new InvalidOperationException(), + }; + } + + public static string SparseIntegerSwitch(int i) + { + Console.WriteLine("SparseIntegerSwitch: " + i); + return i switch { + -10000000 => "-10 mln", + -100 => "-hundred", + -1 => "-1", + 0 => "0", + 1 => "1", + 2 => "2", + 4 => "4", + 100 => "hundred", + 10000 => "ten thousand", + 10001 => "ten thousand and one", + int.MaxValue => "int.MaxValue", + _ => "something else", + }; + } + + public static bool SparseIntegerSwitch3(int i) + { + // not using a switch expression because we'd have to duplicate the 'true' branch + switch (i) { + case 0: + case 10: + case 11: + case 12: + case 100: + case 101: + case 200: + return true; + default: + return false; + } + } + + public static string SwitchOverNullableInt(int? i, int? j) + { + return (i + j) switch { + null => "null", + 0 => "zero", + 5 => "five", + 10 => "ten", + _ => "large", + }; + } + + public static void SwitchOverInt(int i) + { + Console.WriteLine(i switch { + 0 => "zero", + 5 => "five", + 10 => "ten", + 15 => "fifteen", + 20 => "twenty", + 25 => "twenty-five", + 30 => "thirty", + _ => throw new NotImplementedException(), + }); + } + + public static string SwitchOverString1(string text) + { + Console.WriteLine("SwitchOverString1: " + text); + return text switch { + "First case" => "Text1", + "Second case" => "Text2", + "Third case" => "Text3", + "Fourth case" => "Text4", + "Fifth case" => "Text5", + "Sixth case" => "Text6", + null => null, + _ => "Default", + }; + } + + public static string SwitchOverString2(string text) + { + Console.WriteLine("SwitchOverString1: " + text); + // Cannot use switch expression, because "return Text2;" would need to be duplicated + switch (text) { + case "First case": + return "Text1"; + case "Second case": + case "2nd case": + return "Text2"; + case "Third case": + return "Text3"; + case "Fourth case": + return "Text4"; + case "Fifth case": + return "Text5"; + case "Sixth case": + return "Text6"; + case null: + return null; + default: + return "Default"; + } + } + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/YieldReturn.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/YieldReturn.cs index 0306343d3..880428737 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/YieldReturn.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/YieldReturn.cs @@ -356,5 +356,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } Console.WriteLine("normal exit"); } + + internal IEnumerable ForLoopWithYieldReturn(int end, int evil) + { + // This loop needs to pick the implicit "yield break;" as exit point + // in order to produce pretty code; not the "throw" which would + // be a less-pretty option. + for (int i = 0; i < end; i++) { + if (i == evil) { + throw new InvalidOperationException("Found evil number"); + } + yield return i; + } + } } } \ No newline at end of file diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 70358a8bc..40517bfc8 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -405,6 +405,7 @@ namespace ICSharpCode.Decompiler.CSharp typeSystemAstBuilder.AlwaysUseShortTypeNames = true; typeSystemAstBuilder.AddResolveResultAnnotations = true; typeSystemAstBuilder.UseNullableSpecifierForValueTypes = settings.LiftNullables; + typeSystemAstBuilder.SupportInitAccessors = settings.InitAccessors; return typeSystemAstBuilder; } @@ -1345,30 +1346,35 @@ namespace ICSharpCode.Decompiler.CSharp } entityDecl.AddAnnotation(function); - if (function.IsIterator) { - if (localSettings.DecompileMemberBodies && !body.Descendants.Any(d => d is YieldReturnStatement || d is YieldBreakStatement)) { - body.Add(new YieldBreakStatement()); - } - if (function.IsAsync) { - RemoveAttribute(entityDecl, KnownAttribute.AsyncIteratorStateMachine); - } else { - RemoveAttribute(entityDecl, KnownAttribute.IteratorStateMachine); - } - if (function.StateMachineCompiledWithMono) { - RemoveAttribute(entityDecl, KnownAttribute.DebuggerHidden); - } + CleanUpMethodDeclaration(entityDecl, body, function, localSettings.DecompileMemberBodies); + } catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException)) { + throw new DecompilerException(module, method, innerException); + } + } + + internal static void CleanUpMethodDeclaration(EntityDeclaration entityDecl, BlockStatement body, ILFunction function, bool decompileBody = true) + { + if (function.IsIterator) { + if (decompileBody && !body.Descendants.Any(d => d is YieldReturnStatement || d is YieldBreakStatement)) { + body.Add(new YieldBreakStatement()); } if (function.IsAsync) { - entityDecl.Modifiers |= Modifiers.Async; - RemoveAttribute(entityDecl, KnownAttribute.AsyncStateMachine); - RemoveAttribute(entityDecl, KnownAttribute.DebuggerStepThrough); + RemoveAttribute(entityDecl, KnownAttribute.AsyncIteratorStateMachine); + } else { + RemoveAttribute(entityDecl, KnownAttribute.IteratorStateMachine); } - } catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException)) { - throw new DecompilerException(module, method, innerException); + if (function.StateMachineCompiledWithMono) { + RemoveAttribute(entityDecl, KnownAttribute.DebuggerHidden); + } + } + if (function.IsAsync) { + entityDecl.Modifiers |= Modifiers.Async; + RemoveAttribute(entityDecl, KnownAttribute.AsyncStateMachine); + RemoveAttribute(entityDecl, KnownAttribute.DebuggerStepThrough); } } - bool RemoveAttribute(EntityDeclaration entityDecl, KnownAttribute attributeType) + internal static bool RemoveAttribute(EntityDeclaration entityDecl, KnownAttribute attributeType) { bool found = false; foreach (var section in entityDecl.Attributes) { diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 9eeec424c..d1d633a0c 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -197,8 +197,7 @@ namespace ICSharpCode.Decompiler.CSharp } else if (localFunction != null) { var ide = new IdentifierExpression(localFunction.Name); if (method.TypeArguments.Count > 0) { - int skipCount = localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters; - ide.TypeArguments.AddRange(method.TypeArguments.Skip(skipCount).Select(expressionBuilder.ConvertType)); + ide.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType)); } ide.AddAnnotation(localFunction); target = ide.WithoutILInstruction() @@ -1327,7 +1326,6 @@ namespace ICSharpCode.Decompiler.CSharp target = default; targetType = default; methodName = localFunction.Name; - // TODO : think about how to handle generic local functions } else if (method.IsExtensionMethod && invokeMethod != null && method.Parameters.Count - 1 == invokeMethod.Parameters.Count) { step = 5; targetType = method.Parameters[0].Type; @@ -1406,11 +1404,7 @@ namespace ICSharpCode.Decompiler.CSharp } else { var ide = new IdentifierExpression(methodName); if ((step & 2) != 0) { - int skipCount = 0; - if (localFunction != null && method.TypeArguments.Count > 0) { - skipCount = localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters; - } - ide.TypeArguments.AddRange(method.TypeArguments.Skip(skipCount).Select(expressionBuilder.ConvertType)); + ide.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType)); } targetExpression = ide.WithRR(result); } @@ -1469,7 +1463,7 @@ namespace ICSharpCode.Decompiler.CSharp method.DeclaringType, new IParameterizedMember[] { method } ) - }, method.TypeArguments.Skip(localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters).ToArray() + }, method.TypeArguments ); } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index c0c583b00..7bbd4f964 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -92,6 +92,7 @@ namespace ICSharpCode.Decompiler.CSharp this.astBuilder = new TypeSystemAstBuilder(resolver); this.astBuilder.AlwaysUseShortTypeNames = true; this.astBuilder.AddResolveResultAnnotations = true; + this.astBuilder.ShowAttributes = true; this.astBuilder.UseNullableSpecifierForValueTypes = settings.LiftNullables; this.typeInference = new TypeInference(compilation) { Algorithm = TypeInferenceAlgorithm.Improved }; } @@ -1075,7 +1076,11 @@ namespace ICSharpCode.Decompiler.CSharp return null; if (inst.Operator == BinaryNumericOperator.Sub && inst.LeftInputType == StackType.Ref && inst.RightInputType == StackType.Ref) { // ref - ref => i - return CallUnsafeIntrinsic("ByteOffset", new[] { left.Expression, right.Expression }, compilation.FindType(KnownTypeCode.IntPtr), inst); + return CallUnsafeIntrinsic("ByteOffset", new[] { + // ByteOffset() expects the parameters the wrong way around, so order using named arguments + new NamedArgumentExpression("target", left.Expression), + new NamedArgumentExpression("origin", right.Expression) + }, compilation.FindType(KnownTypeCode.IntPtr), inst); } if (inst.LeftInputType == StackType.Ref && inst.RightInputType.IsIntegerType() && left.Type is ByReferenceType brt) { @@ -1788,6 +1793,15 @@ namespace ICSharpCode.Decompiler.CSharp // Case 4 (left-over extension from implicit conversion) can also be handled by our caller. return arg.WithILInstruction(inst); } + case ConversionKind.Invalid: + if (inst.InputType == StackType.Unknown && inst.TargetType == IL.PrimitiveType.None && arg.Type.Kind == TypeKind.Unknown) { + // Unknown -> O conversion. + // Our post-condition allows us to also use expressions with unknown type where O is expected, + // so avoid introducing an `(object)` cast because we're likely to cast back to the same unknown type, + // just in a signature context where we know that it's a class type. + return arg.WithILInstruction(inst); + } + goto default; default: { // We need to convert to inst.TargetType, or to an equivalent type. IType targetType; @@ -1983,7 +1997,7 @@ namespace ICSharpCode.Decompiler.CSharp return SpecialType.UnknownType; } - internal IEnumerable MakeParameters(IReadOnlyList parameters, ILFunction function) + IEnumerable MakeParameters(IReadOnlyList parameters, ILFunction function) { var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index); int i = 0; @@ -1992,9 +2006,6 @@ namespace ICSharpCode.Decompiler.CSharp if (string.IsNullOrEmpty(pd.Name) && !pd.Type.IsArgList()) { // needs to be consistent with logic in ILReader.CreateILVarable(ParameterDefinition) pd.Name = "P_" + i; - // if this is a local function, we have to skip the parameters for closure references - if (settings.LocalFunctions && function.Kind == ILFunctionKind.LocalFunction && IL.Transforms.LocalFunctionDecompiler.IsClosureParameter(parameter, decompilationContext)) - break; } if (settings.AnonymousTypes && parameter.Type.ContainsAnonymousType()) pd.Type = null; @@ -2922,6 +2933,56 @@ namespace ICSharpCode.Decompiler.CSharp } } + protected internal override TranslatedExpression VisitSwitchInstruction(SwitchInstruction inst, TranslationContext context) + { + TranslatedExpression value; + if (inst.Value is StringToInt strToInt) { + value = Translate(strToInt.Argument); + } else { + strToInt = null; + value = Translate(inst.Value); + } + + IL.SwitchSection defaultSection = inst.GetDefaultSection(); + SwitchExpression switchExpr = new SwitchExpression(); + switchExpr.Expression = value; + IType resultType; + if (context.TypeHint.Kind != TypeKind.Unknown && context.TypeHint.GetStackType() == inst.ResultType) { + resultType = context.TypeHint; + } else { + resultType = compilation.FindType(inst.ResultType.ToKnownTypeCode()); + } + + foreach (var section in inst.Sections) { + if (section == defaultSection) + continue; + var ses = new SwitchExpressionSection(); + if (section.HasNullLabel) { + Debug.Assert(section.Labels.IsEmpty); + ses.Pattern = new NullReferenceExpression(); + } else { + long val = section.Labels.Values.Single(); + var rr = statementBuilder.CreateTypedCaseLabel(val, value.Type, strToInt?.Map).Single(); + ses.Pattern = astBuilder.ConvertConstantValue(rr); + } + ses.Body = TranslateSectionBody(section); + switchExpr.SwitchSections.Add(ses); + } + + var defaultSES = new SwitchExpressionSection(); + defaultSES.Pattern = new IdentifierExpression("_"); + defaultSES.Body = TranslateSectionBody(defaultSection); + switchExpr.SwitchSections.Add(defaultSES); + + return switchExpr.WithILInstruction(inst).WithRR(new ResolveResult(resultType)); + + Expression TranslateSectionBody(IL.SwitchSection section) + { + var body = Translate(section.Body, resultType); + return body.ConvertTo(resultType, this, allowImplicitConversion: true); + } + } + protected internal override TranslatedExpression VisitAddressOf(AddressOf inst, TranslationContext context) { // HACK: this is only correct if the argument is an R-value; otherwise we're missing the copy to the temporary diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs index c6b09f079..71506af61 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs @@ -24,6 +24,7 @@ using System.Linq; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.Output; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.TypeSystem.Implementation; namespace ICSharpCode.Decompiler.CSharp.OutputVisitor { @@ -222,7 +223,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor { TypeSystemAstBuilder astBuilder = CreateAstBuilder(); EntityDeclaration node = astBuilder.ConvertEntity(member); - if ((ConversionFlags & ConversionFlags.ShowDeclaringType) == ConversionFlags.ShowDeclaringType && member.DeclaringType != null) { + if ((ConversionFlags & ConversionFlags.ShowDeclaringType) == ConversionFlags.ShowDeclaringType && member.DeclaringType != null && !(member is LocalFunctionMethod)) { ConvertType(member.DeclaringType, writer, formattingPolicy); writer.WriteToken(Roles.Dot, "."); } diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index e9b3a90cd..1f3b9dc2d 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -1759,6 +1759,33 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor EndNode(caseLabel); } + public virtual void VisitSwitchExpression(SwitchExpression switchExpression) + { + StartNode(switchExpression); + switchExpression.Expression.AcceptVisitor(this); + Space(); + WriteKeyword(SwitchExpression.SwitchKeywordRole); + OpenBrace(BraceStyle.EndOfLine); + foreach (AstNode node in switchExpression.SwitchSections) { + node.AcceptVisitor(this); + Comma(node); + NewLine(); + } + CloseBrace(BraceStyle.EndOfLine); + EndNode(switchExpression); + } + + public virtual void VisitSwitchExpressionSection(SwitchExpressionSection switchExpressionSection) + { + StartNode(switchExpressionSection); + switchExpressionSection.Pattern.AcceptVisitor(this); + Space(); + WriteToken(Roles.Arrow); + Space(); + switchExpressionSection.Body.AcceptVisitor(this); + EndNode(switchExpressionSection); + } + public virtual void VisitThrowStatement(ThrowStatement throwStatement) { StartNode(throwStatement); @@ -1876,19 +1903,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor public virtual void VisitLocalFunctionDeclarationStatement(LocalFunctionDeclarationStatement localFunctionDeclarationStatement) { StartNode(localFunctionDeclarationStatement); - - WriteModifiers(localFunctionDeclarationStatement.ModifierTokens); - localFunctionDeclarationStatement.ReturnType.AcceptVisitor(this); - Space(); - WriteIdentifier(localFunctionDeclarationStatement.NameToken); - WriteTypeParameters(localFunctionDeclarationStatement.TypeParameters); - Space(policy.SpaceBeforeMethodDeclarationParentheses); - WriteCommaSeparatedListInParenthesis(localFunctionDeclarationStatement.Parameters, policy.SpaceWithinMethodDeclarationParentheses); - foreach (Constraint constraint in localFunctionDeclarationStatement.Constraints) { - constraint.AcceptVisitor(this); - } - WriteMethodBody(localFunctionDeclarationStatement.Body, policy.MethodBraceStyle); - + localFunctionDeclarationStatement.Declaration.AcceptVisitor(this); EndNode(localFunctionDeclarationStatement); } @@ -1939,7 +1954,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor WriteKeyword("get", PropertyDeclaration.GetKeywordRole); style = policy.PropertyGetBraceStyle; } else if (accessor.Role == PropertyDeclaration.SetterRole) { - WriteKeyword("set", PropertyDeclaration.SetKeywordRole); + if (accessor.Keyword.Role == PropertyDeclaration.InitKeywordRole) { + WriteKeyword("init", PropertyDeclaration.InitKeywordRole); + } else { + WriteKeyword("set", PropertyDeclaration.SetKeywordRole); + } style = policy.PropertySetBraceStyle; } else if (accessor.Role == CustomEventDeclaration.AddAccessorRole) { WriteKeyword("add", CustomEventDeclaration.AddKeywordRole); diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs index 4791b3127..b4a235b97 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs @@ -52,6 +52,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor Shift, // << >> Additive, // binary + - Multiplicative, // * / % + Switch, // C# 8 switch expression Range, // .. Unary, QueryOrLambda, @@ -145,6 +146,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor return PrecedenceLevel.Conditional; if (expr is AssignmentExpression || expr is LambdaExpression) return PrecedenceLevel.Assignment; + if (expr is SwitchExpression) + return PrecedenceLevel.Switch; // anything else: primary expression return PrecedenceLevel.Primary; } @@ -414,5 +417,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor } base.VisitNamedExpression (namedExpression); } + + public override void VisitSwitchExpression(SwitchExpression switchExpression) + { + ParenthesizeIfRequired(switchExpression.Expression, PrecedenceLevel.Switch + 1); + base.VisitSwitchExpression(switchExpression); + } } } diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs index 56394502f..790c21da9 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs @@ -33,6 +33,7 @@ using static ICSharpCode.Decompiler.Metadata.MetadataExtensions; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Solution; using ICSharpCode.Decompiler.DebugInfo; +using System.Collections.Concurrent; namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler { @@ -205,7 +206,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler var progress = ProgressIndicator; DecompilerTypeSystem ts = new DecompilerTypeSystem(module, AssemblyResolver, Settings); Parallel.ForEach( - files, + Partitioner.Create(files, loadBalance: true), new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, CancellationToken = cancellationToken diff --git a/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs b/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs index 8e7922292..2aa5dd8f7 100644 --- a/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs +++ b/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs @@ -17,6 +17,7 @@ namespace ICSharpCode.Decompiler.CSharp static readonly Decompiler.TypeSystem.GenericContext genericContext = default; readonly HashSet namespaces; + readonly HashSet visitedTypes = new HashSet(); public RequiredNamespaceCollector(HashSet namespaces) { @@ -92,17 +93,18 @@ namespace ICSharpCode.Decompiler.CSharp CollectNamespacesForTypeReference(field.ReturnType); break; case IMethod method: - HandleAttributes(method.GetAttributes()); - HandleAttributes(method.GetReturnTypeAttributes()); - CollectNamespacesForTypeReference(method.ReturnType); - foreach (var param in method.Parameters) { - HandleAttributes(param.GetAttributes()); - CollectNamespacesForTypeReference(param.Type); - } - HandleTypeParameters(method.TypeParameters); var reader = module.PEFile.Reader; var parts = mappingInfo.GetMethodParts((MethodDefinitionHandle)method.MetadataToken).ToList(); foreach (var part in parts) { + var partMethod = module.ResolveMethod(part, genericContext); + HandleAttributes(partMethod.GetAttributes()); + HandleAttributes(partMethod.GetReturnTypeAttributes()); + CollectNamespacesForTypeReference(partMethod.ReturnType); + foreach (var param in partMethod.Parameters) { + HandleAttributes(param.GetAttributes()); + CollectNamespacesForTypeReference(param.Type); + } + HandleTypeParameters(partMethod.TypeParameters); HandleOverrides(part.GetMethodImplementations(module.metadata), module); var methodDef = module.metadata.GetMethodDefinition(part); if (method.HasBody) { @@ -141,13 +143,15 @@ namespace ICSharpCode.Decompiler.CSharp void CollectNamespacesForTypeReference(IType type) { + if (!visitedTypes.Add(type)) + return; switch (type) { case ParameterizedType parameterizedType: namespaces.Add(parameterizedType.Namespace); CollectNamespacesForTypeReference(parameterizedType.GenericType); foreach (var arg in parameterizedType.TypeArguments) CollectNamespacesForTypeReference(arg); - break; + return; // no need to collect base types again case TypeWithElementType typeWithElementType: CollectNamespacesForTypeReference(typeWithElementType.ElementType); break; diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index c84b2850b..a935bfb99 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -120,7 +120,7 @@ namespace ICSharpCode.Decompiler.CSharp return new IfElseStatement(condition, trueStatement, falseStatement).WithILInstruction(inst); } - IEnumerable CreateTypedCaseLabel(long i, IType type, List<(string Key, int Value)> map = null) + internal IEnumerable CreateTypedCaseLabel(long i, IType type, List<(string Key, int Value)> map = null) { object value; // unpack nullable type, if necessary: @@ -165,20 +165,14 @@ namespace ICSharpCode.Decompiler.CSharp caseLabelMapping = new Dictionary(); TranslatedExpression value; - var strToInt = inst.Value as StringToInt; - if (strToInt != null) { + if (inst.Value is StringToInt strToInt) { value = exprBuilder.Translate(strToInt.Argument); } else { + strToInt = null; value = exprBuilder.Translate(inst.Value); } - // Pick the section with the most labels as default section. - IL.SwitchSection defaultSection = inst.Sections.First(); - foreach (var section in inst.Sections) { - if (section.Labels.Count() > defaultSection.Labels.Count()) { - defaultSection = section; - } - } + IL.SwitchSection defaultSection = inst.GetDefaultSection(); var stmt = new SwitchStatement() { Expression = value }; Dictionary translationDictionary = new Dictionary(); @@ -1002,34 +996,19 @@ namespace ICSharpCode.Decompiler.CSharp LocalFunctionDeclarationStatement TranslateFunction(ILFunction function) { - var stmt = new LocalFunctionDeclarationStatement(); var nestedBuilder = new StatementBuilder(typeSystem, exprBuilder.decompilationContext, function, settings, cancellationToken); - stmt.Name = function.Name; - stmt.Parameters.AddRange(exprBuilder.MakeParameters(function.Parameters, function)); - stmt.ReturnType = exprBuilder.ConvertType(function.Method.ReturnType); - stmt.Body = nestedBuilder.ConvertAsBlock(function.Body); + var astBuilder = exprBuilder.astBuilder; + var method = (MethodDeclaration)astBuilder.ConvertEntity(function.ReducedMethod); + method.Body = nestedBuilder.ConvertAsBlock(function.Body); Comment prev = null; foreach (string warning in function.Warnings) { - stmt.Body.InsertChildAfter(prev, prev = new Comment(warning), Roles.Comment); + method.Body.InsertChildAfter(prev, prev = new Comment(warning), Roles.Comment); } - if (function.Method.TypeParameters.Count > 0) { - var astBuilder = exprBuilder.astBuilder; - if (astBuilder.ShowTypeParameters) { - int skipCount = function.ReducedMethod.NumberOfCompilerGeneratedTypeParameters; - stmt.TypeParameters.AddRange(function.Method.TypeParameters.Skip(skipCount).Select(t => astBuilder.ConvertTypeParameter(t))); - if (astBuilder.ShowTypeParameterConstraints) { - stmt.Constraints.AddRange(function.Method.TypeParameters.Skip(skipCount).Select(t => astBuilder.ConvertTypeParameterConstraint(t)).Where(c => c != null)); - } - } - } - if (function.IsAsync) { - stmt.Modifiers |= Modifiers.Async; - } - if (settings.StaticLocalFunctions && function.ReducedMethod.IsStaticLocalFunction) { - stmt.Modifiers |= Modifiers.Static; - } + CSharpDecompiler.CleanUpMethodDeclaration(method, method.Body, function); + CSharpDecompiler.RemoveAttribute(method, KnownAttribute.CompilerGenerated); + var stmt = new LocalFunctionDeclarationStatement(method); stmt.AddAnnotation(new MemberResolveResult(null, function.ReducedMethod)); stmt.WithILInstruction(function); return stmt; diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs index 5d6b71a8b..f543cf611 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs @@ -345,7 +345,17 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { VisitChildren (caseLabel); } - + + public virtual void VisitSwitchExpression(SwitchExpression switchExpression) + { + VisitChildren(switchExpression); + } + + public virtual void VisitSwitchExpressionSection(SwitchExpressionSection switchExpressionSection) + { + VisitChildren(switchExpressionSection); + } + public virtual void VisitThrowStatement (ThrowStatement throwStatement) { VisitChildren (throwStatement); @@ -987,7 +997,17 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { return VisitChildren (caseLabel); } - + + public virtual T VisitSwitchExpression(SwitchExpression switchExpression) + { + return VisitChildren(switchExpression); + } + + public virtual T VisitSwitchExpressionSection(SwitchExpressionSection switchExpressionSection) + { + return VisitChildren(switchExpressionSection); + } + public virtual T VisitThrowStatement (ThrowStatement throwStatement) { return VisitChildren (throwStatement); @@ -1629,7 +1649,17 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { return VisitChildren (caseLabel, data); } - + + public virtual S VisitSwitchExpression(SwitchExpression switchExpression, T data) + { + return VisitChildren(switchExpression, data); + } + + public virtual S VisitSwitchExpressionSection(SwitchExpressionSection switchExpressionSection, T data) + { + return VisitChildren(switchExpressionSection, data); + } + public virtual S VisitThrowStatement (ThrowStatement throwStatement, T data) { return VisitChildren (throwStatement, data); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/SwitchExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/SwitchExpression.cs new file mode 100644 index 000000000..9c9e166d6 --- /dev/null +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/SwitchExpression.cs @@ -0,0 +1,118 @@ +// Copyright (c) 2020 Daniel Grunwald +// +// 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. + + +namespace ICSharpCode.Decompiler.CSharp.Syntax +{ + /// + /// Expression switch { SwitchSections } + /// + public class SwitchExpression : Expression + { + public static readonly TokenRole SwitchKeywordRole = new TokenRole("switch"); + public static readonly Role SwitchSectionRole = new Role("SwitchSection"); + + public Expression Expression { + get { return GetChildByRole(Roles.Expression); } + set { SetChildByRole(Roles.Expression, value); } + } + + public CSharpTokenNode SwitchToken { + get { return GetChildByRole(SwitchKeywordRole); } + } + + public CSharpTokenNode LBraceToken { + get { return GetChildByRole(Roles.LBrace); } + } + + public AstNodeCollection SwitchSections { + get { return GetChildrenByRole(SwitchSectionRole); } + } + + public CSharpTokenNode RBraceToken { + get { return GetChildByRole(Roles.RBrace); } + } + + public override void AcceptVisitor(IAstVisitor visitor) + { + visitor.VisitSwitchExpression(this); + } + + public override T AcceptVisitor(IAstVisitor visitor) + { + return visitor.VisitSwitchExpression(this); + } + + public override S AcceptVisitor(IAstVisitor visitor, T data) + { + return visitor.VisitSwitchExpression(this, data); + } + + protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) + { + SwitchExpression o = other as SwitchExpression; + return o != null && this.Expression.DoMatch(o.Expression, match) && this.SwitchSections.DoMatch(o.SwitchSections, match); + } + } + + /// + /// Pattern => Expression + /// + public class SwitchExpressionSection : AstNode + { + public static readonly Role PatternRole = new Role("Pattern", Expression.Null); + public static readonly Role BodyRole = new Role("Body", Expression.Null); + + public Expression Pattern { + get { return GetChildByRole(PatternRole); } + set { SetChildByRole(PatternRole, value); } + } + + public CSharpTokenNode ArrowToken { + get { return GetChildByRole(Roles.Arrow); } + } + + public Expression Body { + get { return GetChildByRole(BodyRole); } + set { SetChildByRole(BodyRole, value); } + } + + public override NodeType NodeType => NodeType.Unknown; + + public override void AcceptVisitor(IAstVisitor visitor) + { + visitor.VisitSwitchExpressionSection(this); + } + + public override T AcceptVisitor(IAstVisitor visitor) + { + return visitor.VisitSwitchExpressionSection(this); + } + + public override S AcceptVisitor(IAstVisitor visitor, T data) + { + return visitor.VisitSwitchExpressionSection(this, data); + } + + protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) + { + SwitchExpressionSection o = other as SwitchExpressionSection; + return o != null && this.Pattern.DoMatch(o.Pattern, match) && this.Body.DoMatch(o.Body, match); + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs index 90d8371ca..ef73b991d 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs @@ -103,6 +103,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax void VisitSwitchStatement(SwitchStatement switchStatement); void VisitSwitchSection(SwitchSection switchSection); void VisitCaseLabel(CaseLabel caseLabel); + void VisitSwitchExpression(SwitchExpression switchExpression); + void VisitSwitchExpressionSection(SwitchExpressionSection switchExpressionSection); void VisitThrowStatement(ThrowStatement throwStatement); void VisitTryCatchStatement(TryCatchStatement tryCatchStatement); void VisitCatchClause(CatchClause catchClause); @@ -243,6 +245,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax S VisitSwitchStatement(SwitchStatement switchStatement); S VisitSwitchSection(SwitchSection switchSection); S VisitCaseLabel(CaseLabel caseLabel); + S VisitSwitchExpression(SwitchExpression switchExpression); + S VisitSwitchExpressionSection(SwitchExpressionSection switchExpressionSection); S VisitThrowStatement(ThrowStatement throwStatement); S VisitTryCatchStatement(TryCatchStatement tryCatchStatement); S VisitCatchClause(CatchClause catchClause); @@ -383,6 +387,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax S VisitSwitchStatement(SwitchStatement switchStatement, T data); S VisitSwitchSection(SwitchSection switchSection, T data); S VisitCaseLabel(CaseLabel caseLabel, T data); + S VisitSwitchExpression(SwitchExpression switchExpression, T data); + S VisitSwitchExpressionSection(SwitchExpressionSection switchExpressionSection, T data); S VisitThrowStatement(ThrowStatement throwStatement, T data); S VisitTryCatchStatement(TryCatchStatement tryCatchStatement, T data); S VisitCatchClause(CatchClause catchClause, T data); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Statements/LocalFunctionDeclarationStatement.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Statements/LocalFunctionDeclarationStatement.cs index 47ff1641c..105789bb3 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Statements/LocalFunctionDeclarationStatement.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Statements/LocalFunctionDeclarationStatement.cs @@ -16,69 +16,22 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System.Collections.Generic; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; namespace ICSharpCode.Decompiler.CSharp.Syntax { public class LocalFunctionDeclarationStatement : Statement { - public AstNodeCollection TypeParameters { - get { return GetChildrenByRole(Roles.TypeParameter); } - } - - public CSharpTokenNode LParToken { - get { return GetChildByRole(Roles.LPar); } - } - - public AstNodeCollection Parameters { - get { return GetChildrenByRole(Roles.Parameter); } - } + public static readonly Role MethodDeclarationRole = new Role("Method"); - public CSharpTokenNode RParToken { - get { return GetChildByRole(Roles.RPar); } + public MethodDeclaration Declaration { + get { return GetChildByRole(MethodDeclarationRole); } + set { SetChildByRole(MethodDeclarationRole, value); } } - public AstNodeCollection Constraints { - get { return GetChildrenByRole(Roles.Constraint); } - } - - public BlockStatement Body { - get { return GetChildByRole(Roles.Body); } - set { SetChildByRole(Roles.Body, value); } - } - - public Modifiers Modifiers { - get { return EntityDeclaration.GetModifiers(this); } - set { EntityDeclaration.SetModifiers(this, value); } - } - - public bool HasModifier(Modifiers mod) + public LocalFunctionDeclarationStatement(MethodDeclaration methodDeclaration) { - return (Modifiers & mod) == mod; - } - - public IEnumerable ModifierTokens { - get { return GetChildrenByRole(EntityDeclaration.ModifierRole); } - } - - public virtual string Name { - get { - return GetChildByRole(Roles.Identifier).Name; - } - set { - SetChildByRole(Roles.Identifier, Identifier.Create(value, TextLocation.Empty)); - } - } - - public virtual Identifier NameToken { - get { return GetChildByRole(Roles.Identifier); } - set { SetChildByRole(Roles.Identifier, value); } - } - - public virtual AstType ReturnType { - get { return GetChildByRole(Roles.Type); } - set { SetChildByRole(Roles.Type, value); } + AddChild(methodDeclaration, MethodDeclarationRole); } public override void AcceptVisitor(IAstVisitor visitor) @@ -98,13 +51,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax protected internal override bool DoMatch(AstNode other, Match match) { - LocalFunctionDeclarationStatement o = other as LocalFunctionDeclarationStatement; - return o != null && MatchString(this.Name, o.Name) - && (this.Modifiers == Modifiers.Any || this.Modifiers == o.Modifiers) - && this.ReturnType.DoMatch(o.ReturnType, match) - && this.TypeParameters.DoMatch(o.TypeParameters, match) - && this.Parameters.DoMatch(o.Parameters, match) && this.Constraints.DoMatch(o.Constraints, match) - && this.Body.DoMatch(o.Body, match); + return other is LocalFunctionDeclarationStatement o && Declaration.DoMatch(o.Declaration, match); } } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/Accessor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/Accessor.cs index dd41e83b7..345aa9aca 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/Accessor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/Accessor.cs @@ -72,13 +72,14 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } /// - /// Gets the 'get'/'set'/'add'/'remove' keyword + /// Gets the 'get'/'set'/'init'/'add'/'remove' keyword /// public CSharpTokenNode Keyword { get { for (AstNode child = this.FirstChild; child != null; child = child.NextSibling) { if (child.Role == PropertyDeclaration.GetKeywordRole || child.Role == PropertyDeclaration.SetKeywordRole - || child.Role == CustomEventDeclaration.AddKeywordRole || child.Role == CustomEventDeclaration.RemoveKeywordRole) + || child.Role == PropertyDeclaration.InitKeywordRole + || child.Role == CustomEventDeclaration.AddKeywordRole || child.Role == CustomEventDeclaration.RemoveKeywordRole) { return (CSharpTokenNode)child; } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/PropertyDeclaration.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/PropertyDeclaration.cs index 00f163122..34ac8039a 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/PropertyDeclaration.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/PropertyDeclaration.cs @@ -32,6 +32,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { public static readonly TokenRole GetKeywordRole = new TokenRole ("get"); public static readonly TokenRole SetKeywordRole = new TokenRole ("set"); + public static readonly TokenRole InitKeywordRole = new TokenRole ("init"); public static readonly Role GetterRole = new Role("Getter", Accessor.Null); public static readonly Role SetterRole = new Role("Setter", Accessor.Null); public static readonly Role ExpressionBodyRole = new Role("ExpressionBody", Expression.Null); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 3fe6a5f02..1f944a664 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using System.Reflection; using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.CSharp.TypeSystem; using ICSharpCode.Decompiler.Semantics; @@ -205,6 +206,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax /// The default value is . /// public bool PrintIntegralValuesAsHex { get; set; } + + /// + /// Controls whether C# 9 "init;" accessors are supported. + /// If disabled, emits "set /*init*/;" instead. + /// + public bool SupportInitAccessors { get; set; } #endregion #region Convert Type @@ -1363,7 +1370,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return ConvertDestructor((IMethod)entity); case SymbolKind.Accessor: IMethod accessor = (IMethod)entity; - return ConvertAccessor(accessor, accessor.AccessorOwner != null ? accessor.AccessorOwner.Accessibility : Accessibility.None, false); + Accessibility ownerAccessibility = accessor.AccessorOwner?.Accessibility ?? Accessibility.None; + return ConvertAccessor(accessor, accessor.AccessorKind, ownerAccessibility, false); default: throw new ArgumentException("Invalid value for SymbolKind: " + entity.SymbolKind); } @@ -1554,15 +1562,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } } - Accessor ConvertAccessor(IMethod accessor, Accessibility ownerAccessibility, bool addParameterAttribute) + Accessor ConvertAccessor(IMethod accessor, MethodSemanticsAttributes kind, Accessibility ownerAccessibility, bool addParameterAttribute) { if (accessor == null) return Accessor.Null; Accessor decl = new Accessor(); - if (this.ShowAccessibility && accessor.Accessibility != ownerAccessibility) - decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility); - if (accessor.HasReadonlyModifier()) - decl.Modifiers |= Modifiers.Readonly; if (ShowAttributes) { decl.Attributes.AddRange(ConvertAttributes(accessor.GetAttributes())); decl.Attributes.AddRange(ConvertAttributes(accessor.GetReturnTypeAttributes(), "return")); @@ -1570,10 +1574,35 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax decl.Attributes.AddRange(ConvertAttributes(accessor.Parameters.Last().GetAttributes(), "param")); } } + if (this.ShowAccessibility && accessor.Accessibility != ownerAccessibility) + decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility); + if (accessor.HasReadonlyModifier()) + decl.Modifiers |= Modifiers.Readonly; + TokenRole keywordRole = kind switch + { + MethodSemanticsAttributes.Getter => PropertyDeclaration.GetKeywordRole, + MethodSemanticsAttributes.Setter => PropertyDeclaration.SetKeywordRole, + MethodSemanticsAttributes.Adder => CustomEventDeclaration.AddKeywordRole, + MethodSemanticsAttributes.Remover => CustomEventDeclaration.RemoveKeywordRole, + _ => null + }; + if (kind == MethodSemanticsAttributes.Setter && SupportInitAccessors && accessor.IsInitOnly) { + keywordRole = PropertyDeclaration.InitKeywordRole; + } + if (keywordRole != null) { + decl.AddChild(new CSharpTokenNode(TextLocation.Empty, keywordRole), keywordRole); + } + if (accessor.IsInitOnly && keywordRole != PropertyDeclaration.InitKeywordRole) { + decl.AddChild(new Comment("init", CommentType.MultiLine), Roles.Comment); + } if (AddResolveResultAnnotations) { decl.AddAnnotation(new MemberResolveResult(null, accessor)); } - decl.Body = GenerateBodyBlock(); + if (GenerateBody) { + decl.Body = GenerateBodyBlock(); + } else { + decl.AddChild(new CSharpTokenNode(TextLocation.Empty, Roles.Semicolon), Roles.Semicolon); + } return decl; } @@ -1592,8 +1621,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax ct.HasReadOnlySpecifier = true; } decl.Name = property.Name; - decl.Getter = ConvertAccessor(property.Getter, property.Accessibility, false); - decl.Setter = ConvertAccessor(property.Setter, property.Accessibility, true); + decl.Getter = ConvertAccessor(property.Getter, MethodSemanticsAttributes.Getter, property.Accessibility, false); + decl.Setter = ConvertAccessor(property.Setter, MethodSemanticsAttributes.Setter, property.Accessibility, true); decl.PrivateImplementationType = GetExplicitInterfaceType (property); MergeReadOnlyModifiers(decl, decl.Getter, decl.Setter); return decl; @@ -1624,8 +1653,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax foreach (IParameter p in indexer.Parameters) { decl.Parameters.Add(ConvertParameter(p)); } - decl.Getter = ConvertAccessor(indexer.Getter, indexer.Accessibility, false); - decl.Setter = ConvertAccessor(indexer.Setter, indexer.Accessibility, true); + decl.Getter = ConvertAccessor(indexer.Getter, MethodSemanticsAttributes.Getter, indexer.Accessibility, false); + decl.Setter = ConvertAccessor(indexer.Setter, MethodSemanticsAttributes.Setter, indexer.Accessibility, true); decl.PrivateImplementationType = GetExplicitInterfaceType (indexer); MergeReadOnlyModifiers(decl, decl.Getter, decl.Setter); return decl; @@ -1644,8 +1673,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } decl.ReturnType = ConvertType(ev.ReturnType); decl.Name = ev.Name; - decl.AddAccessor = ConvertAccessor(ev.AddAccessor, ev.Accessibility, true); - decl.RemoveAccessor = ConvertAccessor(ev.RemoveAccessor, ev.Accessibility, true); + decl.AddAccessor = ConvertAccessor(ev.AddAccessor, MethodSemanticsAttributes.Adder, ev.Accessibility, true); + decl.RemoveAccessor = ConvertAccessor(ev.RemoveAccessor, MethodSemanticsAttributes.Remover, ev.Accessibility, true); decl.PrivateImplementationType = GetExplicitInterfaceType (ev); MergeReadOnlyModifiers(decl, decl.AddAccessor, decl.RemoveAccessor); return decl; @@ -1799,6 +1828,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return !member.IsStatic; case SymbolKind.Destructor: return false; + case SymbolKind.Method: + return !((IMethod)member).IsLocalFunction; default: return true; } @@ -1811,7 +1842,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax m |= ModifierFromAccessibility (member.Accessibility); } if (this.ShowModifiers) { - if (member.IsStatic) { + if (member is LocalFunctionMethod localFunction) { + if (localFunction.IsStaticLocalFunction) { + m |= Modifiers.Static; + } + } else if (member.IsStatic) { m |= Modifiers.Static; } else { var declaringType = member.DeclaringType; diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs b/ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs index adf6e3f22..3f3015a33 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs @@ -55,56 +55,56 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms public override TResult VisitMethodDeclaration(MethodDeclaration methodDeclaration) { - Debug.Assert(currentMethod == null); + var oldMethod = currentMethod; try { currentMethod = methodDeclaration.GetSymbol() as IMethod; return base.VisitMethodDeclaration(methodDeclaration); } finally { - currentMethod = null; + currentMethod = oldMethod; } } public override TResult VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration) { - Debug.Assert(currentMethod == null); + var oldMethod = currentMethod; try { currentMethod = constructorDeclaration.GetSymbol() as IMethod; return base.VisitConstructorDeclaration(constructorDeclaration); } finally { - currentMethod = null; + currentMethod = oldMethod; } } public override TResult VisitDestructorDeclaration(DestructorDeclaration destructorDeclaration) { - Debug.Assert(currentMethod == null); + var oldMethod = currentMethod; try { currentMethod = destructorDeclaration.GetSymbol() as IMethod; return base.VisitDestructorDeclaration(destructorDeclaration); } finally { - currentMethod = null; + currentMethod = oldMethod; } } public override TResult VisitOperatorDeclaration(OperatorDeclaration operatorDeclaration) { - Debug.Assert(currentMethod == null); + var oldMethod = currentMethod; try { currentMethod = operatorDeclaration.GetSymbol() as IMethod; return base.VisitOperatorDeclaration(operatorDeclaration); } finally { - currentMethod = null; + currentMethod = oldMethod; } } public override TResult VisitAccessor(Accessor accessor) { - Debug.Assert(currentMethod == null); + var oldMethod = currentMethod; try { currentMethod = accessor.GetSymbol() as IMethod; return base.VisitAccessor(accessor); } finally { - currentMethod = null; + currentMethod = oldMethod; } } } diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index e1b158b0a..32623ecd6 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -113,17 +113,19 @@ namespace ICSharpCode.Decompiler asyncEnumerator = false; staticLocalFunctions = false; ranges = false; + switchExpressions = false; } if (languageVersion < CSharp.LanguageVersion.Preview) { nativeIntegers = false; + initAccessors = false; } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { - if (nativeIntegers) + if (nativeIntegers || initAccessors) return CSharp.LanguageVersion.Preview; - if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions || ranges) + if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions || ranges || switchExpressions) return CSharp.LanguageVersion.CSharp8_0; if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers || patternBasedFixedStatement) return CSharp.LanguageVersion.CSharp7_3; @@ -163,6 +165,40 @@ namespace ICSharpCode.Decompiler } } + bool initAccessors = true; + + /// + /// Use C# 9 init; property accessors. + /// + [Category("C# 9.0 (experimental)")] + [Description("DecompilerSettings.InitAccessors")] + public bool InitAccessors { + get { return initAccessors; } + set { + if (initAccessors != value) { + initAccessors = value; + OnPropertyChanged(); + } + } + } + + bool switchExpressions = true; + + /// + /// Use C# 8 switch expressions. + /// + [Category("C# 8.0 / VS 2019")] + [Description("DecompilerSettings.SwitchExpressions")] + public bool SwitchExpressions { + get { return switchExpressions; } + set { + if (switchExpressions != value) { + switchExpressions = value; + OnPropertyChanged(); + } + } + } + bool anonymousMethods = true; /// @@ -1387,6 +1423,25 @@ namespace ICSharpCode.Decompiler } } + bool aggressiveInlining = false; + + /// + /// If set to false (the default), the decompiler will inline local variables only when they occur + /// in a context where the C# compiler is known to emit compiler-generated locals. + /// If set to true, the decompiler will inline local variables whenever possible. + /// + [Category("DecompilerSettings.Other")] + [Description("DecompilerSettings.AggressiveInlining")] + public bool AggressiveInlining { + get { return aggressiveInlining; } + set { + if (aggressiveInlining != value) { + aggressiveInlining = value; + OnPropertyChanged(); + } + } + } + CSharpFormattingOptions csharpFormattingOptions; [Browsable(false)] diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index a00763439..e10626114 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -70,6 +70,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs index e22109e70..3a69d5859 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs @@ -20,6 +20,7 @@ using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.Decompiler.IL.Transforms; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.Util; using System; using System.Collections.Generic; @@ -33,9 +34,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow /// /// Decompiler step for C# 5 async/await. /// - class AsyncAwaitDecompiler : IILTransform + public class AsyncAwaitDecompiler : IILTransform { - public static bool IsCompilerGeneratedStateMachine(TypeDefinitionHandle type, MetadataReader metadata) + internal static bool IsCompilerGeneratedStateMachine(TypeDefinitionHandle type, MetadataReader metadata) { TypeDefinition td; if (type.IsNil || (td = metadata.GetTypeDefinition(type)).GetDeclaringType().IsNil) @@ -50,7 +51,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow return false; } - public static bool IsCompilerGeneratedMainMethod(Metadata.PEFile module, MethodDefinitionHandle method) + internal static bool IsCompilerGeneratedMainMethod(Metadata.PEFile module, MethodDefinitionHandle method) { var metadata = module.Metadata; var definition = metadata.GetMethodDefinition(method); @@ -74,7 +75,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow IType underlyingReturnType; // return type of the method (only the "T" for Task{T}), for async enumerators this is the type being yielded AsyncMethodType methodType; ITypeDefinition stateMachineType; - ITypeDefinition builderType; + IType builderType; IField builderField; IField stateField; int initialState; @@ -218,24 +219,32 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow if (startCall.Method.Name != "Start") return false; taskType = function.Method.ReturnType; - builderType = startCall.Method.DeclaringTypeDefinition; + builderType = startCall.Method.DeclaringType; + FullTypeName builderTypeName; + if (builderType?.GetDefinition() is { } builderTypeDef) { + builderTypeName = builderTypeDef.FullTypeName; + } else if (builderType is UnknownType unknownBuilderType) { + builderTypeName = unknownBuilderType.FullTypeName; + } else { + return false; + } if (taskType.IsKnownType(KnownTypeCode.Void)) { methodType = AsyncMethodType.Void; underlyingReturnType = taskType; - if (builderType?.FullTypeName != new TopLevelTypeName("System.Runtime.CompilerServices", "AsyncVoidMethodBuilder")) + if (builderTypeName != new TopLevelTypeName("System.Runtime.CompilerServices", "AsyncVoidMethodBuilder")) return false; - } else if (TaskType.IsNonGenericTaskType(taskType, out var builderTypeName)) { + } else if (TaskType.IsNonGenericTaskType(taskType, out var builderTypeNameFromTask)) { methodType = AsyncMethodType.Task; underlyingReturnType = context.TypeSystem.FindType(KnownTypeCode.Void); - if (builderType?.FullTypeName != builderTypeName) + if (builderTypeNameFromTask != builderTypeName) return false; - } else if (TaskType.IsGenericTaskType(taskType, out builderTypeName)) { + } else if (TaskType.IsGenericTaskType(taskType, out builderTypeNameFromTask)) { methodType = AsyncMethodType.TaskOfT; if (taskType.IsKnownType(KnownTypeCode.TaskOfT)) underlyingReturnType = TaskType.UnpackTask(context.TypeSystem, taskType); else underlyingReturnType = startCall.Method.DeclaringType.TypeArguments[0]; - if (builderType?.FullTypeName != builderTypeName) + if (builderTypeNameFromTask != builderTypeName) return false; } else { return false; @@ -484,7 +493,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow return false; } - static void AnalyzeEnumeratorCtor(IMethod ctor, ILTransformContext context, out IField builderField, out ITypeDefinition builderType, out IField stateField) + static void AnalyzeEnumeratorCtor(IMethod ctor, ILTransformContext context, out IField builderField, out IType builderType, out IField stateField) { builderField = null; stateField = null; @@ -515,7 +524,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow if (stateField == null || builderField == null) throw new SymbolicAnalysisFailedException(); - builderType = builderField.Type.GetDefinition(); + builderType = builderField.Type; if (builderType == null) throw new SymbolicAnalysisFailedException(); } diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs index 1ba3a1437..36632f739 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs @@ -200,11 +200,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow /// * use only a single exit point if at all possible /// * minimize the amount of code in the in-loop partition /// (thus: maximize the amount of code in the out-of-loop partition) - /// "amount of code" could be measured as: - /// * number of basic blocks - /// * number of instructions directly in those basic blocks (~= number of statements) - /// * number of instructions in those basic blocks (~= number of expressions) - /// (we currently use the number of statements) /// /// Observations: /// * If a node is in-loop, so are all its ancestors in the dominator tree (up to the loop entry point) @@ -315,6 +310,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } ControlFlowNode exitPoint = null; int exitPointILOffset = -1; + ConsiderReturnAsExitPoint((Block)loopHead.UserData, ref exitPoint, ref exitPointILOffset); foreach (var node in loopHead.DominatorTreeChildren) { PickExitPoint(node, ref exitPoint, ref exitPointILOffset); } @@ -457,12 +453,31 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow exitPoint = node; exitPointILOffset = block.StartILOffset; return; // don't visit children, they are likely to have even later IL offsets and we'd end up - // moving almost all of the code into the loop. + // moving almost all of the code into the loop. } + ConsiderReturnAsExitPoint(block, ref exitPoint, ref exitPointILOffset); foreach (var child in node.DominatorTreeChildren) { PickExitPoint(child, ref exitPoint, ref exitPointILOffset); } } + + private static void ConsiderReturnAsExitPoint(Block block, ref ControlFlowNode exitPoint, ref int exitPointILOffset) + { + // It's possible that the real exit point of the loop is a "return;" that has been combined (by ControlFlowSimplification) + // with the condition block. + if (!block.MatchIfAtEndOfBlock(out _, out var trueInst, out var falseInst)) + return; + if (trueInst.StartILOffset > exitPointILOffset && trueInst is Leave { IsLeavingFunction: true, Value: Nop _ }) { + // By using NoExitPoint, everything (including the "return;") becomes part of the loop body + // Then DetectExitPoint will move the "return;" out of the loop body. + exitPoint = NoExitPoint; + exitPointILOffset = trueInst.StartILOffset; + } + if (falseInst.StartILOffset > exitPointILOffset && falseInst is Leave { IsLeavingFunction: true, Value: Nop _ }) { + exitPoint = NoExitPoint; + exitPointILOffset = falseInst.StartILOffset; + } + } /// /// Constructs a new control flow graph. diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs index 9ae80f766..9eb2d3701 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs @@ -35,7 +35,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow /// Detect sequences of conditional branches that all test a single integer value, /// and simplify them into a ILAst switch instruction (which like C# does not require contiguous values). /// - class SwitchDetection : IILTransform + public class SwitchDetection : IILTransform { private readonly SwitchAnalysis analysis = new SwitchAnalysis(); diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs index 268891509..ca6314a74 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs @@ -28,7 +28,7 @@ using System.Reflection.Metadata; namespace ICSharpCode.Decompiler.IL.ControlFlow { - class YieldReturnDecompiler : IILTransform + public class YieldReturnDecompiler : IILTransform { // For a description on the code generated by the C# compiler for yield return: // http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index e86597bd9..c85375d2d 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -515,10 +515,10 @@ namespace ICSharpCode.Decompiler.IL var blockBuilder = new BlockBuilder(body, variableByExceptionHandler); blockBuilder.CreateBlocks(mainContainer, instructionBuilder, isBranchTarget, cancellationToken); var function = new ILFunction(this.method, body.GetCodeSize(), this.genericContext, mainContainer, kind); - CollectionExtensions.AddRange(function.Variables, parameterVariables); - CollectionExtensions.AddRange(function.Variables, localVariables); - CollectionExtensions.AddRange(function.Variables, stackVariables); - CollectionExtensions.AddRange(function.Variables, variableByExceptionHandler.Values); + function.Variables.AddRange(parameterVariables); + function.Variables.AddRange(localVariables); + function.Variables.AddRange(stackVariables); + function.Variables.AddRange(variableByExceptionHandler.Values); function.AddRef(); // mark the root node var removedBlocks = new List(); foreach (var c in function.Descendants.OfType()) { diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index cd420c1e7..1de9d1fc8 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -1497,7 +1497,7 @@ namespace ICSharpCode.Decompiler.IL /// Switch statement public sealed partial class SwitchInstruction : ILInstruction { - public override StackType ResultType { get { return StackType.Void; } } + public override void AcceptVisitor(ILVisitor visitor) { visitor.VisitSwitchInstruction(this); diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index 2e3e35d83..b288ed9b2 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -116,7 +116,7 @@ new ChildInfo("fallbackInst"), }), CustomConstructor, CustomComputeFlags, CustomWriteTo), new OpCode("switch", "Switch statement", - CustomClassName("SwitchInstruction"), CustomConstructor, CustomComputeFlags, CustomWriteTo, ResultType("Void"), + CustomClassName("SwitchInstruction"), CustomConstructor, CustomComputeFlags, CustomWriteTo, MatchCondition("IsLifted == o.IsLifted && Value.PerformMatch(o.Value, ref match) && Patterns.ListMatch.DoMatch(this.Sections, o.Sections, ref match)")), new OpCode("switch.section", "Switch section within a switch statement", CustomClassName("SwitchSection"), CustomChildren(new [] { new ChildInfo("body") }), diff --git a/ICSharpCode.Decompiler/IL/Instructions/DynamicInstructions.cs b/ICSharpCode.Decompiler/IL/Instructions/DynamicInstructions.cs index 5a3b64b54..d11c69171 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/DynamicInstructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/DynamicInstructions.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using ICSharpCode.Decompiler.IL.Patterns; using ICSharpCode.Decompiler.TypeSystem; diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs index daaeb214a..d66248133 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs @@ -166,13 +166,11 @@ namespace ICSharpCode.Decompiler.IL /// /// Return type of this function. - /// Might be null, if this function was not created from metadata. /// public readonly IType ReturnType; /// /// List of parameters of this function. - /// Might be null, if this function was not created from metadata. /// public readonly IReadOnlyList Parameters; @@ -188,17 +186,16 @@ namespace ICSharpCode.Decompiler.IL /// /// /// Use to create ILAst. - /// may be null. /// public ILFunction(IMethod method, int codeSize, GenericContext genericContext, ILInstruction body, ILFunctionKind kind = ILFunctionKind.TopLevelFunction) : base(OpCode.ILFunction) { this.Method = method; - this.Name = Method?.Name; + this.Name = method.Name; this.CodeSize = codeSize; this.GenericContext = genericContext; this.Body = body; - this.ReturnType = Method?.ReturnType; - this.Parameters = Method?.Parameters; + this.ReturnType = method.ReturnType; + this.Parameters = method.Parameters; this.Variables = new ILVariableCollection(this); this.LocalFunctions = new InstructionCollection(this, 1); this.kind = kind; @@ -207,15 +204,15 @@ namespace ICSharpCode.Decompiler.IL /// /// This constructor is only to be used by the TransformExpressionTrees step. /// - internal ILFunction(IType returnType, IReadOnlyList parameters, GenericContext genericContext, ILInstruction body) : base(OpCode.ILFunction) + internal ILFunction(IType returnType, IReadOnlyList parameters, GenericContext genericContext, ILInstruction body, ILFunctionKind kind = ILFunctionKind.TopLevelFunction) : base(OpCode.ILFunction) { this.GenericContext = genericContext; this.Body = body; - this.ReturnType = returnType; - this.Parameters = parameters; + this.ReturnType = returnType ?? throw new ArgumentNullException(nameof(returnType)); + this.Parameters = parameters ?? throw new ArgumentNullException(nameof(parameters)); this.Variables = new ILVariableCollection(this); this.LocalFunctions = new InstructionCollection(this, 1); - this.kind = ILFunctionKind.ExpressionTree; + this.kind = kind; } internal override void CheckInvariant(ILPhase phase) diff --git a/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs index 6faacc0a0..63924b18e 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs @@ -130,7 +130,16 @@ namespace ICSharpCode.Decompiler.IL clone.Sections.AddRange(this.Sections.Select(h => (SwitchSection)h.Clone())); return clone; } - + + StackType resultType = StackType.Void; + + public override StackType ResultType => resultType; + + public void SetResultType(StackType resultType) + { + this.resultType = resultType; + } + internal override void CheckInvariant(ILPhase phase) { base.CheckInvariant(phase); @@ -143,12 +152,25 @@ namespace ICSharpCode.Decompiler.IL } Debug.Assert(!section.Labels.IsEmpty || section.HasNullLabel); Debug.Assert(!section.Labels.Overlaps(sets)); + Debug.Assert(section.Body.ResultType == this.ResultType); sets = sets.UnionWith(section.Labels); } Debug.Assert(sets.SetEquals(LongSet.Universe), "switch does not handle all possible cases"); Debug.Assert(!expectNullSection, "Lifted switch is missing 'case null'"); Debug.Assert(this.IsLifted ? (value.ResultType == StackType.O) : (value.ResultType == StackType.I4 || value.ResultType == StackType.I8)); } + + public SwitchSection GetDefaultSection() + { + // Pick the section with the most labels as default section. + IL.SwitchSection defaultSection = Sections.First(); + foreach (var section in Sections) { + if (section.Labels.Count() > defaultSection.Labels.Count()) { + defaultSection = section; + } + } + return defaultSection; + } } partial class SwitchSection diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index d95cc63da..86541e59a 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -20,6 +20,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Reflection.Metadata; + using Humanizer; using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.TypeSystem; @@ -51,6 +53,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms ILTransformContext context; string[] currentFieldNames; Dictionary reservedVariableNames; + Dictionary localFunctionMapping; HashSet loopCounters; const char maxLoopVariableName = 'n'; @@ -59,6 +62,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms this.context = context; currentFieldNames = function.Method.DeclaringTypeDefinition.Fields.Select(f => f.Name).ToArray(); reservedVariableNames = new Dictionary(); + localFunctionMapping = new Dictionary(); loopCounters = CollectLoopCounters(function); foreach (var f in function.Descendants.OfType()) { if (f.Method != null) { @@ -180,6 +184,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!LocalFunctionDecompiler.ParseLocalFunctionName(localFunction.Name, out _, out var newName) || !IsValidName(newName)) newName = null; localFunction.Name = newName; + localFunction.ReducedMethod.Name = newName; } // Now generate names: var mapping = new Dictionary(ILVariableEqualityComparer.Instance); @@ -199,6 +204,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms newName = GetAlternativeName("f"); } localFunction.Name = newName; + localFunction.ReducedMethod.Name = newName; + localFunctionMapping[(MethodDefinitionHandle)localFunction.ReducedMethod.MetadataToken] = newName; + } + foreach (var inst in function.Descendants) { + LocalFunctionMethod localFunction; + switch (inst) { + case Call call: + localFunction = call.Method as LocalFunctionMethod; + break; + case LdFtn ldftn: + localFunction = ldftn.Method as LocalFunctionMethod; + break; + default: + localFunction = null; + break; + } + if (localFunction == null || !localFunctionMapping.TryGetValue((MethodDefinitionHandle)localFunction.MetadataToken, out var name)) + continue; + localFunction.Name = name; } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs b/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs index b96909efc..4c5fe176d 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs @@ -138,7 +138,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms }; block.Instructions.Insert(i++, new StLoc(uninlinedArgs[j], arg)); } - CollectionExtensions.AddRange(v.Function.Variables, uninlinedArgs); + v.Function.Variables.AddRange(uninlinedArgs); // perform copy propagation: foreach (var expr in v.LoadInstructions.ToArray()) { var clone = copiedExpr.Clone(); diff --git a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs index b5dc0423f..27143e9b3 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs @@ -25,9 +25,9 @@ using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.Decompiler.IL.Transforms { /// - /// + /// Transforms anonymous methods and lambdas by creating nested ILFunctions. /// - class DelegateConstruction : IILTransform + public class DelegateConstruction : IILTransform { ILTransformContext context; ITypeResolveContext decompilationContext; diff --git a/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs index 8b5b3c6e7..8bba5a9a0 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs @@ -25,7 +25,7 @@ using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.Decompiler.IL.Transforms { - class EarlyExpressionTransforms : ILVisitor, IILTransform + public class EarlyExpressionTransforms : ILVisitor, IILTransform { ILTransformContext context; diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 97c17d1a8..cc4ec940a 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -65,8 +65,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms { if (container.Kind == ContainerKind.Switch) { // Special case for switch: Only visit the switch condition block. - var switchInst = (SwitchInstruction)container.EntryPoint.Instructions[0]; + var switchInst = (SwitchInstruction)container.EntryPoint.Instructions[0]; switchInst.Value.AcceptVisitor(this); + + HandleSwitchExpression(container, switchInst); } // No need to call base.VisitBlockContainer, see comment in VisitBlock. } @@ -121,12 +123,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms inst.Kind = ComparisonKind.Equality; } } - + var rightWithoutConv = inst.Right.UnwrapConv(ConversionKind.SignExtend).UnwrapConv(ConversionKind.ZeroExtend); if (rightWithoutConv.MatchLdcI4(0) - && inst.Sign == Sign.Unsigned - && (inst.Kind == ComparisonKind.GreaterThan || inst.Kind == ComparisonKind.LessThanOrEqual)) - { + && inst.Sign == Sign.Unsigned + && (inst.Kind == ComparisonKind.GreaterThan || inst.Kind == ComparisonKind.LessThanOrEqual)) { if (inst.Kind == ComparisonKind.GreaterThan) { context.Step("comp.unsigned(left > ldc.i4 0) => comp(left != ldc.i4 0)", inst); inst.Kind = ComparisonKind.Inequality; @@ -169,21 +170,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } } - + protected internal override void VisitConv(Conv inst) { inst.Argument.AcceptVisitor(this); if (inst.Argument.MatchLdLen(StackType.I, out ILInstruction array) && inst.TargetType.IsIntegerType() - && (!inst.CheckForOverflow || context.Settings.AssumeArrayLengthFitsIntoInt32)) - { + && (!inst.CheckForOverflow || context.Settings.AssumeArrayLengthFitsIntoInt32)) { context.Step("conv.i4(ldlen array) => ldlen.i4(array)", inst); inst.AddILRange(inst.Argument); inst.ReplaceWith(new LdLen(inst.TargetType.GetStackType(), array).WithILRange(inst)); return; } - if (inst.TargetType.IsFloatType() && inst.Argument is Conv conv - && conv.Kind == ConversionKind.IntToFloat && conv.TargetType == PrimitiveType.R) - { + if (inst.TargetType.IsFloatType() && inst.Argument is Conv conv + && conv.Kind == ConversionKind.IntToFloat && conv.TargetType == PrimitiveType.R) { // IL conv.r.un does not indicate whether to convert the target type to R4 or R8, // so the C# compiler usually follows it with an explicit conv.r4 or conv.r8. // To avoid emitting '(float)(double)val', we combine these two conversions: @@ -270,7 +269,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms arg.AcceptVisitor(this); } } - + protected internal override void VisitCall(Call inst) { var expr = EarlyExpressionTransforms.HandleCall(inst, context); @@ -430,9 +429,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms } else if (args.Count == 5) { int lo, mid, hi, isNegative, scale; if (args[0].MatchLdcI4(out lo) && args[1].MatchLdcI4(out mid) && - args[2].MatchLdcI4(out hi) && args[3].MatchLdcI4(out isNegative) && - args[4].MatchLdcI4(out scale)) - { + args[2].MatchLdcI4(out hi) && args[3].MatchLdcI4(out isNegative) && + args[4].MatchLdcI4(out scale)) { result = new LdcDecimal(new decimal(lo, mid, hi, isNegative != 0, (byte)scale)); return true; } @@ -501,8 +499,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms // Be careful: when both LHS and RHS are the constant 1, we must not // swap the arguments as it would lead to an infinite transform loop. if (inst.TrueInst.MatchLdcI4(0) && !inst.FalseInst.MatchLdcI4(0) - || inst.FalseInst.MatchLdcI4(1) && !inst.TrueInst.MatchLdcI4(1)) - { + || inst.FalseInst.MatchLdcI4(1) && !inst.TrueInst.MatchLdcI4(1)) { context.Step("canonicalize logic and/or", inst); var t = inst.TrueInst; inst.TrueInst = inst.FalseInst; @@ -531,6 +528,112 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } + IfInstruction HandleConditionalOperator(IfInstruction inst) + { + // if (cond) stloc (A, V1) else stloc (A, V2) --> stloc (A, if (cond) V1 else V2) + Block trueInst = inst.TrueInst as Block; + if (trueInst == null || trueInst.Instructions.Count != 1) + return inst; + Block falseInst = inst.FalseInst as Block; + if (falseInst == null || falseInst.Instructions.Count != 1) + return inst; + ILVariable v; + ILInstruction value1, value2; + if (trueInst.Instructions[0].MatchStLoc(out v, out value1) && falseInst.Instructions[0].MatchStLoc(v, out value2)) { + context.Step("conditional operator", inst); + var newIf = new IfInstruction(Comp.LogicNot(inst.Condition), value2, value1); + newIf.AddILRange(inst); + inst.ReplaceWith(new StLoc(v, newIf)); + context.RequestRerun(); // trigger potential inlining of the newly created StLoc + return newIf; + } + return inst; + } + + private void HandleSwitchExpression(BlockContainer container, SwitchInstruction switchInst) + { + if (!context.Settings.SwitchExpressions) + return; + Debug.Assert(container.Kind == ContainerKind.Switch); + Debug.Assert(container.ResultType == StackType.Void); + var defaultSection = switchInst.GetDefaultSection(); + StackType resultType = StackType.Void; + BlockContainer leaveTarget = null; + ILVariable resultVariable = null; + foreach (var section in switchInst.Sections) { + if (section != defaultSection) { + // every section except for the default must have exactly 1 label + if (section.Labels.Count() != (section.HasNullLabel ? 0u : 1u)) + return; + } + if (!section.Body.MatchBranch(out var sectionBlock)) + return; + if (sectionBlock.IncomingEdgeCount != 1) + return; + if (sectionBlock.Parent != container) + return; + if (sectionBlock.Instructions.Count == 1) { + if (sectionBlock.Instructions[0] is Throw) { + // OK + } else if (sectionBlock.Instructions[0] is Leave leave) { + if (!leave.IsLeavingFunction) + return; + leaveTarget ??= leave.TargetContainer; + Debug.Assert(leaveTarget == leave.TargetContainer); + resultType = leave.Value.ResultType; + } else { + return; + } + } else if (sectionBlock.Instructions.Count == 2) { + if (!sectionBlock.Instructions[0].MatchStLoc(out var v, out _)) + return; + if (!sectionBlock.Instructions[1].MatchLeave(container)) + return; + resultVariable ??= v; + if (resultVariable != v) + return; + resultType = resultVariable.StackType; + } else { + return; + } + } + // Exactly one of resultVariable/leaveTarget must be null + if ((resultVariable == null) == (leaveTarget == null)) + return; + if (switchInst.Value is StringToInt str2int) { + // validate that each integer is used for exactly one value + var integersUsed = new HashSet(); + foreach ((string key, int val) in str2int.Map) { + if (!integersUsed.Add(val)) + return; + } + } + + context.Step("Switch Expression", switchInst); + switchInst.SetResultType(resultType); + foreach (var section in switchInst.Sections) { + var block = ((Branch)section.Body).TargetBlock; + if (block.Instructions.Count == 1) { + if (block.Instructions[0] is Throw t) { + t.resultType = resultType; + section.Body = t; + } else if (block.Instructions[0] is Leave leave) { + section.Body = leave.Value; + } else { + throw new InvalidOperationException(); + } + } else { + section.Body = ((StLoc)block.Instructions[0]).Value; + } + } + if (resultVariable != null) { + container.ReplaceWith(new StLoc(resultVariable, switchInst)); + } else { + container.ReplaceWith(new Leave(leaveTarget, switchInst)); + } + context.RequestRerun(); // new StLoc might trigger inlining + } + /// /// op is either add or remove/subtract: /// if (dynamic.isevent (target)) { @@ -641,28 +744,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms inst.ReplaceWith(new DynamicCompoundAssign(binaryOp.Operation, binaryOp.BinderFlags, binaryOp.Left, binaryOp.LeftArgumentInfo, binaryOp.Right, binaryOp.RightArgumentInfo)); } - IfInstruction HandleConditionalOperator(IfInstruction inst) - { - // if (cond) stloc (A, V1) else stloc (A, V2) --> stloc (A, if (cond) V1 else V2) - Block trueInst = inst.TrueInst as Block; - if (trueInst == null || trueInst.Instructions.Count != 1) - return inst; - Block falseInst = inst.FalseInst as Block; - if (falseInst == null || falseInst.Instructions.Count != 1) - return inst; - ILVariable v; - ILInstruction value1, value2; - if (trueInst.Instructions[0].MatchStLoc(out v, out value1) && falseInst.Instructions[0].MatchStLoc(v, out value2)) { - context.Step("conditional operator", inst); - var newIf = new IfInstruction(Comp.LogicNot(inst.Condition), value2, value1); - newIf.AddILRange(inst); - inst.ReplaceWith(new StLoc(v, newIf)); - context.RequestRerun(); // trigger potential inlining of the newly created StLoc - return newIf; - } - return inst; - } - protected internal override void VisitBinaryNumericInstruction(BinaryNumericInstruction inst) { base.VisitBinaryNumericInstruction(inst); @@ -670,8 +751,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms case BinaryNumericOperator.ShiftLeft: case BinaryNumericOperator.ShiftRight: if (inst.Right.MatchBinaryNumericInstruction(BinaryNumericOperator.BitAnd, out var lhs, out var rhs) - && rhs.MatchLdcI4(inst.ResultType == StackType.I8 ? 63 : 31)) - { + && rhs.MatchLdcI4(inst.ResultType == StackType.I8 ? 63 : 31)) { // a << (b & 31) => a << b context.Step("Combine bit.and into shift", inst); inst.Right = lhs; @@ -679,8 +759,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms break; case BinaryNumericOperator.BitAnd: if (inst.Left.InferType(context.TypeSystem).IsKnownType(KnownTypeCode.Boolean) - && inst.Right.InferType(context.TypeSystem).IsKnownType(KnownTypeCode.Boolean)) - { + && inst.Right.InferType(context.TypeSystem).IsKnownType(KnownTypeCode.Boolean)) { if (new NullableLiftingTransform(context).Run(inst)) { // e.g. "(a.GetValueOrDefault() == b.GetValueOrDefault()) & (a.HasValue & b.HasValue)" } @@ -688,7 +767,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms break; } } - + protected internal override void VisitTryCatchHandler(TryCatchHandler inst) { base.VisitTryCatchHandler(inst); diff --git a/ICSharpCode.Decompiler/IL/Transforms/FixLoneIsInst.cs b/ICSharpCode.Decompiler/IL/Transforms/FixLoneIsInst.cs index 3b270f62b..6d5367e75 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/FixLoneIsInst.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/FixLoneIsInst.cs @@ -28,7 +28,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// This transform un-inlines the argument of `isinst` instructions that can't be directly translated to C#, /// thus allowing the emulation via "expr is T ? (T)expr : null". /// - class FixLoneIsInst : IILTransform + public class FixLoneIsInst : IILTransform { void IILTransform.Run(ILFunction function, ILTransformContext context) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index ab26ec7b5..db93a64bc 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -53,15 +53,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms public void Run(Block block, int pos, StatementTransformContext context) { - InlineOneIfPossible(block, pos, OptionsForBlock(block, pos), context: context); + InlineOneIfPossible(block, pos, OptionsForBlock(block, pos, context), context: context); } - internal static InliningOptions OptionsForBlock(Block block, int pos) + internal static InliningOptions OptionsForBlock(Block block, int pos, ILTransformContext context) { InliningOptions options = InliningOptions.None; - if (IsCatchWhenBlock(block)) + if (context.Settings.AggressiveInlining || IsCatchWhenBlock(block)) { options |= InliningOptions.Aggressive; - else { + } else { var function = block.Ancestors.OfType().FirstOrDefault(); var inst = block.Instructions[pos]; if (IsInConstructorInitializer(function, inst)) @@ -77,7 +77,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms for (int i = instructions.Count - 1; i >= 0; i--) { if (instructions[i] is StLoc inst) { InliningOptions options = InliningOptions.None; - if (IsCatchWhenBlock(block) || IsInConstructorInitializer(function, inst)) + if (context.Settings.AggressiveInlining || IsCatchWhenBlock(block) || IsInConstructorInitializer(function, inst)) options = InliningOptions.Aggressive; if (InlineOneIfPossible(block, i, options, context)) { modified = true; @@ -418,6 +418,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms case OpCode.NumericCompoundAssign: case OpCode.UserDefinedCompoundAssign: case OpCode.Await: + case OpCode.SwitchInstruction: return true; case OpCode.LdLoc: if (v.StateMachineField == null && ((LdLoc)inlinedExpression).Variable.StateMachineField != null) { @@ -479,32 +480,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } break; + case OpCode.Leave: + case OpCode.YieldReturn: + return true; + case OpCode.SwitchInstruction: + //case OpCode.BinaryNumericInstruction when parent.SlotInfo == SwitchInstruction.ValueSlot: + case OpCode.StringToInt when parent.SlotInfo == SwitchInstruction.ValueSlot: + return true; } // decide based on the top-level target instruction into which we are inlining: switch (next.OpCode) { - case OpCode.Leave: - case OpCode.YieldReturn: - return parent == next; case OpCode.IfInstruction: while (parent.MatchLogicNot(out _)) { parent = parent.Parent; } return parent == next; - case OpCode.BlockContainer: - if (((BlockContainer)next).EntryPoint.Instructions[0] is SwitchInstruction switchInst) { - next = switchInst; - goto case OpCode.SwitchInstruction; - } else { - return false; - } - case OpCode.SwitchInstruction: - if (parent == next) - return true; - if (parent.MatchBinaryNumericInstruction(BinaryNumericOperator.Sub) && parent.Parent == next) - return true; - if (parent is StringToInt stringToInt && stringToInt.Parent == next) - return true; - return false; default: return false; } diff --git a/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs index 22e62edf0..651113ce6 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs @@ -205,6 +205,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!CheckContainerLengthVariableUseCount(containerLengthVar, startIndexKind)) { return; } + if (!call.IsDescendantOf(block.Instructions[pos])) + return; // startOffsetVar might be used deep inside a complex statement, ensure we can inline up to that point: for (int i = startPos; i < pos; i++) { if (!ILInlining.CanInlineInto(block.Instructions[pos], startOffsetVar, block.Instructions[i])) @@ -275,6 +277,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms } if (!(sliceLengthVar.LoadInstructions.Single().Parent is CallInstruction call)) return; + if (!call.IsDescendantOf(block.Instructions[pos])) + return; if (!IsSlicingMethod(call.Method)) return; if (call.Arguments.Count != 3) diff --git a/ICSharpCode.Decompiler/IL/Transforms/IntroduceDynamicTypeOnLocals.cs b/ICSharpCode.Decompiler/IL/Transforms/IntroduceDynamicTypeOnLocals.cs index 08e90f086..a16786ea1 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/IntroduceDynamicTypeOnLocals.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/IntroduceDynamicTypeOnLocals.cs @@ -24,7 +24,7 @@ using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.Decompiler.IL { - class IntroduceDynamicTypeOnLocals : IILTransform + public class IntroduceDynamicTypeOnLocals : IILTransform { public void Run(ILFunction function, ILTransformContext context) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/IntroduceRefReadOnlyModifierOnLocals.cs b/ICSharpCode.Decompiler/IL/Transforms/IntroduceRefReadOnlyModifierOnLocals.cs index d75d3096b..233bf87ef 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/IntroduceRefReadOnlyModifierOnLocals.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/IntroduceRefReadOnlyModifierOnLocals.cs @@ -25,7 +25,7 @@ using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.Decompiler.IL { - class IntroduceRefReadOnlyModifierOnLocals : IILTransform + public class IntroduceRefReadOnlyModifierOnLocals : IILTransform { public void Run(ILFunction function, ILTransformContext context) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index 0a553839c..003462046 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -36,7 +36,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// Decompiler step for C# 7.0 local functions /// - class LocalFunctionDecompiler : IILTransform + public class LocalFunctionDecompiler : IILTransform { ILTransformContext context; ITypeResolveContext resolveContext; @@ -484,7 +484,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms break; parametersToRemove++; } - return new LocalFunctionMethod(method, parametersToRemove, typeParametersToRemove); + return new LocalFunctionMethod(method, method.Name, CanBeStaticLocalFunction(), parametersToRemove, typeParametersToRemove); + + bool CanBeStaticLocalFunction() + { + if (!context.Settings.StaticLocalFunctions) + return false; + // Cannot be static because there are closure parameters that will be removed + if (parametersToRemove > 0) + return false; + // no closure parameters, but static: + // we can safely assume, this local function can be declared static + if (method.IsStatic) + return true; + // the local function is used in conjunction with a lambda, which means, + // it is defined inside the display-class type + var declaringType = method.DeclaringTypeDefinition; + if (!declaringType.IsCompilerGenerated()) + return false; + // if there are no instance fields, we can make it a static local function + return !declaringType.GetFields(f => !f.IsStatic).Any(); + } } static void TransformToLocalFunctionReference(ILFunction function, CallInstruction useSite) diff --git a/ICSharpCode.Decompiler/IL/Transforms/LockTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/LockTransform.cs index 1c273dd76..ab7a4bb83 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LockTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LockTransform.cs @@ -25,7 +25,7 @@ using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.Decompiler.IL.Transforms { - class LockTransform : IBlockTransform + public class LockTransform : IBlockTransform { BlockTransformContext context; diff --git a/ICSharpCode.Decompiler/IL/Transforms/NamedArgumentTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NamedArgumentTransform.cs index fb9bb125a..3b4c2c3dd 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NamedArgumentTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NamedArgumentTransform.cs @@ -10,9 +10,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms using FindResult = ILInlining.FindResult; using FindResultType = ILInlining.FindResultType; - class NamedArgumentTransform : IStatementTransform + public class NamedArgumentTransform : IStatementTransform { - public static FindResult CanIntroduceNamedArgument(CallInstruction call, ILInstruction child, ILVariable v, ILInstruction expressionBeingMoved) + internal static FindResult CanIntroduceNamedArgument(CallInstruction call, ILInstruction child, ILVariable v, ILInstruction expressionBeingMoved) { Debug.Assert(child.Parent == call); if (call.IsInstanceCall && child.ChildIndex == 0) @@ -106,7 +106,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms { if (!context.Settings.NamedArguments) return; - var options = ILInlining.OptionsForBlock(block, pos); + var options = ILInlining.OptionsForBlock(block, pos, context); options |= InliningOptions.IntroduceNamedArguments; ILInlining.InlineOneIfPossible(block, pos, options, context: context); } diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs index 2a0780636..e251c23cd 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs @@ -31,7 +31,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// The ?? operator for nullable value types is handled by NullableLiftingTransform. /// - class NullCoalescingTransform : IStatementTransform + public class NullCoalescingTransform : IStatementTransform { public void Run(Block block, int pos, StatementTransformContext context) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs index 93d7f90f5..798c03f19 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs @@ -489,7 +489,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } - class NullPropagationStatementTransform : IStatementTransform + public class NullPropagationStatementTransform : IStatementTransform { public void Run(Block block, int pos, StatementTransformContext context) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs index 38b8bb942..80775465c 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs @@ -908,7 +908,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms #endregion } - class NullableLiftingStatementTransform : IStatementTransform + public class NullableLiftingStatementTransform : IStatementTransform { public void Run(Block block, int pos, StatementTransformContext context) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnNullableTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnNullableTransform.cs index b63ba77d9..9d022a3c8 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnNullableTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnNullableTransform.cs @@ -28,7 +28,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// Detects switch-on-nullable patterns employed by the C# compiler and transforms them to an ILAst-switch-instruction. /// - class SwitchOnNullableTransform : IILTransform + public class SwitchOnNullableTransform : IILTransform { public void Run(ILFunction function, ILTransformContext context) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs index 6385ee159..bab175088 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs @@ -31,7 +31,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// Detects switch-on-string patterns employed by the C# compiler and transforms them to an ILAst-switch-instruction. /// - class SwitchOnStringTransform : IILTransform + public class SwitchOnStringTransform : IILTransform { ILTransformContext context; diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs index d33d9d0f9..8af899362 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs @@ -43,7 +43,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// Note that 2) and 3) apply because declarations and uses of lambdas and local functions /// are already transformed by the time this transform is applied. /// - class TransformDisplayClassUsage : ILVisitor, IILTransform + public class TransformDisplayClassUsage : ILVisitor, IILTransform { class VariableToDeclare { @@ -68,7 +68,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms public void Propagate(ILVariable variable) { - Debug.Assert(declaredVariable == null || (variable == null && declaredVariable.StateMachineField == null)); this.declaredVariable = variable; this.CanPropagate = variable != null; } @@ -456,7 +455,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms { ILVariable v; switch (value) { - case LdLoc load when load.Variable.StateMachineField == null: + case LdLoc load: v = load.Variable; if (v.Kind == VariableKind.Parameter) { if (v.LoadCount != 1 && !v.IsThis()) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs index 4ed88e0c7..725bce082 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs @@ -156,8 +156,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms var container = new BlockContainer(); container.AddILRange(instruction); var functionType = instruction.Method.ReturnType.TypeArguments[0]; - var returnType = functionType.GetDelegateInvokeMethod()?.ReturnType; - var function = new ILFunction(returnType, parameterList, context.Function.GenericContext, container); + var returnType = functionType.GetDelegateInvokeMethod()?.ReturnType ?? SpecialType.UnknownType; + var function = new ILFunction(returnType, parameterList, context.Function.GenericContext, container, ILFunctionKind.ExpressionTree); function.DelegateType = functionType; function.Kind = IsExpressionTree(functionType) ? ILFunctionKind.ExpressionTree : ILFunctionKind.Delegate; function.Variables.AddRange(parameterVariablesList); diff --git a/ICSharpCode.Decompiler/IL/Transforms/UserDefinedLogicTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/UserDefinedLogicTransform.cs index 826228b99..ec6eeaf91 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/UserDefinedLogicTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/UserDefinedLogicTransform.cs @@ -26,7 +26,7 @@ using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.IL.Transforms { - class UserDefinedLogicTransform : IStatementTransform + public class UserDefinedLogicTransform : IStatementTransform { void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs index 31c8c91d5..06375f71c 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs @@ -25,7 +25,7 @@ using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.Decompiler.IL.Transforms { - class UsingTransform : IBlockTransform + public class UsingTransform : IBlockTransform { BlockTransformContext context; diff --git a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs index 740ecc0d4..c5c8722cf 100644 --- a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs +++ b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs @@ -61,29 +61,30 @@ namespace ICSharpCode.Decompiler.Metadata }; readonly DotNetCorePackageInfo[] packages; - ISet packageBasePaths = new HashSet(StringComparer.Ordinal); - readonly Version version; + readonly List searchPaths = new List(); + readonly List packageBasePaths = new List(); + readonly Version targetFrameworkVersion; readonly string dotnetBasePath = FindDotNetExeDirectory(); - public DotNetCorePathFinder(Version version) + public DotNetCorePathFinder(TargetFrameworkIdentifier targetFramework, Version targetFrameworkVersion) { - this.version = version; - } - - public DotNetCorePathFinder(string parentAssemblyFileName, string targetFrameworkIdString, TargetFrameworkIdentifier targetFramework, Version version, ReferenceLoadInfo loadInfo = null) - { - string assemblyName = Path.GetFileNameWithoutExtension(parentAssemblyFileName); - string basePath = Path.GetDirectoryName(parentAssemblyFileName); - this.version = version; + this.targetFrameworkVersion = targetFrameworkVersion; if (targetFramework == TargetFrameworkIdentifier.NETStandard) { // .NET Standard 2.1 is implemented by .NET Core 3.0 or higher - if (version.Major == 2 && version.Minor == 1) { - this.version = new Version(3, 0, 0); + if (targetFrameworkVersion.Major == 2 && targetFrameworkVersion.Minor == 1) { + this.targetFrameworkVersion = new Version(3, 0, 0); } } + } + + public DotNetCorePathFinder(string parentAssemblyFileName, string targetFrameworkIdString, TargetFrameworkIdentifier targetFramework, Version targetFrameworkVersion, ReferenceLoadInfo loadInfo = null) + : this(targetFramework, targetFrameworkVersion) + { + string assemblyName = Path.GetFileNameWithoutExtension(parentAssemblyFileName); + string basePath = Path.GetDirectoryName(parentAssemblyFileName); - packageBasePaths.Add(basePath); + searchPaths.Add(basePath); var depsJsonFileName = Path.Combine(basePath, $"{assemblyName}.deps.json"); if (File.Exists(depsJsonFileName)) { @@ -106,17 +107,17 @@ namespace ICSharpCode.Decompiler.Metadata public void AddSearchDirectory(string path) { - this.packageBasePaths.Add(path); + this.searchPaths.Add(path); } public void RemoveSearchDirectory(string path) { - this.packageBasePaths.Remove(path); + this.searchPaths.Remove(path); } public string TryResolveDotNetCore(IAssemblyReference name) { - foreach (var basePath in packageBasePaths) { + foreach (var basePath in searchPaths.Concat(packageBasePaths)) { if (File.Exists(Path.Combine(basePath, name.Name + ".dll"))) { return Path.Combine(basePath, name.Name + ".dll"); } else if (File.Exists(Path.Combine(basePath, name.Name + ".exe"))) { @@ -124,7 +125,7 @@ namespace ICSharpCode.Decompiler.Metadata } } - return FallbackToDotNetSharedDirectory(name, version); + return FallbackToDotNetSharedDirectory(name); } internal string GetReferenceAssemblyPath(string targetFramework) @@ -169,7 +170,7 @@ namespace ICSharpCode.Decompiler.Metadata } } - string FallbackToDotNetSharedDirectory(IAssemblyReference name, Version version) + string FallbackToDotNetSharedDirectory(IAssemblyReference name) { if (dotnetBasePath == null) return null; @@ -177,7 +178,7 @@ namespace ICSharpCode.Decompiler.Metadata foreach (var basePath in basePaths) { if (!Directory.Exists(basePath)) continue; - var closestVersion = GetClosestVersionFolder(basePath, version); + var closestVersion = GetClosestVersionFolder(basePath, targetFrameworkVersion); 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"))) { @@ -189,15 +190,17 @@ namespace ICSharpCode.Decompiler.Metadata static string GetClosestVersionFolder(string basePath, Version version) { - string result = null; - foreach (var folder in new DirectoryInfo(basePath).GetDirectories().Select(d => ConvertToVersion(d.Name)).Where(v => v.Item1 != null).OrderByDescending(v => v.Item1)) { - if (folder.Item1 >= version) - result = folder.Item2; + var foundVersions = new DirectoryInfo(basePath).GetDirectories() + .Select(d => ConvertToVersion(d.Name)) + .Where(v => v.version != null); + foreach (var folder in foundVersions.OrderBy(v => v.Item1)) { + if (folder.version >= version) + return folder.directoryName; } - return result ?? version.ToString(); + return version.ToString(); } - internal static (Version, string) ConvertToVersion(string name) + internal static (Version version, string directoryName) ConvertToVersion(string name) { string RemoveTrailingVersionInfo() { diff --git a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs index 2be6f8072..dd7aee077 100644 --- a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs +++ b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs @@ -20,20 +20,26 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; -using ICSharpCode.Decompiler.TypeSystem.Implementation; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; +using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.Decompiler.Metadata { public static class DotNetCorePathFinderExtensions { - static readonly string RefPathPattern = + static readonly string PathPattern = @"(Reference Assemblies[/\\]Microsoft[/\\]Framework[/\\](?.NETFramework)[/\\]v(?[^/\\]+)[/\\])" + @"|((?Microsoft\.NET)[/\\]assembly[/\\]GAC_(MSIL|32|64)[/\\])" + @"|((?Microsoft\.NET)[/\\]Framework(64)?[/\\](?[^/\\]+)[/\\])" + @"|(NuGetFallbackFolder[/\\](?[^/\\]+)\\(?[^/\\]+)([/\\].*)?[/\\]ref[/\\])" + - @"|(shared[/\\](?[^/\\]+)\\(?[^/\\]+)([/\\].*)?[/\\])"; + @"|(shared[/\\](?[^/\\]+)\\(?[^/\\]+)([/\\].*)?[/\\])" + + @"|(packs[/\\](?[^/\\]+)\\(?[^/\\]+)\\ref([/\\].*)?[/\\])"; + + static readonly string RefPathPattern = + @"(Reference Assemblies[/\\]Microsoft[/\\]Framework[/\\](?.NETFramework)[/\\]v(?[^/\\]+)[/\\])" + + @"|(NuGetFallbackFolder[/\\](?[^/\\]+)\\(?[^/\\]+)([/\\].*)?[/\\]ref[/\\])" + + @"|(packs[/\\](?[^/\\]+)\\(?[^/\\]+)\\ref([/\\].*)?[/\\])"; public static string DetectTargetFrameworkId(this PEFile assembly) { @@ -49,44 +55,52 @@ namespace ICSharpCode.Decompiler.Metadata var reader = assembly.GetMetadataReader(); foreach (var h in reader.GetCustomAttributes(Handle.AssemblyDefinition)) { - var attribute = reader.GetCustomAttribute(h); - if (attribute.GetAttributeType(reader).GetFullTypeName(reader).ToString() != TargetFrameworkAttributeName) - continue; - var blobReader = reader.GetBlobReader(attribute.Value); - if (blobReader.ReadUInt16() == 0x0001) { - return blobReader.ReadSerializedString(); + try { + var attribute = reader.GetCustomAttribute(h); + if (attribute.GetAttributeType(reader).GetFullTypeName(reader).ToString() != TargetFrameworkAttributeName) + continue; + var blobReader = reader.GetBlobReader(attribute.Value); + if (blobReader.ReadUInt16() == 0x0001) { + return blobReader.ReadSerializedString(); + } + } catch (BadImageFormatException) { + // ignore malformed attributes } } foreach (var h in reader.AssemblyReferences) { - var r = reader.GetAssemblyReference(h); - if (r.PublicKeyOrToken.IsNil) - continue; - string version; - switch (reader.GetString(r.Name)) { - case "netstandard": - version = r.Version.ToString(3); - return $".NETStandard,Version=v{version}"; - case "System.Runtime": - // System.Runtime.dll uses the following scheme: - // 4.2.0 => .NET Core 2.0 - // 4.2.1 => .NET Core 2.1 / 3.0 - // 4.2.2 => .NET Core 3.1 - if (r.Version >= new Version(4, 2, 0)) { - version = "2.0"; - if (r.Version >= new Version(4, 2, 1)) { - version = "3.0"; - } - if (r.Version >= new Version(4, 2, 2)) { - version = "3.1"; + try { + var r = reader.GetAssemblyReference(h); + if (r.PublicKeyOrToken.IsNil) + continue; + string version; + switch (reader.GetString(r.Name)) { + case "netstandard": + version = r.Version.ToString(3); + return $".NETStandard,Version=v{version}"; + case "System.Runtime": + // System.Runtime.dll uses the following scheme: + // 4.2.0 => .NET Core 2.0 + // 4.2.1 => .NET Core 2.1 / 3.0 + // 4.2.2 => .NET Core 3.1 + if (r.Version >= new Version(4, 2, 0)) { + version = "2.0"; + if (r.Version >= new Version(4, 2, 1)) { + version = "3.0"; + } + if (r.Version >= new Version(4, 2, 2)) { + version = "3.1"; + } + return $".NETCoreApp,Version=v{version}"; + } else { + continue; } - return $".NETCoreApp,Version=v{version}"; - } else { - continue; - } - case "mscorlib": - version = r.Version.ToString(2); - return $".NETFramework,Version=v{version}"; + case "mscorlib": + version = r.Version.ToString(2); + return $".NETFramework,Version=v{version}"; + } + } catch (BadImageFormatException) { + // ignore malformed references } } @@ -99,7 +113,7 @@ namespace ICSharpCode.Decompiler.Metadata * - .NETCore -> C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.netcore.app\2.1.0\ref\netcoreapp2.1\System.Console.dll * - .NETStandard -> C:\Program Files\dotnet\sdk\NuGetFallbackFolder\netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll */ - var pathMatch = Regex.Match(assemblyPath, RefPathPattern, + var pathMatch = Regex.Match(assemblyPath, PathPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); if (pathMatch.Success) { var type = pathMatch.Groups["type"].Value; @@ -121,6 +135,25 @@ namespace ICSharpCode.Decompiler.Metadata return string.Empty; } + + public static bool IsReferenceAssembly(this PEFile assembly) + { + return IsReferenceAssembly(assembly.Reader, assembly.FileName); + } + + public static bool IsReferenceAssembly(this PEReader assembly, string assemblyPath) + { + if (assembly == null) + throw new ArgumentNullException(nameof(assembly)); + + var metadata = assembly.GetMetadataReader(); + if (metadata.GetCustomAttributes(Handle.AssemblyDefinition).HasKnownAttribute(metadata, KnownAttribute.ReferenceAssembly)) + return true; + + // Try to detect reference assembly through specific path pattern + var refPathMatch = Regex.Match(assemblyPath, RefPathPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + return refPathMatch.Success; + } } public class ReferenceLoadInfo diff --git a/ICSharpCode.Decompiler/Metadata/MetadataExtensions.cs b/ICSharpCode.Decompiler/Metadata/MetadataExtensions.cs index 8d304cc29..dcb2013bd 100644 --- a/ICSharpCode.Decompiler/Metadata/MetadataExtensions.cs +++ b/ICSharpCode.Decompiler/Metadata/MetadataExtensions.cs @@ -95,6 +95,9 @@ namespace ICSharpCode.Decompiler.Metadata public static string ToHexString(this IEnumerable bytes, int estimatedLength) { + if (bytes == null) + throw new ArgumentNullException(nameof(bytes)); + StringBuilder sb = new StringBuilder(estimatedLength * 2); foreach (var b in bytes) sb.AppendFormat("{0:x2}", b); @@ -147,6 +150,7 @@ namespace ICSharpCode.Decompiler.Metadata return Disassembler.DisassemblerHelpers.Escape(name); } + [Obsolete("Use MetadataModule.GetDeclaringModule() instead")] public static IModuleReference GetDeclaringModule(this TypeReferenceHandle handle, MetadataReader reader) { var tr = reader.GetTypeReference(handle); diff --git a/ICSharpCode.Decompiler/Metadata/PEFile.cs b/ICSharpCode.Decompiler/Metadata/PEFile.cs index c261dbb5e..9b69c8a9a 100644 --- a/ICSharpCode.Decompiler/Metadata/PEFile.cs +++ b/ICSharpCode.Decompiler/Metadata/PEFile.cs @@ -75,8 +75,12 @@ namespace ICSharpCode.Decompiler.Metadata public TargetRuntime GetRuntime() { string version = Metadata.MetadataVersion; + if (version == null || version.Length <= 1) + return TargetRuntime.Unknown; switch (version[1]) { case '1': + if (version.Length <= 3) + return TargetRuntime.Unknown; if (version[3] == 1) return TargetRuntime.Net_1_0; else diff --git a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs index 3186fa3d3..f5245a5a2 100644 --- a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs +++ b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs @@ -92,16 +92,19 @@ namespace ICSharpCode.Decompiler.Metadata public UniversalAssemblyResolver(string mainAssemblyFileName, bool throwOnError, string targetFramework, PEStreamOptions streamOptions = PEStreamOptions.Default, MetadataReaderOptions metadataOptions = MetadataReaderOptions.Default) { + this.mainAssemblyFileName = mainAssemblyFileName; + this.throwOnError = throwOnError; this.streamOptions = streamOptions; this.metadataOptions = metadataOptions; this.targetFramework = targetFramework ?? string.Empty; (targetFrameworkIdentifier, targetFrameworkVersion) = ParseTargetFramework(this.targetFramework); - this.mainAssemblyFileName = mainAssemblyFileName; - this.baseDirectory = Path.GetDirectoryName(mainAssemblyFileName); - this.throwOnError = throwOnError; - if (string.IsNullOrWhiteSpace(this.baseDirectory)) - this.baseDirectory = Environment.CurrentDirectory; - AddSearchDirectory(baseDirectory); + + if (mainAssemblyFileName != null) { + string baseDirectory = Path.GetDirectoryName(mainAssemblyFileName); + if (string.IsNullOrWhiteSpace(this.baseDirectory)) + this.baseDirectory = Environment.CurrentDirectory; + AddSearchDirectory(baseDirectory); + } } internal static (TargetFrameworkIdentifier, Version) ParseTargetFramework(string targetFramework) @@ -191,7 +194,10 @@ namespace ICSharpCode.Decompiler.Metadata if (IsZeroOrAllOnes(targetFrameworkVersion)) goto default; if (dotNetCorePathFinder == null) { - dotNetCorePathFinder = new DotNetCorePathFinder(mainAssemblyFileName, targetFramework, targetFrameworkIdentifier, targetFrameworkVersion); + if (mainAssemblyFileName == null) + dotNetCorePathFinder = new DotNetCorePathFinder(targetFrameworkIdentifier, targetFrameworkVersion); + else + dotNetCorePathFinder = new DotNetCorePathFinder(mainAssemblyFileName, targetFramework, targetFrameworkIdentifier, targetFrameworkVersion); foreach (var directory in directories) { dotNetCorePathFinder.AddSearchDirectory(directory); } @@ -327,9 +333,13 @@ namespace ICSharpCode.Decompiler.Metadata if (assembly != null) return assembly; - assembly = SearchDirectory(name, framework_dirs); - if (assembly != null) - return assembly; + // when decompiling assemblies that target frameworks prior to 4.0, we can fall back to the 4.0 assemblies in case the target framework is not installed. + // but when looking for Microsoft.Build.Framework, Version=15.0.0.0 we should not use the version 4.0 assembly here so that the LoadedAssembly logic can instead fall back to version 15.1.0.0 + if (name.Version <= new Version(4, 0, 0, 0)) { + assembly = SearchDirectory(name, framework_dirs); + if (assembly != null) + return assembly; + } if (throwOnError) throw new AssemblyResolutionException(name); @@ -388,6 +398,9 @@ namespace ICSharpCode.Decompiler.Metadata return typeof(object).Module.FullyQualifiedName; } + if (reference.PublicKeyToken == null) + return null; + string path; if (decompilerRuntime == DecompilerRuntime.Mono) { path = GetMonoMscorlibBasePath(version); diff --git a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs index 1ca9a792a..82730d64f 100644 --- a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs +++ b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Linq; + using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.CSharp.Resolver; @@ -26,6 +27,7 @@ using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.TypeSystem.Implementation; namespace ICSharpCode.Decompiler { @@ -39,7 +41,7 @@ namespace ICSharpCode.Decompiler bool inDocumentationComment = false; bool firstUsingDeclaration; bool lastUsingDeclaration; - + public TextTokenWriter(ITextOutput output, DecompilerSettings settings, IDecompilerTypeSystem typeSystem) { if (output == null) @@ -52,13 +54,13 @@ namespace ICSharpCode.Decompiler this.settings = settings; this.typeSystem = typeSystem; } - + public override void WriteIdentifier(Identifier identifier) { if (identifier.IsVerbatim || CSharpOutputVisitor.IsKeyword(identifier.Name, identifier)) { output.Write('@'); } - + var definition = GetCurrentDefinition(); string name = TextWriterTokenWriter.EscapeIdentifier(identifier.Name); switch (definition) { @@ -69,7 +71,7 @@ namespace ICSharpCode.Decompiler output.WriteReference(m, name, true); return; } - + var member = GetCurrentMemberReference(); switch (member) { case IType t: @@ -110,6 +112,7 @@ namespace ICSharpCode.Decompiler if (symbol != null && node.Role == Roles.Type && node.Parent is ObjectCreateExpression) { symbol = node.Parent.GetSymbol(); } + if (node is IdentifierExpression && node.Role == Roles.TargetExpression && node.Parent is InvocationExpression && symbol is IMember member) { var declaringType = member.DeclaringType; if (declaringType != null && declaringType.Kind == TypeKind.Delegate) @@ -123,10 +126,8 @@ namespace ICSharpCode.Decompiler if (symbol == null) return null; - //if (settings.AutomaticEvents && member is FieldDefinition) { - // var field = (FieldDefinition)member; - // return field.DeclaringType.Events.FirstOrDefault(ev => ev.Name == field.Name) ?? member; - //} + if (symbol is LocalFunctionMethod) + return null; return symbol; } @@ -142,14 +143,18 @@ namespace ICSharpCode.Decompiler if (letClauseVariable != null) return letClauseVariable; - var gotoStatement = node as GotoStatement; - if (gotoStatement != null) - { + if (node is GotoStatement gotoStatement) { var method = nodeStack.Select(nd => nd.GetSymbol() as IMethod).FirstOrDefault(mr => mr != null); if (method != null) return method + gotoStatement.Label; } + if (node.Role == Roles.TargetExpression && node.Parent is InvocationExpression) { + var symbol = node.Parent.GetSymbol(); + if (symbol is LocalFunctionMethod) + return symbol; + } + return null; } @@ -177,29 +182,29 @@ namespace ICSharpCode.Decompiler return method + label.Label; } - if (node is LocalFunctionDeclarationStatement) { - var localFunction = node.GetResolveResult() as MemberResolveResult; + if (node is MethodDeclaration && node.Parent is LocalFunctionDeclarationStatement) { + var localFunction = node.Parent.GetResolveResult() as MemberResolveResult; if (localFunction != null) return localFunction.Member; } return null; } - + ISymbol GetCurrentDefinition() { if (nodeStack == null || nodeStack.Count == 0) return null; - + var node = nodeStack.Peek(); if (node is Identifier) node = node.Parent; if (IsDefinition(ref node)) return node.GetSymbol(); - + return null; } - + public override void WriteKeyword(Role role, string keyword) { //To make reference for 'this' and 'base' keywords in the ClassName():this() expression @@ -211,7 +216,7 @@ namespace ICSharpCode.Decompiler } output.Write(keyword); } - + public override void WriteToken(Role role, string token) { switch (token) { @@ -253,22 +258,22 @@ namespace ICSharpCode.Decompiler break; } } - + public override void Space() { output.Write(' '); } - + public override void Indent() { output.Indent(); } - + public override void Unindent() { output.Unindent(); } - + public override void NewLine() { if (!firstUsingDeclaration && lastUsingDeclaration) { @@ -277,7 +282,7 @@ namespace ICSharpCode.Decompiler } output.WriteLine(); } - + public override void WriteComment(CommentType commentType, string content) { switch (commentType) { @@ -309,7 +314,7 @@ namespace ICSharpCode.Decompiler break; } } - + public override void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) { // pre-processor directive must start on its own line @@ -321,7 +326,7 @@ namespace ICSharpCode.Decompiler } output.WriteLine(); } - + public override void WritePrimitiveValue(object value, LiteralFormat format = LiteralFormat.None) { new TextWriterTokenWriter(new TextOutputWriter(output)).WritePrimitiveValue(value, format); @@ -376,7 +381,7 @@ namespace ICSharpCode.Decompiler break; } } - + public override void StartNode(AstNode node) { if (nodeStack.Count == 0) { @@ -390,7 +395,7 @@ namespace ICSharpCode.Decompiler } nodeStack.Push(node); } - + private bool IsUsingDeclaration(AstNode node) { return node is UsingDeclaration || node is UsingAliasDeclaration; @@ -401,10 +406,10 @@ namespace ICSharpCode.Decompiler if (nodeStack.Pop() != node) throw new InvalidOperationException(); } - + public static bool IsDefinition(ref AstNode node) { - if (node is EntityDeclaration) + if (node is EntityDeclaration && !(node.Parent is LocalFunctionDeclarationStatement)) return true; if (node is VariableInitializer && node.Parent is FieldDeclaration) { node = node.Parent; diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs index 98fd65194..9c126bbcf 100644 --- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs +++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs @@ -224,24 +224,26 @@ namespace ICSharpCode.Decompiler.TypeSystem var mainModuleWithOptions = mainModule.WithOptions(typeSystemOptions); var referencedAssembliesWithOptions = referencedAssemblies.Select(file => file.WithOptions(typeSystemOptions)); // Primitive types are necessary to avoid assertions in ILReader. - // Fallback to MinimalCorlib to provide the primitive types. - if (!HasType(KnownTypeCode.Void) || !HasType(KnownTypeCode.Int32)) { - Init(mainModule.WithOptions(typeSystemOptions), referencedAssembliesWithOptions.Concat(new[] { MinimalCorlib.Instance })); + // Other known types are necessary in order for transforms to work (e.g. Task for async transform). + // Figure out which known types are missing from our type system so far: + var missingKnownTypes = KnownTypeReference.AllKnownTypes.Where(IsMissing).ToList(); + if (missingKnownTypes.Count > 0) { + Init(mainModule.WithOptions(typeSystemOptions), referencedAssembliesWithOptions.Concat(new[] { MinimalCorlib.CreateWithTypes(missingKnownTypes) })); } else { Init(mainModuleWithOptions, referencedAssembliesWithOptions); } this.MainModule = (MetadataModule)base.MainModule; - bool HasType(KnownTypeCode code) + bool IsMissing(KnownTypeReference knownType) { - TopLevelTypeName name = KnownTypeReference.Get(code).TypeName; + var name = knownType.TypeName; if (!mainModule.GetTypeDefinition(name).IsNil) - return true; + return false; foreach (var file in referencedAssemblies) { if (!file.GetTypeDefinition(name).IsNil) - return true; + return false; } - return false; + return true; } } diff --git a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs index ad1c177d6..b12b4f064 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 this method may only be called on fresh instances. + /// Used with C# 9 `init;` property setters. + /// + bool IsInitOnly { 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' diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs index 2a11babee..837539720 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs @@ -134,6 +134,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation IEnumerable IMethod.GetReturnTypeAttributes() => EmptyList.Instance; bool IMethod.ReturnTypeIsRefReadOnly => false; bool IMethod.ThisIsRefReadOnly => false; + bool IMethod.IsInitOnly => false; public IReadOnlyList TypeParameters { get; set; } = EmptyList.Instance; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/GetClassTypeReference.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/GetClassTypeReference.cs index 107c2eb76..b8b3f368a 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/GetClassTypeReference.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/GetClassTypeReference.cs @@ -84,9 +84,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation /// public FullTypeName FullTypeName { get { return fullTypeName; } } - IType ResolveInAllAssemblies(ITypeResolveContext context) + internal static IType ResolveInAllAssemblies(ICompilation compilation, in FullTypeName fullTypeName) { - var compilation = context.Compilation; foreach (var asm in compilation.Modules) { IType type = asm.GetTypeDefinition(fullTypeName); if (type != null) @@ -107,7 +106,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation type = context.CurrentModule.GetTypeDefinition(fullTypeName); } if (type == null) { - type = ResolveInAllAssemblies(context); + type = ResolveInAllAssemblies(context.Compilation, in fullTypeName); } } else { // Assembly specified: only look in the specified assembly. @@ -118,7 +117,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation if (asm != null) { type = asm.GetTypeDefinition(fullTypeName); } else { - type = ResolveInAllAssemblies(context); + type = ResolveInAllAssemblies(context.Compilation, in fullTypeName); } } return type ?? new UnknownType(fullTypeName, isReferenceType); diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs index 242b6b193..1e14fa85b 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs @@ -31,11 +31,13 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation { readonly IMethod baseMethod; - public LocalFunctionMethod(IMethod baseMethod, int numberOfCompilerGeneratedParameters, int numberOfCompilerGeneratedTypeParameters) + public LocalFunctionMethod(IMethod baseMethod, string name, bool isStaticLocalFunction, int numberOfCompilerGeneratedParameters, int numberOfCompilerGeneratedTypeParameters) { if (baseMethod == null) throw new ArgumentNullException(nameof(baseMethod)); this.baseMethod = baseMethod; + this.Name = name; + this.IsStaticLocalFunction = isStaticLocalFunction; this.NumberOfCompilerGeneratedParameters = numberOfCompilerGeneratedParameters; this.NumberOfCompilerGeneratedTypeParameters = numberOfCompilerGeneratedTypeParameters; } @@ -46,7 +48,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation return false; return baseMethod.Equals(other.baseMethod, typeNormalization) && NumberOfCompilerGeneratedParameters == other.NumberOfCompilerGeneratedParameters - && NumberOfCompilerGeneratedTypeParameters == other.NumberOfCompilerGeneratedTypeParameters; + && NumberOfCompilerGeneratedTypeParameters == other.NumberOfCompilerGeneratedTypeParameters + && IsStaticLocalFunction == other.IsStaticLocalFunction; } public override bool Equals(object obj) @@ -55,7 +58,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation return false; return baseMethod.Equals(other.baseMethod) && NumberOfCompilerGeneratedParameters == other.NumberOfCompilerGeneratedParameters - && NumberOfCompilerGeneratedTypeParameters == other.NumberOfCompilerGeneratedTypeParameters; + && NumberOfCompilerGeneratedTypeParameters == other.NumberOfCompilerGeneratedTypeParameters + && IsStaticLocalFunction == other.IsStaticLocalFunction; } public override int GetHashCode() @@ -65,14 +69,14 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public override string ToString() { - return string.Format("[LocalFunctionMethod: ReducedFrom={0}, NumberOfGeneratedParameters={1}, NumberOfCompilerGeneratedTypeParameters={2}]", ReducedFrom, NumberOfCompilerGeneratedParameters, NumberOfCompilerGeneratedTypeParameters); + return string.Format("[LocalFunctionMethod: ReducedFrom={0}, Name={1}, NumberOfGeneratedParameters={2}, NumberOfCompilerGeneratedTypeParameters={3}, IsStaticLocalFunction={4}]", ReducedFrom, Name, NumberOfCompilerGeneratedParameters, NumberOfCompilerGeneratedTypeParameters, IsStaticLocalFunction); } internal int NumberOfCompilerGeneratedParameters { get; } internal int NumberOfCompilerGeneratedTypeParameters { get; } - internal bool IsStaticLocalFunction => NumberOfCompilerGeneratedParameters == 0 && (baseMethod.IsStatic || (baseMethod.DeclaringTypeDefinition.IsCompilerGenerated() && !baseMethod.DeclaringType.GetFields(f => !f.IsStatic).Any())); + internal bool IsStaticLocalFunction { get; } public IMember MemberDefinition => this; @@ -88,7 +92,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation { return new LocalFunctionMethod( baseMethod.Specialize(substitution), - NumberOfCompilerGeneratedParameters, NumberOfCompilerGeneratedTypeParameters); + Name, IsStaticLocalFunction, NumberOfCompilerGeneratedParameters, NumberOfCompilerGeneratedTypeParameters); } IMember IMember.Specialize(TypeParameterSubstitution substitution) @@ -96,7 +100,6 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation return Specialize(substitution); } - public IReadOnlyList TypeParameters => baseMethod.TypeParameters; public bool IsExtensionMethod => baseMethod.IsExtensionMethod; public bool IsLocalFunction => true; public bool IsConstructor => baseMethod.IsConstructor; @@ -107,7 +110,24 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public IMember AccessorOwner => baseMethod.AccessorOwner; public MethodSemanticsAttributes AccessorKind => baseMethod.AccessorKind; public IMethod ReducedFrom => baseMethod; - public IReadOnlyList TypeArguments => baseMethod.TypeArguments; + + List typeParameters; + public IReadOnlyList TypeParameters { + get { + if (typeParameters == null) + typeParameters = new List(baseMethod.TypeParameters.Skip(NumberOfCompilerGeneratedTypeParameters)); + return typeParameters; + } + } + + List typeArguments; + public IReadOnlyList TypeArguments { + get { + if (typeArguments == null) + typeArguments = new List(baseMethod.TypeArguments.Skip(NumberOfCompilerGeneratedTypeParameters)); + return typeArguments; + } + } List parameters; public IReadOnlyList Parameters { @@ -127,6 +147,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation IEnumerable IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes(); bool IMethod.ReturnTypeIsRefReadOnly => baseMethod.ReturnTypeIsRefReadOnly; bool IMethod.ThisIsRefReadOnly => baseMethod.ThisIsRefReadOnly; + bool IMethod.IsInitOnly => baseMethod.IsInitOnly; /// /// We consider local functions as always static, because they do not have a "this parameter". /// Even local functions in instance methods capture this. @@ -137,8 +158,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public Accessibility Accessibility => baseMethod.Accessibility; - public string FullName => baseMethod.FullName; - public string Name => baseMethod.Name; + public string FullName => Name; + public string Name { get; set; } public string ReflectionName => baseMethod.ReflectionName; public string Namespace => baseMethod.Namespace; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs index ab02b8252..a015d7d3d 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs @@ -50,6 +50,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation IType returnType; byte returnTypeIsRefReadonly = ThreeState.Unknown; byte thisIsRefReadonly = ThreeState.Unknown; + bool isInitOnly; internal MetadataMethod(MetadataModule module, MethodDefinitionHandle handle) { @@ -150,6 +151,15 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation } } + public bool IsInitOnly { + get { + var returnType = LazyInit.VolatileRead(ref this.returnType); + if (returnType == null) + DecodeSignature(); + return this.isInitOnly; + } + } + internal Nullability NullableContext { get { var methodDef = module.metadata.GetMethodDefinition(handle); @@ -163,19 +173,23 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation var genericContext = new GenericContext(DeclaringType.TypeParameters, this.TypeParameters); IType returnType; IParameter[] parameters; + ModifiedType mod; try { var nullableContext = methodDef.GetCustomAttributes().GetNullableContext(module.metadata) ?? DeclaringTypeDefinition.NullableContext; var signature = methodDef.DecodeSignature(module.TypeProvider, genericContext); - (returnType, parameters) = DecodeSignature(module, this, signature, methodDef.GetParameters(), nullableContext, module.OptionsForEntity(this)); + (returnType, parameters, mod) = DecodeSignature(module, this, signature, methodDef.GetParameters(), nullableContext, module.OptionsForEntity(this)); } catch (BadImageFormatException) { returnType = SpecialType.UnknownType; parameters = Empty.Array; + mod = null; } + this.isInitOnly = mod is { Modifier: { Name: "IsExternalInit", Namespace: "System.Runtime.CompilerServices" } }; LazyInit.GetOrSet(ref this.returnType, returnType); LazyInit.GetOrSet(ref this.parameters, parameters); } - internal static (IType, IParameter[]) DecodeSignature(MetadataModule module, IParameterizedMember owner, + internal static (IType returnType, IParameter[] parameters, ModifiedType returnTypeModifier) DecodeSignature( + MetadataModule module, IParameterizedMember owner, MethodSignature signature, ParameterHandleCollection? parameterHandles, Nullability nullableContext, TypeSystemOptions typeSystemOptions, CustomAttributeHandleCollection? returnTypeAttributes = null) @@ -231,7 +245,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation Debug.Assert(i == parameters.Length); var returnType = ApplyAttributeTypeVisitor.ApplyAttributesToType(signature.ReturnType, module.Compilation, returnTypeAttributes, metadata, typeSystemOptions, nullableContext); - return (returnType, parameters); + return (returnType, parameters, signature.ReturnType as ModifiedType); } #endregion @@ -453,7 +467,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation #endregion public Accessibility Accessibility => GetAccessibility(attributes); - + internal static Accessibility GetAccessibility(MethodAttributes attr) { switch (attr & MethodAttributes.MemberAccessMask) { diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs index 83596cb51..2f33e3d17 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs @@ -153,7 +153,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation // Roslyn uses the same workaround (see the NullableTypeDecoder.TransformType // call in PEPropertySymbol). var typeOptions = module.OptionsForEntity(declTypeDef); - (returnType, parameters) = MetadataMethod.DecodeSignature(module, this, signature, + (returnType, parameters, _) = MetadataMethod.DecodeSignature( + module, this, signature, parameterHandles, nullableContext, typeOptions, returnTypeAttributes: propertyDef.GetCustomAttributes()); } catch (BadImageFormatException) { diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MinimalCorlib.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MinimalCorlib.cs index fbab0db4b..5874e8c29 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MinimalCorlib.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MinimalCorlib.cs @@ -31,22 +31,25 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation /// public sealed class MinimalCorlib : IModule { - public static readonly IModuleReference Instance = new CorlibModuleReference(); + /// + /// Minimal corlib instance containing all known types. + /// + public static readonly IModuleReference Instance = new CorlibModuleReference(KnownTypeReference.AllKnownTypes); + + public static IModuleReference CreateWithTypes(IEnumerable types) + { + return new CorlibModuleReference(types); + } public ICompilation Compilation { get; } CorlibTypeDefinition[] typeDefinitions; readonly CorlibNamespace rootNamespace; - private MinimalCorlib(ICompilation compilation) + private MinimalCorlib(ICompilation compilation, IEnumerable types) { this.Compilation = compilation; - this.typeDefinitions = new CorlibTypeDefinition[KnownTypeReference.KnownTypeCodeCount]; + this.typeDefinitions = types.Select(ktr => new CorlibTypeDefinition(this, ktr.KnownTypeCode)).ToArray(); this.rootNamespace = new CorlibNamespace(this, null, string.Empty, string.Empty); - for (int i = 0; i < KnownTypeReference.KnownTypeCodeCount; i++) { - if (KnownTypeReference.Get((KnownTypeCode)i) != null) { - typeDefinitions[i] = new CorlibTypeDefinition(this, (KnownTypeCode)i); - } - } } bool IModule.IsMainModule => Compilation.MainModule == this; @@ -65,7 +68,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public ITypeDefinition GetTypeDefinition(TopLevelTypeName topLevelTypeName) { foreach (var typeDef in typeDefinitions) { - if (typeDef != null && typeDef.FullTypeName == topLevelTypeName) + if (typeDef.FullTypeName == topLevelTypeName) return typeDef; } return null; @@ -81,9 +84,16 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation sealed class CorlibModuleReference : IModuleReference { + readonly IEnumerable types; + + public CorlibModuleReference(IEnumerable types) + { + this.types = types; + } + IModule IModuleReference.Resolve(ITypeResolveContext context) { - return new MinimalCorlib(context.Compilation); + return new MinimalCorlib(context.Compilation, types); } } @@ -196,7 +206,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation get { var baseType = KnownTypeReference.Get(typeCode).baseType; if (baseType != KnownTypeCode.None) - return new[] { corlib.typeDefinitions[(int)baseType] }; + return new[] { corlib.Compilation.FindType(baseType) }; else return EmptyList.Instance; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs index 58d1e2f56..7ec70de21 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs @@ -98,6 +98,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public bool ReturnTypeIsRefReadOnly => methodDefinition.ReturnTypeIsRefReadOnly; bool IMethod.ThisIsRefReadOnly => methodDefinition.ThisIsRefReadOnly; + bool IMethod.IsInitOnly => methodDefinition.IsInitOnly; public IReadOnlyList TypeParameters { get { diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs index 9d79e1b79..0acaf9d28 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs @@ -62,6 +62,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation bool IMethod.ReturnTypeIsRefReadOnly => underlyingMethod.ReturnTypeIsRefReadOnly; bool IMethod.ThisIsRefReadOnly => underlyingMethod.ThisIsRefReadOnly; + bool IMethod.IsInitOnly => underlyingMethod.IsInitOnly; IReadOnlyList IMethod.TypeParameters => EmptyList.Instance; IReadOnlyList IMethod.TypeArguments => EmptyList.Instance; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/UnknownType.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/UnknownType.cs index 018276115..84d483df2 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/UnknownType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/UnknownType.cs @@ -86,7 +86,9 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public override string ReflectionName { get { return namespaceKnown ? fullTypeName.ReflectionName : "?"; } } - + + public FullTypeName FullTypeName => fullTypeName; + public override int TypeParameterCount => fullTypeName.TypeParameterCount; public override IReadOnlyList TypeParameters => DummyTypeParameter.GetClassTypeParameterList(TypeParameterCount); public override IReadOnlyList TypeArguments => TypeParameters; diff --git a/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs b/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs index a6981f2b2..d3a121ffc 100644 --- a/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs +++ b/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; namespace ICSharpCode.Decompiler.TypeSystem { @@ -235,6 +236,17 @@ namespace ICSharpCode.Decompiler.TypeSystem return knownTypeReferences[(int)typeCode]; } + public static IEnumerable AllKnownTypes { + get { + for (int i = 0; i < KnownTypeCodeCount; i++) { + var ktr = Get((KnownTypeCode)i); + if (ktr == null) + continue; + yield return ktr; + } + } + } + readonly KnownTypeCode knownTypeCode; readonly string namespaceName; readonly string name; diff --git a/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs b/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs index 5d8d4bd3a..10f6a17fd 100644 --- a/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs +++ b/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs @@ -48,6 +48,7 @@ namespace ICSharpCode.Decompiler.TypeSystem readonly MetadataMethod[] methodDefs; readonly MetadataProperty[] propertyDefs; readonly MetadataEvent[] eventDefs; + readonly IModule[] referencedAssemblies; internal MetadataModule(ICompilation compilation, Metadata.PEFile peFile, TypeSystemOptions options) { @@ -79,6 +80,7 @@ namespace ICSharpCode.Decompiler.TypeSystem this.methodDefs = new MetadataMethod[metadata.MethodDefinitions.Count + 1]; this.propertyDefs = new MetadataProperty[metadata.PropertyDefinitions.Count + 1]; this.eventDefs = new MetadataEvent[metadata.EventDefinitions.Count + 1]; + this.referencedAssemblies = new IModule[metadata.AssemblyReferences.Count + 1]; } } @@ -270,6 +272,64 @@ namespace ICSharpCode.Decompiler.TypeSystem } #endregion + #region Resolve Module + + public IModule ResolveModule(AssemblyReferenceHandle handle) + { + if (handle.IsNil) + return null; + + if (referencedAssemblies == null) + return ResolveModuleUncached(handle); + int row = MetadataTokens.GetRowNumber(handle); + Debug.Assert(row != 0); + if (row >= referencedAssemblies.Length) + HandleOutOfRange(handle); + var module = LazyInit.VolatileRead(ref referencedAssemblies[row]); + if (module != null) + return module; + module = ResolveModuleUncached(handle); + return LazyInit.GetOrSet(ref referencedAssemblies[row], module); + } + + IModule ResolveModuleUncached(AssemblyReferenceHandle handle) + { + var asmRef = new Metadata.AssemblyReference(metadata, handle); + return Compilation.FindModuleByReference(asmRef); + } + + public IModule ResolveModule(ModuleReferenceHandle handle) + { + if (handle.IsNil) + return null; + var modRef = metadata.GetModuleReference(handle); + string name = metadata.GetString(modRef.Name); + foreach (var mod in Compilation.Modules) { + if (mod.Name == name) { + return mod; + } + } + return null; + } + + public IModule GetDeclaringModule(TypeReferenceHandle handle) + { + if (handle.IsNil) + return null; + var tr = metadata.GetTypeReference(handle); + switch (tr.ResolutionScope.Kind) { + case HandleKind.TypeReference: + return GetDeclaringModule((TypeReferenceHandle)tr.ResolutionScope); + case HandleKind.AssemblyReference: + return ResolveModule((AssemblyReferenceHandle)tr.ResolutionScope); + case HandleKind.ModuleReference: + return ResolveModule((ModuleReferenceHandle)tr.ResolutionScope); + default: + return this; + } + } + #endregion + #region Resolve Type public IType ResolveType(EntityHandle typeRefDefSpec, GenericContext context, CustomAttributeHandleCollection? typeAttributes = null, Nullability nullableContext = Nullability.Oblivious) { diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs b/ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs index 0ab8f4faa..5c2924d8c 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeProvider.cs @@ -131,9 +131,15 @@ namespace ICSharpCode.Decompiler.TypeSystem public IType GetTypeFromReference(SRM.MetadataReader reader, SRM.TypeReferenceHandle handle, byte rawTypeKind) { - bool? isReferenceType = IsReferenceType(reader, handle, rawTypeKind); - var gctr = new GetClassTypeReference(handle.GetFullTypeName(reader), handle.GetDeclaringModule(reader), isReferenceType); - return gctr.Resolve(module != null ? new SimpleTypeResolveContext(module) : new SimpleTypeResolveContext(compilation)); + IModule resolvedModule = module.GetDeclaringModule(handle); + var fullTypeName = handle.GetFullTypeName(reader); + IType type; + if (resolvedModule != null) { + type = resolvedModule.GetTypeDefinition(fullTypeName); + } else { + type = GetClassTypeReference.ResolveInAllAssemblies(compilation, in fullTypeName); + } + return type ?? new UnknownType(fullTypeName, IsReferenceType(reader, handle, rawTypeKind)); } public IType GetTypeFromSerializedName(string name) diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs index 64684e8f6..26da4cdd9 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs @@ -50,7 +50,7 @@ namespace ICSharpCode.Decompiler.TypeSystem collector.CollectBaseTypes(type); return collector; } - + /// /// Gets all non-interface base types. /// @@ -69,7 +69,7 @@ namespace ICSharpCode.Decompiler.TypeSystem return collector; } #endregion - + #region GetAllBaseTypeDefinitions /// /// Gets all base type definitions. @@ -82,10 +82,10 @@ namespace ICSharpCode.Decompiler.TypeSystem { if (type == null) throw new ArgumentNullException(nameof(type)); - + return type.GetAllBaseTypes().Select(t => t.GetDefinition()).Where(d => d != null).Distinct(); } - + /// /// Gets whether this type definition is derived from the base type definition. /// @@ -100,7 +100,7 @@ namespace ICSharpCode.Decompiler.TypeSystem } return type.GetAllBaseTypeDefinitions().Contains(baseType); } - + /// /// Gets whether this type definition is derived from a given known type. /// @@ -138,7 +138,7 @@ namespace ICSharpCode.Decompiler.TypeSystem internal bool isOpen; internal IEntity typeParameterOwner; int typeParameterOwnerNestingLevel; - + public override IType VisitTypeParameter(ITypeParameter type) { isOpen = true; @@ -151,7 +151,7 @@ namespace ICSharpCode.Decompiler.TypeSystem } return base.VisitTypeParameter(type); } - + static int GetNestingLevel(IEntity entity) { int level = 0; @@ -162,7 +162,7 @@ namespace ICSharpCode.Decompiler.TypeSystem return level; } } - + /// /// Gets whether the type is an open type (contains type parameters). /// @@ -184,7 +184,7 @@ namespace ICSharpCode.Decompiler.TypeSystem type.AcceptVisitor(v); return v.isOpen; } - + /// /// Gets the entity that owns the type parameters occurring in the specified type. /// If both class and method type parameters are present, the method is returned. @@ -199,7 +199,7 @@ namespace ICSharpCode.Decompiler.TypeSystem type.AcceptVisitor(v); return v.typeParameterOwner; } - + /// /// Gets whether the type is unbound (is a generic type, but no type arguments were provided). /// @@ -214,7 +214,7 @@ namespace ICSharpCode.Decompiler.TypeSystem throw new ArgumentNullException(nameof(type)); return (type is ITypeDefinition || type is UnknownType) && type.TypeParameterCount > 0; } - + /// /// Gets whether the type is the specified known type. /// For generic known types, this returns true for any parameterization of the type (and also for the definition itself). @@ -273,7 +273,7 @@ namespace ICSharpCode.Decompiler.TypeSystem return null; } #endregion - + public static IType SkipModifiers(this IType ty) { while (ty is ModifiedType mt) { @@ -292,7 +292,7 @@ namespace ICSharpCode.Decompiler.TypeSystem /// Gets all type definitions in the compilation. /// This may include types from referenced assemblies that are not accessible in the main assembly. /// - public static IEnumerable GetAllTypeDefinitions (this ICompilation compilation) + public static IEnumerable GetAllTypeDefinitions(this ICompilation compilation) { return compilation.Modules.SelectMany(a => a.TypeDefinitions); } @@ -301,12 +301,12 @@ namespace ICSharpCode.Decompiler.TypeSystem /// Gets all top level type definitions in the compilation. /// This may include types from referenced assemblies that are not accessible in the main assembly. /// - public static IEnumerable GetTopLevelTypeDefinitions (this ICompilation compilation) + public static IEnumerable GetTopLevelTypeDefinitions(this ICompilation compilation) { return compilation.Modules.SelectMany(a => a.TopLevelTypeDefinitions); } #endregion - + #region Resolve on collections public static IReadOnlyList Resolve(this IList typeReferences, ITypeResolveContext context) { @@ -317,11 +317,11 @@ namespace ICSharpCode.Decompiler.TypeSystem else return new ProjectedList(context, typeReferences, (c, t) => t.Resolve(c)); } - + // There is intentionally no Resolve() overload for IList: the resulting IList would // contains nulls when there are resolve errors. #endregion - + #region IAssembly.GetTypeDefinition() /// /// Retrieves the specified type in this compilation. @@ -345,7 +345,7 @@ namespace ICSharpCode.Decompiler.TypeSystem } return new UnknownType(fullTypeName); } - + /// /// Gets the type definition for the specified unresolved type. /// Returns null if the unresolved type does not belong to this assembly. @@ -368,7 +368,7 @@ namespace ICSharpCode.Decompiler.TypeSystem } return typeDef; } - + static ITypeDefinition FindNestedType(ITypeDefinition typeDef, string name, int typeParameterCount) { foreach (var nestedType in typeDef.NestedTypes) { @@ -378,7 +378,7 @@ namespace ICSharpCode.Decompiler.TypeSystem return null; } #endregion - + #region IEntity.GetAttribute /// /// Gets whether the entity has an attribute of the specified attribute type (or derived attribute types). @@ -390,7 +390,7 @@ namespace ICSharpCode.Decompiler.TypeSystem /// (if the given in an override) /// should be returned. /// - public static bool HasAttribute(this IEntity entity, KnownAttribute attributeType, bool inherit=false) + public static bool HasAttribute(this IEntity entity, KnownAttribute attributeType, bool inherit = false) { return GetAttribute(entity, attributeType, inherit) != null; } @@ -410,7 +410,7 @@ namespace ICSharpCode.Decompiler.TypeSystem /// If inherit is true, an from the entity itself will be returned if possible; /// and the base entity will only be searched if none exists. /// - public static IAttribute GetAttribute(this IEntity entity, KnownAttribute attributeType, bool inherit=false) + public static IAttribute GetAttribute(this IEntity entity, KnownAttribute attributeType, bool inherit = false) { return GetAttributes(entity, inherit).FirstOrDefault(a => a.AttributeType.IsKnownType(attributeType)); } @@ -478,11 +478,11 @@ namespace ICSharpCode.Decompiler.TypeSystem public static ITypeDefinition GetTypeDefinition(this IModule module, string namespaceName, string name, int typeParameterCount = 0) { if (module == null) - throw new ArgumentNullException ("assembly"); - return module.GetTypeDefinition (new TopLevelTypeName (namespaceName, name, typeParameterCount)); + throw new ArgumentNullException("assembly"); + return module.GetTypeDefinition(new TopLevelTypeName(namespaceName, name, typeParameterCount)); } #endregion - + #region ResolveResult public static ISymbol GetSymbol(this ResolveResult rr) { @@ -495,7 +495,7 @@ namespace ICSharpCode.Decompiler.TypeSystem } else if (rr is ConversionResolveResult) { return ((ConversionResolveResult)rr).Input.GetSymbol(); } - + return null; } #endregion @@ -560,7 +560,12 @@ namespace ICSharpCode.Decompiler.TypeSystem public static IModule FindModuleByReference(this ICompilation compilation, IAssemblyReference assemblyName) { foreach (var module in compilation.Modules) { - if (module.FullAssemblyName == assemblyName.FullName) { + if (string.Equals(module.FullAssemblyName, assemblyName.FullName, StringComparison.OrdinalIgnoreCase)) { + return module; + } + } + foreach (var module in compilation.Modules) { + if (string.Equals(module.Name, assemblyName.Name, StringComparison.OrdinalIgnoreCase)) { return module; } } diff --git a/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs b/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs index ecc0cbf22..6b53caa88 100644 --- a/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs @@ -115,6 +115,7 @@ namespace ICSharpCode.Decompiler.TypeSystem IEnumerable IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes(); bool IMethod.ReturnTypeIsRefReadOnly => baseMethod.ReturnTypeIsRefReadOnly; bool IMethod.ThisIsRefReadOnly => baseMethod.ThisIsRefReadOnly; + bool IMethod.IsInitOnly => baseMethod.IsInitOnly; public IReadOnlyList TypeParameters { get { return baseMethod.TypeParameters; } diff --git a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs index 4c01285df..df585d159 100644 --- a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs +++ b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs @@ -12,10 +12,12 @@ namespace ICSharpCode.Decompiler.Util value = pair.Value; } +#if !NETCORE public static IEnumerable<(A, B)> Zip(this IEnumerable input1, IEnumerable input2) { return input1.Zip(input2, (a, b) => (a, b)); } +#endif public static IEnumerable<(A, B)> ZipLongest(this IEnumerable input1, IEnumerable input2) { @@ -51,10 +53,12 @@ namespace ICSharpCode.Decompiler.Util } } +#if !NETCORE public static HashSet ToHashSet(this IEnumerable input) { return new HashSet(input); } +#endif public static IEnumerable SkipLast(this IReadOnlyCollection input, int count) { @@ -323,7 +327,7 @@ namespace ICSharpCode.Decompiler.Util return first; } - #region Aliases/shortcuts for Enumerable extension methods +#region Aliases/shortcuts for Enumerable extension methods public static bool Any(this ICollection list) => list.Count > 0; public static bool Any(this T[] array, Predicate match) => Array.Exists(array, match); public static bool Any(this List list, Predicate match) => list.Exists(match); @@ -335,6 +339,6 @@ namespace ICSharpCode.Decompiler.Util public static T FirstOrDefault(this List list, Predicate predicate) => list.Find(predicate); public static T Last(this IList list) => list[list.Count - 1]; - #endregion +#endregion } } diff --git a/ILSpy.AddIn/AssemblyFileFinder.cs b/ILSpy.AddIn/AssemblyFileFinder.cs index 66846a6cc..cc6676782 100644 --- a/ILSpy.AddIn/AssemblyFileFinder.cs +++ b/ILSpy.AddIn/AssemblyFileFinder.cs @@ -1,32 +1,29 @@ using System; -using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; using System.Text.RegularExpressions; -using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Util; -using Mono.Cecil; namespace ICSharpCode.ILSpy.AddIn { public class AssemblyFileFinder { - public static string FindAssemblyFile(AssemblyDefinition assemblyDefinition, string assemblyFile) + public static string FindAssemblyFile(Mono.Cecil.AssemblyDefinition assemblyDefinition, string assemblyFile) { - var assemblyResolver = new UniversalAssemblyResolver(assemblyFile, false, - DetectTargetFrameworkId(assemblyDefinition, assemblyFile)); + string tfi = DetectTargetFrameworkId(assemblyDefinition, assemblyFile); + UniversalAssemblyResolver assemblyResolver; if (IsReferenceAssembly(assemblyDefinition, assemblyFile)) { - assemblyResolver.RemoveSearchDirectory(Path.GetDirectoryName(assemblyFile)); + assemblyResolver = new UniversalAssemblyResolver(null, throwOnError: false, tfi); + } else { + assemblyResolver = new UniversalAssemblyResolver(assemblyFile, throwOnError: false, tfi); } - return assemblyResolver.FindAssemblyFile( - ICSharpCode.Decompiler.Metadata.AssemblyNameReference.Parse(assemblyDefinition.Name.FullName)); + + return assemblyResolver.FindAssemblyFile(AssemblyNameReference.Parse(assemblyDefinition.Name.FullName)); } static readonly string RefPathPattern = @"NuGetFallbackFolder[/\\][^/\\]+[/\\][^/\\]+[/\\]ref[/\\]"; - public static bool IsReferenceAssembly(AssemblyDefinition assemblyDef, string assemblyFile) + public static bool IsReferenceAssembly(Mono.Cecil.AssemblyDefinition assemblyDef, string assemblyFile) { if (assemblyDef.CustomAttributes.Any(ca => ca.AttributeType.FullName == "System.Runtime.CompilerServices.ReferenceAssemblyAttribute")) return true; @@ -40,7 +37,7 @@ namespace ICSharpCode.ILSpy.AddIn @"(Reference Assemblies[/\\]Microsoft[/\\]Framework[/\\](?<1>.NETFramework)[/\\]v(?<2>[^/\\]+)[/\\])" + @"|((NuGetFallbackFolder|packs)[/\\](?<1>[^/\\]+)\\(?<2>[^/\\]+)([/\\].*)?[/\\]ref[/\\])"; - public static string DetectTargetFrameworkId(AssemblyDefinition assembly, string assemblyPath = null) + public static string DetectTargetFrameworkId(Mono.Cecil.AssemblyDefinition assembly, string assemblyPath = null) { if (assembly == null) throw new ArgumentNullException(nameof(assembly)); diff --git a/ILSpy.AddIn/Commands/OpenCodeItemCommand.cs b/ILSpy.AddIn/Commands/OpenCodeItemCommand.cs index c98481363..07cdd9989 100644 --- a/ILSpy.AddIn/Commands/OpenCodeItemCommand.cs +++ b/ILSpy.AddIn/Commands/OpenCodeItemCommand.cs @@ -38,10 +38,7 @@ namespace ICSharpCode.ILSpy.AddIn.Commands var document = owner.DTE.ActiveDocument; menuItem.Visible = - (document != null) && - (document.ProjectItem != null) && - (document.ProjectItem.ContainingProject != null) && - (document.ProjectItem.ContainingProject.ConfigurationManager != null) && + (document?.ProjectItem?.ContainingProject?.ConfigurationManager != null) && !string.IsNullOrEmpty(document.ProjectItem.ContainingProject.FileName); } } @@ -51,7 +48,6 @@ namespace ICSharpCode.ILSpy.AddIn.Commands 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(); if (id == null) return null; @@ -59,14 +55,6 @@ namespace ICSharpCode.ILSpy.AddIn.Commands return owner.Workspace.CurrentSolution.GetDocument(id); } - 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(); diff --git a/ILSpy.AddIn/Commands/OpenILSpyCommand.cs b/ILSpy.AddIn/Commands/OpenILSpyCommand.cs index 1d569193a..13ef992b0 100644 --- a/ILSpy.AddIn/Commands/OpenILSpyCommand.cs +++ b/ILSpy.AddIn/Commands/OpenILSpyCommand.cs @@ -50,7 +50,7 @@ namespace ICSharpCode.ILSpy.AddIn.Commands protected string GetILSpyPath() { var basePath = Path.GetDirectoryName(typeof(ILSpyAddInPackage).Assembly.Location); - return Path.Combine(basePath, "ILSpy.exe"); + return Path.Combine(basePath, "ILSpy", "ILSpy.exe"); } protected void OpenAssembliesInILSpy(ILSpyParameters parameters) @@ -80,8 +80,7 @@ namespace ICSharpCode.ILSpy.AddIn.Commands using (var assemblyDef = AssemblyDefinition.ReadAssembly(reference.Display)) { string assemblyName = assemblyDef.Name.Name; string resolvedAssemblyFile = AssemblyFileFinder.FindAssemblyFile(assemblyDef, reference.Display); - dict.Add(assemblyName, - new DetectedReference(assemblyName, resolvedAssemblyFile, false)); + dict.Add(assemblyName, new DetectedReference(assemblyName, resolvedAssemblyFile, false)); } } foreach (var projectReference in parentProject.ProjectReferences) { diff --git a/ILSpy.AddIn/ILSpy.AddIn.csproj b/ILSpy.AddIn/ILSpy.AddIn.csproj index 1cf8c6a09..a5471e387 100644 --- a/ILSpy.AddIn/ILSpy.AddIn.csproj +++ b/ILSpy.AddIn/ILSpy.AddIn.csproj @@ -12,7 +12,6 @@ IC#Code ILSpy - en-US 1.7.1.0 1.7.1.0 @@ -51,10 +50,15 @@ - + + + false + false + + @@ -85,10 +89,20 @@ Resources.Designer.cs Designer + + true + VSPackage.en-US.resources + VSPackage.resx + true VSPackage + + true + VSPackage.zh-Hans.resources + VSPackage.resx + - - - - Designer - + + true + \ILSpy + - + + true + \ILSpy\zh-Hans\ + - + + + Designer + + + + + Designer + + + true + Always + + Menus.ctmenu Designer + ILSpyAddIn.vsct + + Menus.ctmenu + Designer + ILSpyAddIn.vsct + + + + diff --git a/ILSpy.AddIn/ILSpyAddIn.en-US.vsct b/ILSpy.AddIn/ILSpyAddIn.en-US.vsct new file mode 100644 index 000000000..a6a3c6947 --- /dev/null +++ b/ILSpy.AddIn/ILSpyAddIn.en-US.vsct @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ILSpy.AddIn/ILSpyAddIn.vsct b/ILSpy.AddIn/ILSpyAddIn.vsct index d747baf57..3d992eae7 100644 --- a/ILSpy.AddIn/ILSpyAddIn.vsct +++ b/ILSpy.AddIn/ILSpyAddIn.vsct @@ -20,9 +20,6 @@ - - - @@ -34,7 +31,7 @@ group; your package should define its own command set in order to avoid collisions with command ids defined by other packages. --> - + - - - - - - - - - - - - - - + @@ -136,7 +80,7 @@ - + diff --git a/ILSpy.AddIn/ILSpyAddIn.zh-Hans.vsct b/ILSpy.AddIn/ILSpyAddIn.zh-Hans.vsct new file mode 100644 index 000000000..d50bfb062 --- /dev/null +++ b/ILSpy.AddIn/ILSpyAddIn.zh-Hans.vsct @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ILSpy.AddIn/ILSpyAddInPackage.cs b/ILSpy.AddIn/ILSpyAddInPackage.cs index 533a78bdd..950cdc0d5 100644 --- a/ILSpy.AddIn/ILSpyAddInPackage.cs +++ b/ILSpy.AddIn/ILSpyAddInPackage.cs @@ -37,6 +37,7 @@ namespace ICSharpCode.ILSpy.AddIn [ProvideMenuResource("Menus.ctmenu", 1)] [Guid(GuidList.guidILSpyAddInPkgString)] [ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExistsAndFullyLoaded_string, PackageAutoLoadFlags.BackgroundLoad)] + [ProvideBindingPath] public sealed class ILSpyAddInPackage : AsyncPackage { /// diff --git a/ILSpy.AddIn/ILSpyInstance.cs b/ILSpy.AddIn/ILSpyInstance.cs index adf922328..182ee3e9e 100644 --- a/ILSpy.AddIn/ILSpyInstance.cs +++ b/ILSpy.AddIn/ILSpyInstance.cs @@ -12,11 +12,11 @@ namespace ICSharpCode.ILSpy.AddIn { public ILSpyParameters(IEnumerable assemblyFileNames, params string[] arguments) { - this.AssemblyFileNames = assemblyFileNames; + this.AssemblyFileNames = assemblyFileNames.ToArray(); this.Arguments = arguments; } - public IEnumerable AssemblyFileNames { get; private set; } + public string[] AssemblyFileNames { get; private set; } public string[] Arguments { get; private set; } } @@ -32,16 +32,16 @@ namespace ICSharpCode.ILSpy.AddIn static string GetILSpyPath() { var basePath = Path.GetDirectoryName(typeof(ILSpyAddInPackage).Assembly.Location); - return Path.Combine(basePath, "ILSpy.exe"); + return Path.Combine(basePath, "ILSpy", "ILSpy.exe"); } public void Start() { var commandLineArguments = parameters?.AssemblyFileNames?.Concat(parameters.Arguments); - + string ilSpyExe = GetILSpyPath(); var process = new Process() { StartInfo = new ProcessStartInfo() { - FileName = GetILSpyPath(), + FileName = ilSpyExe, UseShellExecute = false, Arguments = "/navigateTo:none" } @@ -50,13 +50,14 @@ namespace ICSharpCode.ILSpy.AddIn if ((commandLineArguments != null) && commandLineArguments.Any()) { // Only need a message to started process if there are any parameters to pass - SendMessage(process, "ILSpy:\r\n" + string.Join(Environment.NewLine, commandLineArguments), true); + SendMessage(ilSpyExe, "ILSpy:\r\n" + string.Join(Environment.NewLine, commandLineArguments), true); } } [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD110:Observe result of async calls", Justification = "")] - void SendMessage(Process ilspyProcess, string message, bool activate) + void SendMessage(string ilSpyExe, string message, bool activate) { + string expectedProcessName = Path.GetFileNameWithoutExtension(ilSpyExe); // We wait asynchronously until target window can be found and try to find it multiple times Task.Run(async () => { bool success = false; @@ -66,14 +67,17 @@ namespace ICSharpCode.ILSpy.AddIn (hWnd, lParam) => { string windowTitle = NativeMethods.GetWindowText(hWnd, 100); if (windowTitle.StartsWith("ILSpy", StringComparison.Ordinal)) { - Debug.WriteLine("Found {0:x4}: {1}", hWnd, windowTitle); - IntPtr result = Send(hWnd, message); - Debug.WriteLine("WM_COPYDATA result: {0:x8}", result); - if (result == (IntPtr)1) { - if (activate) - NativeMethods.SetForegroundWindow(hWnd); - success = true; - return false; // stop enumeration + string processName = NativeMethods.GetProcessNameFromWindow(hWnd); + Debug.WriteLine("Found {0:x4}: '{1}' in '{2}'", hWnd, windowTitle, processName); + if (string.Equals(processName, expectedProcessName, StringComparison.OrdinalIgnoreCase)) { + IntPtr result = Send(hWnd, message); + Debug.WriteLine("WM_COPYDATA result: {0:x8}", result); + if (result == (IntPtr)1) { + if (activate) + NativeMethods.SetForegroundWindow(hWnd); + success = true; + return false; // stop enumeration + } } } return true; // continue enumeration diff --git a/ILSpy.AddIn/Properties/AssemblyInfo.cs b/ILSpy.AddIn/Properties/AssemblyInfo.cs index db36e8d59..64f95fad1 100644 --- a/ILSpy.AddIn/Properties/AssemblyInfo.cs +++ b/ILSpy.AddIn/Properties/AssemblyInfo.cs @@ -1,5 +1,6 @@ using System; using System.Reflection; +using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -11,6 +12,6 @@ using System.Runtime.InteropServices; [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: CLSCompliant(false)] - +[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] [assembly: InternalsVisibleTo("ILSpy.AddIn_IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100653c4a319be4f524972c3c5bba5fd243330f8e900287d9022d7821a63fd0086fd3801e3683dbe9897f2ecc44727023e9b40adcf180730af70c81c54476b3e5ba8b0f07f5132b2c3cc54347a2c1a9d64ebaaaf3cbffc1a18c427981e2a51d53d5ab02536b7550e732f795121c38a0abfdb38596353525d034baf9e6f1fd8ac4ac")] [assembly: InternalsVisibleTo("ILSpy.AddIn_UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100653c4a319be4f524972c3c5bba5fd243330f8e900287d9022d7821a63fd0086fd3801e3683dbe9897f2ecc44727023e9b40adcf180730af70c81c54476b3e5ba8b0f07f5132b2c3cc54347a2c1a9d64ebaaaf3cbffc1a18c427981e2a51d53d5ab02536b7550e732f795121c38a0abfdb38596353525d034baf9e6f1fd8ac4ac")] diff --git a/ILSpy.AddIn/Utils.cs b/ILSpy.AddIn/Utils.cs index 74af2d797..65d3dc4b0 100644 --- a/ILSpy.AddIn/Utils.cs +++ b/ILSpy.AddIn/Utils.cs @@ -30,101 +30,6 @@ namespace ICSharpCode.ILSpy.AddIn static class Utils { - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] - [DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - static extern unsafe char** CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs); - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] - [DllImport("kernel32.dll")] - static extern IntPtr LocalFree(IntPtr hMem); - - #region CommandLine <-> Argument Array - /// - /// Decodes a command line into an array of arguments according to the CommandLineToArgvW rules. - /// - /// - /// Command line parsing rules: - /// - 2n backslashes followed by a quotation mark produce n backslashes, and the quotation mark is considered to be the end of the argument. - /// - (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark. - /// - n backslashes not followed by a quotation mark simply produce n backslashes. - /// - public static unsafe string[] CommandLineToArgumentArray(string commandLine) - { - if (string.IsNullOrEmpty(commandLine)) - return new string[0]; - int numberOfArgs; - char** arr = CommandLineToArgvW(commandLine, out numberOfArgs); - if (arr == null) - throw new Win32Exception(); - try { - string[] result = new string[numberOfArgs]; - for (int i = 0; i < numberOfArgs; i++) { - result[i] = new string(arr[i]); - } - return result; - } finally { - // Free memory obtained by CommandLineToArgW. - LocalFree(new IntPtr(arr)); - } - } - - static readonly char[] charsNeedingQuoting = { ' ', '\t', '\n', '\v', '"' }; - - /// - /// Escapes a set of arguments according to the CommandLineToArgvW rules. - /// - /// - /// Command line parsing rules: - /// - 2n backslashes followed by a quotation mark produce n backslashes, and the quotation mark is considered to be the end of the argument. - /// - (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark. - /// - n backslashes not followed by a quotation mark simply produce n backslashes. - /// - public static string ArgumentArrayToCommandLine(params string[] arguments) - { - if (arguments == null) - return null; - StringBuilder b = new StringBuilder(); - for (int i = 0; i < arguments.Length; i++) { - if (i > 0) - b.Append(' '); - AppendArgument(b, arguments[i]); - } - return b.ToString(); - } - - static void AppendArgument(StringBuilder b, string arg) - { - if (arg == null) { - return; - } - - if (arg.Length > 0 && arg.IndexOfAny(charsNeedingQuoting) < 0) { - b.Append(arg); - } else { - b.Append('"'); - for (int j = 0; ; j++) { - int backslashCount = 0; - while (j < arg.Length && arg[j] == '\\') { - backslashCount++; - j++; - } - if (j == arg.Length) { - b.Append('\\', backslashCount * 2); - break; - } else if (arg[j] == '"') { - b.Append('\\', backslashCount * 2 + 1); - b.Append('"'); - } else { - b.Append('\\', backslashCount); - b.Append(arg[j]); - } - } - b.Append('"'); - } - } - #endregion - public static byte[] HexStringToBytes(string hex) { if (hex == null) diff --git a/ILSpy.AddIn/VSPackage.en-US.resx b/ILSpy.AddIn/VSPackage.en-US.resx new file mode 100644 index 000000000..73c158134 --- /dev/null +++ b/ILSpy.AddIn/VSPackage.en-US.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ILSpy.AddIn + + + Integration of the ILSpy Decompiler into Visual Studio. + + \ No newline at end of file diff --git a/ILSpy.AddIn/VSPackage.zh-Hans.resx b/ILSpy.AddIn/VSPackage.zh-Hans.resx new file mode 100644 index 000000000..db43f348a --- /dev/null +++ b/ILSpy.AddIn/VSPackage.zh-Hans.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ILSpy.AddIn + + + 可以在 Visual Studio 中直接打开反编译工具 ILSpy 。 + + \ No newline at end of file diff --git a/ILSpy.AddIn/zh-Hans/extension.vsixlangpack b/ILSpy.AddIn/zh-Hans/extension.vsixlangpack new file mode 100644 index 000000000..c650a50a2 --- /dev/null +++ b/ILSpy.AddIn/zh-Hans/extension.vsixlangpack @@ -0,0 +1,7 @@ + + + + ILSpy + 可以在 Visual Studio 中直接打开反编译工具 ILSpy 。 + + \ No newline at end of file diff --git a/ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj b/ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj index aacd89197..9e27fa5b5 100644 --- a/ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj +++ b/ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj @@ -1,6 +1,5 @@  - - + net472 @@ -8,8 +7,11 @@ 8.0 False - + en-US false + true + false + false @@ -44,6 +46,12 @@ + + + True + True + Resources.resx + ReadyToRunOptionPage.xaml @@ -51,6 +59,16 @@ + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + + + @@ -60,8 +78,6 @@ - - diff --git a/ILSpy.ReadyToRun/Properties/AssemblyInfo.cs b/ILSpy.ReadyToRun/Properties/AssemblyInfo.cs index 85918694e..cd1bac780 100644 --- a/ILSpy.ReadyToRun/Properties/AssemblyInfo.cs +++ b/ILSpy.ReadyToRun/Properties/AssemblyInfo.cs @@ -1,33 +1,10 @@ -#region Using directives - +using System; using System.Reflection; -using System.Runtime.InteropServices; +using System.Resources; using System.Runtime.CompilerServices; - -#endregion +using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("ILSpy.ReadyToRun.Plugin")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ILSpy.ReadyToRun.Plugin")] -[assembly: AssemblyCopyright("Copyright 2011")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// This sets the default COM visibility of types in the assembly to invisible. -// If you need to expose a type to COM, use [ComVisible(true)] on that type. -[assembly: ComVisible(false)] - -[assembly: InternalsVisibleTo("ILSpy.ReadyToRun.Tests")] - -// The assembly version has following format : -// -// Major.Minor.Build.Revision -// -// You can specify all the values or you can use the default the Revision and -// Build Numbers by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] diff --git a/ILSpy.ReadyToRun/Properties/Resources.Designer.cs b/ILSpy.ReadyToRun/Properties/Resources.Designer.cs new file mode 100644 index 000000000..56cf7ac1f --- /dev/null +++ b/ILSpy.ReadyToRun/Properties/Resources.Designer.cs @@ -0,0 +1,99 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace ILSpy.ReadyToRun.Properties { + using System; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ILSpy.ReadyToRun.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 重写当前线程的 CurrentUICulture 属性 + /// 重写当前线程的 CurrentUICulture 属性。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找类似 Disassembly Format 的本地化字符串。 + /// + public static string DisassemblyFormat { + get { + return ResourceManager.GetString("DisassemblyFormat", resourceCulture); + } + } + + /// + /// 查找类似 ReadyToRun 的本地化字符串。 + /// + public static string ReadyToRun { + get { + return ResourceManager.GetString("ReadyToRun", resourceCulture); + } + } + + /// + /// 查找类似 Show Debug Info 的本地化字符串。 + /// + public static string ShowDebugInfo { + get { + return ResourceManager.GetString("ShowDebugInfo", resourceCulture); + } + } + + /// + /// 查找类似 Show Unwind Info 的本地化字符串。 + /// + public static string ShowUnwindInfo { + get { + return ResourceManager.GetString("ShowUnwindInfo", resourceCulture); + } + } + } +} diff --git a/ILSpy.ReadyToRun/Properties/Resources.resx b/ILSpy.ReadyToRun/Properties/Resources.resx new file mode 100644 index 000000000..8c6d1ce8c --- /dev/null +++ b/ILSpy.ReadyToRun/Properties/Resources.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Disassembly Format + + + ReadyToRun + + + Show Debug Info + + + Show Unwind Info + + \ No newline at end of file diff --git a/ILSpy.ReadyToRun/Properties/Resources.zh-Hans.resx b/ILSpy.ReadyToRun/Properties/Resources.zh-Hans.resx new file mode 100644 index 000000000..9430c8a3d --- /dev/null +++ b/ILSpy.ReadyToRun/Properties/Resources.zh-Hans.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 反汇编格式 + + + ReadyToRun + + + 显示调试信息 + + + 显示展开信息 + + \ No newline at end of file diff --git a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs index bcf830831..2d6271028 100644 --- a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs +++ b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs @@ -24,9 +24,9 @@ using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; +using System.Resources; using System.Runtime.CompilerServices; using Iced.Intel; - using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.IL; diff --git a/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml b/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml index 7fb149cbe..c11450ef7 100644 --- a/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml +++ b/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml @@ -1,5 +1,6 @@  @@ -11,11 +12,11 @@ - Disassembly Format + - Show Unwind Info + - Show Debug Info + \ No newline at end of file diff --git a/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs b/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs index 3bf810d85..7abcb69a1 100644 --- a/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs +++ b/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs @@ -20,10 +20,10 @@ using System.ComponentModel; using System.Windows.Controls; using System.Xml.Linq; using ICSharpCode.ILSpy.Options; - +using ILSpy.ReadyToRun; namespace ICSharpCode.ILSpy.ReadyToRun { - [ExportOptionPage(Title = "ReadyToRun", Order = 40)] + [ExportOptionPage(Title = nameof(global::ILSpy.ReadyToRun.Properties.Resources.ReadyToRun), Order = 40)] partial class ReadyToRunOptionPage : UserControl, IOptionPage { public ReadyToRunOptionPage() diff --git a/ILSpy.Tests/ILSpy.Tests.csproj b/ILSpy.Tests/ILSpy.Tests.csproj index 8dc71b75d..fb4a483a6 100644 --- a/ILSpy.Tests/ILSpy.Tests.csproj +++ b/ILSpy.Tests/ILSpy.Tests.csproj @@ -46,8 +46,8 @@ - - + + diff --git a/ILSpy/AboutPage.cs b/ILSpy/AboutPage.cs index e904de4a8..1916abee5 100644 --- a/ILSpy/AboutPage.cs +++ b/ILSpy/AboutPage.cs @@ -88,7 +88,7 @@ namespace ICSharpCode.ILSpy plugin.Write(output); output.WriteLine(); output.Address = new Uri("resource://AboutPage"); - using (Stream s = typeof(AboutPage).Assembly.GetManifestResourceStream(typeof(AboutPage), "ILSpyAboutPage.txt")) { + using (Stream s = typeof(AboutPage).Assembly.GetManifestResourceStream(typeof(AboutPage), Resources.ILSpyAboutPageTxt)) { using (StreamReader r = new StreamReader(s)) { string line; while ((line = r.ReadLine()) != null) { diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs index 5d5f5e36b..2fa17861a 100644 --- a/ILSpy/App.xaml.cs +++ b/ILSpy/App.xaml.cs @@ -165,6 +165,9 @@ namespace ICSharpCode.ILSpy } } + [ThreadStatic] + static bool showingError; + static void UnhandledException(Exception exception) { Debug.WriteLine(exception.ToString()); @@ -176,26 +179,44 @@ namespace ICSharpCode.ILSpy break; } } - MessageBox.Show(exception.ToString(), "Sorry, we crashed"); + if (showingError) { + // Ignore re-entrant calls + // We run the risk of opening an infinite number of exception dialogs. + return; + } + showingError = true; + try { + MessageBox.Show(exception.ToString(), "Sorry, we crashed"); + } finally { + showingError = false; + } } #endregion #region Pass Command Line Arguments to previous instance bool SendToPreviousInstance(string message, bool activate) { + string ownProcessName; + using (var ownProcess = Process.GetCurrentProcess()) { + ownProcessName = ownProcess.ProcessName; + } + bool success = false; NativeMethods.EnumWindows( (hWnd, lParam) => { string windowTitle = NativeMethods.GetWindowText(hWnd, 100); if (windowTitle.StartsWith("ILSpy", StringComparison.Ordinal)) { - Debug.WriteLine("Found {0:x4}: {1}", hWnd, windowTitle); - IntPtr result = Send(hWnd, message); - Debug.WriteLine("WM_COPYDATA result: {0:x8}", result); - if (result == (IntPtr)1) { - if (activate) - NativeMethods.SetForegroundWindow(hWnd); - success = true; - return false; // stop enumeration + string processName = NativeMethods.GetProcessNameFromWindow(hWnd); + Debug.WriteLine("Found {0:x4}: '{1}' in '{2}'", hWnd, windowTitle, processName); + if (string.Equals(processName, ownProcessName, StringComparison.OrdinalIgnoreCase)) { + IntPtr result = Send(hWnd, message); + Debug.WriteLine("WM_COPYDATA result: {0:x8}", result); + if (result == (IntPtr)1) { + if (activate) + NativeMethods.SetForegroundWindow(hWnd); + success = true; + return false; // stop enumeration + } } } return true; // continue enumeration diff --git a/ILSpy/Commands/DecompileAllCommand.cs b/ILSpy/Commands/DecompileAllCommand.cs index ac263edf7..56a698e33 100644 --- a/ILSpy/Commands/DecompileAllCommand.cs +++ b/ILSpy/Commands/DecompileAllCommand.cs @@ -19,6 +19,7 @@ #if DEBUG using System; +using System.Collections.Concurrent; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -40,29 +41,32 @@ namespace ICSharpCode.ILSpy { Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task.Factory.StartNew(() => { AvalonEditTextOutput output = new AvalonEditTextOutput(); - Parallel.ForEach(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, delegate(LoadedAssembly asm) { - if (!asm.HasLoadError) { - Stopwatch w = Stopwatch.StartNew(); - Exception exception = null; - using (var writer = new System.IO.StreamWriter("c:\\temp\\decompiled\\" + asm.ShortName + ".cs")) { - try { - new CSharpLanguage().DecompileAssembly(asm, new Decompiler.PlainTextOutput(writer), new DecompilationOptions() { FullDecompilation = true, CancellationToken = ct }); + Parallel.ForEach( + Partitioner.Create( MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), loadBalance: true), + new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, + delegate(LoadedAssembly asm) { + if (!asm.HasLoadError) { + Stopwatch w = Stopwatch.StartNew(); + Exception exception = null; + using (var writer = new System.IO.StreamWriter("c:\\temp\\decompiled\\" + asm.ShortName + ".cs")) { + try { + new CSharpLanguage().DecompileAssembly(asm, new Decompiler.PlainTextOutput(writer), new DecompilationOptions() { FullDecompilation = true, CancellationToken = ct }); + } + catch (Exception ex) { + writer.WriteLine(ex.ToString()); + exception = ex; + } } - catch (Exception ex) { - writer.WriteLine(ex.ToString()); - exception = ex; + lock (output) { + output.Write(asm.ShortName + " - " + w.Elapsed); + if (exception != null) { + output.Write(" - "); + output.Write(exception.GetType().Name); + } + output.WriteLine(); } } - lock (output) { - output.Write(asm.ShortName + " - " + w.Elapsed); - if (exception != null) { - output.Write(" - "); - output.Write(exception.GetType().Name); - } - output.WriteLine(); - } - } - }); + }); return output; }, ct)).Then(output => Docking.DockWorkspace.Instance.ShowText(output)).HandleExceptions(); } diff --git a/ILSpy/Commands/DecompileCommand.cs b/ILSpy/Commands/DecompileCommand.cs index 24c6c4180..d9485e512 100644 --- a/ILSpy/Commands/DecompileCommand.cs +++ b/ILSpy/Commands/DecompileCommand.cs @@ -24,11 +24,12 @@ using System.Windows.Controls; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.ILSpy.Metadata; +using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TreeNodes; namespace ICSharpCode.ILSpy.Commands { - [ExportContextMenuEntry(Header = "Decompile", Order = 10)] + [ExportContextMenuEntry(Header = nameof(Resources.Decompile), Order = 10)] class DecompileCommand : IContextMenuEntry { public bool IsVisible(TextViewContext context) @@ -68,7 +69,7 @@ namespace ICSharpCode.ILSpy.Commands } } - [ExportContextMenuEntry(Header = "Go to token", Order = 10)] + [ExportContextMenuEntry(Header = nameof(Resources.GoToToken), Order = 10)] class GoToToken : IContextMenuEntry { public void Execute(TextViewContext context) diff --git a/ILSpy/Commands/DisassembleAllCommand.cs b/ILSpy/Commands/DisassembleAllCommand.cs index 23319be78..e220ead0c 100644 --- a/ILSpy/Commands/DisassembleAllCommand.cs +++ b/ILSpy/Commands/DisassembleAllCommand.cs @@ -23,6 +23,8 @@ using System.Diagnostics; using System.Threading.Tasks; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.Properties; +using System.Collections.Concurrent; + namespace ICSharpCode.ILSpy { [ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources.DEBUGDisassemble), MenuCategory = nameof(Resources.Open), MenuOrder = 2.5)] @@ -37,8 +39,11 @@ namespace ICSharpCode.ILSpy { Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task.Factory.StartNew(() => { AvalonEditTextOutput output = new AvalonEditTextOutput(); - Parallel.ForEach(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, delegate(LoadedAssembly asm) { - if (!asm.HasLoadError) { + Parallel.ForEach( + Partitioner.Create(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), loadBalance: true), + new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, + delegate (LoadedAssembly asm) { + if (!asm.HasLoadError) { Stopwatch w = Stopwatch.StartNew(); Exception exception = null; using (var writer = new System.IO.StreamWriter("c:\\temp\\disassembled\\" + asm.Text.Replace("(", "").Replace(")", "").Replace(' ', '_') + ".il")) { diff --git a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs index 9bea8740e..36330fefa 100644 --- a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs +++ b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs @@ -32,7 +32,7 @@ using ICSharpCode.ILSpy.Properties; namespace ICSharpCode.ILSpy { - [ExportContextMenuEntry(Header = "Generate portable PDB")] + [ExportContextMenuEntry(Header = nameof(Resources.GeneratePortable))] class GeneratePdbContextMenuEntry : IContextMenuEntry { public void Execute(TextViewContext context) @@ -55,12 +55,12 @@ namespace ICSharpCode.ILSpy { var file = assembly.GetPEFileOrNull(); if (!PortablePdbWriter.HasCodeViewDebugDirectoryEntry(file)) { - MessageBox.Show($"Cannot create PDB file for {Path.GetFileName(assembly.FileName)}, because it does not contain a PE Debug Directory Entry of type 'CodeView'."); + MessageBox.Show(string.Format(Resources.CannotCreatePDBFile, Path.GetFileName(assembly.FileName))); return; } SaveFileDialog dlg = new SaveFileDialog(); dlg.FileName = DecompilerTextView.CleanUpName(assembly.ShortName) + ".pdb"; - dlg.Filter = "Portable PDB|*.pdb|All files|*.*"; + dlg.Filter = Resources.PortablePDBPdbAllFiles; dlg.InitialDirectory = Path.GetDirectoryName(assembly.FileName); if (dlg.ShowDialog() != true) return; DecompilationOptions options = new DecompilationOptions(); @@ -74,21 +74,21 @@ namespace ICSharpCode.ILSpy PortablePdbWriter.WritePdb(file, decompiler, options.DecompilerSettings, stream); } catch (OperationCanceledException) { output.WriteLine(); - output.WriteLine("Generation was cancelled."); + output.WriteLine(Resources.GenerationWasCancelled); throw; } } stopwatch.Stop(); - output.WriteLine("Generation complete in " + stopwatch.Elapsed.TotalSeconds.ToString("F1") + " seconds."); + output.WriteLine(string.Format(Resources.GenerationCompleteInSeconds, stopwatch.Elapsed.TotalSeconds.ToString("F1"))); output.WriteLine(); - output.AddButton(null, "Open Explorer", delegate { Process.Start("explorer", "/select,\"" + fileName + "\""); }); + output.AddButton(null, Resources.OpenExplorer, delegate { Process.Start("explorer", "/select,\"" + fileName + "\""); }); output.WriteLine(); return output; }, ct)).Then(output => Docking.DockWorkspace.Instance.ShowText(output)).HandleExceptions(); } } - [ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources.GeneratePortable), MenuCategory = "Save")] + [ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources.GeneratePortable), MenuCategory = nameof(Resources.Save))] class GeneratePdbMainMenuEntry : SimpleCommand { public override bool CanExecute(object parameter) diff --git a/ILSpy/Commands/ManageAssemblyListsCommand.cs b/ILSpy/Commands/ManageAssemblyListsCommand.cs index 49effa4a7..10ba404d2 100644 --- a/ILSpy/Commands/ManageAssemblyListsCommand.cs +++ b/ILSpy/Commands/ManageAssemblyListsCommand.cs @@ -21,7 +21,7 @@ using ICSharpCode.ILSpy.Properties; namespace ICSharpCode.ILSpy { - [ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources.ManageAssemblyLists), MenuIcon = "Images/AssemblyList", MenuCategory = nameof(Resources.Open), MenuOrder = 1.7)] + [ExportMainMenuCommand(Menu = nameof(Resources._File), Header = nameof(Resources.ManageAssembly_Lists), MenuIcon = "Images/AssemblyList", MenuCategory = nameof(Resources.Open), MenuOrder = 1.7)] sealed class ManageAssemblyListsCommand : SimpleCommand { public override void Execute(object parameter) diff --git a/ILSpy/Commands/Pdb2XmlCommand.cs b/ILSpy/Commands/Pdb2XmlCommand.cs index 91b481f7b..13856e8b0 100644 --- a/ILSpy/Commands/Pdb2XmlCommand.cs +++ b/ILSpy/Commands/Pdb2XmlCommand.cs @@ -24,13 +24,14 @@ using System.Linq; using System.Threading.Tasks; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.Decompiler; +using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; using Microsoft.DiaSymReader.Tools; namespace ICSharpCode.ILSpy { - [ExportMainMenuCommand(Menu = "_File", Header = "DEBUG -- Dump PDB as XML", MenuCategory = "Open", MenuOrder = 2.6)] + [ExportMainMenuCommand(Menu = nameof(Resources._File) , Header = nameof(Resources.DEBUGDumpPDBAsXML), MenuCategory = nameof(Resources.Open), MenuOrder = 2.6)] sealed class Pdb2XmlCommand : SimpleCommand { public override bool CanExecute(object parameter) @@ -64,7 +65,7 @@ namespace ICSharpCode.ILSpy } } - [ExportContextMenuEntry(Header = "DEBUG -- Dump PDB as XML")] + [ExportContextMenuEntry(Header = nameof(Resources.DEBUGDumpPDBAsXML))] class Pdb2XmlCommandContextMenuEntry : IContextMenuEntry { public void Execute(TextViewContext context) diff --git a/ILSpy/Commands/SaveCodeContextMenuEntry.cs b/ILSpy/Commands/SaveCodeContextMenuEntry.cs index 84282eaa2..c54a2ba7b 100644 --- a/ILSpy/Commands/SaveCodeContextMenuEntry.cs +++ b/ILSpy/Commands/SaveCodeContextMenuEntry.cs @@ -93,7 +93,7 @@ namespace ICSharpCode.ILSpy.TextView { SaveFileDialog dlg = new SaveFileDialog(); dlg.FileName = "Solution.sln"; - dlg.Filter = "Visual Studio Solution file|*.sln|All files|*.*"; + dlg.Filter = Resources.VisualStudioSolutionFileSlnAllFiles; if (dlg.ShowDialog() != true) { return null; diff --git a/ILSpy/Commands/SelectPdbContextMenuEntry.cs b/ILSpy/Commands/SelectPdbContextMenuEntry.cs index 3303ca15c..b538ad708 100644 --- a/ILSpy/Commands/SelectPdbContextMenuEntry.cs +++ b/ILSpy/Commands/SelectPdbContextMenuEntry.cs @@ -18,12 +18,13 @@ using System.IO; using System.Linq; +using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; using Microsoft.Win32; namespace ICSharpCode.ILSpy { - [ExportContextMenuEntry(Header = "Select PDB...")] + [ExportContextMenuEntry(Header = nameof(Resources.SelectPDB))] class SelectPdbContextMenuEntry : IContextMenuEntry { public void Execute(TextViewContext context) @@ -32,7 +33,7 @@ namespace ICSharpCode.ILSpy if (assembly == null) return; OpenFileDialog dlg = new OpenFileDialog(); dlg.FileName = DecompilerTextView.CleanUpName(assembly.ShortName) + ".pdb"; - dlg.Filter = "Portable PDB|*.pdb|All files|*.*"; + dlg.Filter = Resources.PortablePDBPdbAllFiles; dlg.InitialDirectory = Path.GetDirectoryName(assembly.FileName); if (dlg.ShowDialog() != true) return; diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 71414747b..197bd9ed7 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -128,9 +128,7 @@ - - Form - + @@ -457,6 +455,7 @@ + license.txt @@ -845,7 +844,14 @@ - + + + + powershell -NoProfile -ExecutionPolicy Bypass -File BuildTools/sort-resx.ps1 + + + + diff --git a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs index 6845088a3..f588cd153 100644 --- a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs +++ b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs @@ -184,8 +184,10 @@ namespace ICSharpCode.ILSpy case "set": case "add": case "remove": + case "init": if (role == PropertyDeclaration.GetKeywordRole || role == PropertyDeclaration.SetKeywordRole || + role == PropertyDeclaration.InitKeywordRole || role == CustomEventDeclaration.AddKeywordRole || role == CustomEventDeclaration.RemoveKeywordRole) color = accessorKeywordsColor; diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index 8b0253026..96c22ce91 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -648,6 +648,9 @@ namespace ICSharpCode.ILSpy if (!settings.LiftNullables) { flags &= ~ConversionFlags.UseNullableSpecifierForValueTypes; } + if (entity is IMethod m && m.IsLocalFunction) { + writer.WriteIdentifier(Identifier.Create("(local)")); + } new CSharpAmbience() { ConversionFlags = flags, }.ConvertSymbol(entity, writer, settings.CSharpFormattingOptions); diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs index fc0e1bcdc..60c37eefe 100644 --- a/ILSpy/LoadedAssembly.cs +++ b/ILSpy/LoadedAssembly.cs @@ -20,17 +20,21 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using System.Windows.Threading; + using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.PdbProvider; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem.Implementation; +using ICSharpCode.Decompiler.Util; using ICSharpCode.ILSpy.Options; namespace ICSharpCode.ILSpy @@ -318,6 +322,17 @@ namespace ICSharpCode.ILSpy static readonly Dictionary loadingAssemblies = new Dictionary(); MyUniversalResolver universalResolver; + /// + /// 1) try to find exact match by tfm + full asm name in loaded assemblies + /// 2) try to find match in search paths + /// 3) if a.deps.json is found: search %USERPROFILE%/.nuget/packages/* as well + /// 4) look in /dotnet/shared/{runtime-pack}/{closest-version} + /// 5) if the version is retargetable or all zeros or ones, search C:\Windows\Microsoft.NET\Framework64\v4.0.30319 + /// 6) For "mscorlib.dll" we use the exact same assembly with which ILSpy runs + /// 7) Search the GAC + /// 8) search C:\Windows\Microsoft.NET\Framework64\v4.0.30319 + /// 9) try to find match by asm name (no tfm/version) in loaded assemblies + /// LoadedAssembly LookupReferencedAssemblyInternal(IAssemblyReference fullName, bool isWinRT, string tfm) { string key = tfm + ";" + (isWinRT ? fullName.Name : fullName.FullName); @@ -359,8 +374,29 @@ namespace ICSharpCode.ILSpy LoadedAssemblyReferencesInfo.AddMessage(fullName.ToString(), MessageKind.Info, "Success - Loading from: " + file); asm = new LoadedAssembly(assemblyList, file) { IsAutoLoaded = true }; } else { - LoadedAssemblyReferencesInfo.AddMessageOnce(fullName.ToString(), MessageKind.Error, "Could not find reference: " + fullName); - return null; + var candidates = new List<(LoadedAssembly assembly, Version version)>(); + + foreach (LoadedAssembly loaded in assemblyList.GetAssemblies()) { + var module = loaded.GetPEFileOrNull(); + var reader = module?.Metadata; + if (reader == null || !reader.IsAssembly) continue; + var asmDef = reader.GetAssemblyDefinition(); + var asmDefName = reader.GetString(asmDef.Name); + if (fullName.Name.Equals(asmDefName, StringComparison.OrdinalIgnoreCase)) { + candidates.Add((loaded, asmDef.Version)); + } + } + + if (candidates.Count == 0) { + LoadedAssemblyReferencesInfo.AddMessageOnce(fullName.ToString(), MessageKind.Error, "Could not find reference: " + fullName); + return null; + } + + candidates.SortBy(c => c.version); + + var bestCandidate = candidates.FirstOrDefault(c => c.version >= fullName.Version).assembly ?? candidates.Last().assembly; + LoadedAssemblyReferencesInfo.AddMessageOnce(fullName.ToString(), MessageKind.Info, "Success - Found in Assembly List with different TFM or version: " + bestCandidate.fileName); + return bestCandidate; } loadingAssemblies.Add(file, asm); } @@ -371,7 +407,7 @@ namespace ICSharpCode.ILSpy lock (loadingAssemblies) { loadingAssemblies.Remove(file); } - }); + }, DispatcherPriority.Normal); return asm; } diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 9e03b263c..10bd97d47 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -408,6 +408,9 @@ namespace ICSharpCode.ILSpy found = true; } else { IEntity mr = await Task.Run(() => FindEntityInRelevantAssemblies(navigateTo, relevantAssemblies)); + // Make sure we wait for assemblies being loaded... + // BeginInvoke in LoadedAssembly.LookupReferencedAssemblyInternal + await Dispatcher.InvokeAsync(delegate { }, DispatcherPriority.Normal); if (mr != null && mr.ParentModule.PEFile != null) { found = true; if (AssemblyTreeView.SelectedItem == initialSelection) { @@ -483,6 +486,12 @@ namespace ICSharpCode.ILSpy return false; } + // We intentionally ignore reference assemblies, so that the loop continues looking for another assembly that might have a usable definition. + if (module.IsReferenceAssembly()) { + typeHandle = default; + return false; + } + switch (typeRef) { case GetPotentiallyNestedClassTypeReference topLevelType: typeHandle = topLevelType.ResolveInPEFile(module); diff --git a/ILSpy/Metadata/Helpers.cs b/ILSpy/Metadata/Helpers.cs index 38f69d30d..6321f8ee3 100644 --- a/ILSpy/Metadata/Helpers.cs +++ b/ILSpy/Metadata/Helpers.cs @@ -113,6 +113,7 @@ namespace ICSharpCode.ILSpy.Metadata }; switch (e.PropertyName) { case "RID": + case "Meaning": e.Column.SetTemplate((ControlTemplate)MetadataTableViews.Instance["DefaultFilter"]); ((DataGridCustomTextColumn)e.Column).ToolTipBinding = null; break; diff --git a/ILSpy/NativeMethods.cs b/ILSpy/NativeMethods.cs index 9487743d6..3805f3a23 100644 --- a/ILSpy/NativeMethods.cs +++ b/ILSpy/NativeMethods.cs @@ -20,20 +20,24 @@ using System; using System.Text; using System.Runtime.InteropServices; using System.ComponentModel; +using System.Diagnostics; namespace ICSharpCode.ILSpy { static class NativeMethods { public const uint WM_COPYDATA = 0x4a; - + [DllImport("user32.dll", CharSet = CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); - + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + internal static extern unsafe int GetWindowThreadProcessId(IntPtr hWnd, int* lpdwProcessId); + [DllImport("user32.dll", CharSet = CharSet.Auto)] static extern int GetWindowText(IntPtr hWnd, [Out] StringBuilder title, int size); - + public static string GetWindowText(IntPtr hWnd, int maxLength) { StringBuilder b = new StringBuilder(maxLength + 1); @@ -42,12 +46,12 @@ namespace ICSharpCode.ILSpy else return string.Empty; } - + [DllImport("user32.dll", CharSet = CharSet.Auto)] internal static extern IntPtr SendMessageTimeout( IntPtr hWnd, uint msg, IntPtr wParam, ref CopyDataStruct lParam, uint flags, uint timeout, out IntPtr result); - + [DllImport("user32.dll", CharSet = CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SetForegroundWindow(IntPtr hWnd); @@ -145,11 +149,31 @@ namespace ICSharpCode.ILSpy } } #endregion + + public unsafe static string GetProcessNameFromWindow(IntPtr hWnd) + { + int processId; + GetWindowThreadProcessId(hWnd, &processId); + try { + using (var p = Process.GetProcessById(processId)) { + return p.ProcessName; + } + } catch (ArgumentException ex) { + Debug.WriteLine(ex.Message); + return null; + } catch (InvalidOperationException ex) { + Debug.WriteLine(ex.Message); + return null; + } catch (Win32Exception ex) { + Debug.WriteLine(ex.Message); + return null; + } + } } [return: MarshalAs(UnmanagedType.Bool)] delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); - + [StructLayout(LayoutKind.Sequential)] struct CopyDataStruct { diff --git a/ILSpy/Properties/AssemblyInfo.template.cs b/ILSpy/Properties/AssemblyInfo.template.cs index ac72179e6..e564bd6b8 100644 --- a/ILSpy/Properties/AssemblyInfo.template.cs +++ b/ILSpy/Properties/AssemblyInfo.template.cs @@ -37,7 +37,7 @@ using System.Diagnostics.CodeAnalysis; internal static class RevisionClass { public const string Major = "6"; - public const string Minor = "0"; + public const string Minor = "1"; public const string Build = "0"; public const string Revision = "$INSERTREVISION$"; public const string VersionName = null; diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 84a05cf4c..32289de3d 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -441,6 +441,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Cannot create PDB file for {0}, because it does not contain a PE Debug Directory Entry of type 'CodeView'.. + /// + public static string CannotCreatePDBFile { + get { + return ResourceManager.GetString("CannotCreatePDBFile", resourceCulture); + } + } + /// /// Looks up a localized string similar to Check again. /// @@ -558,6 +567,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to DEBUG -- Dump PDB as XML. + /// + public static string DEBUGDumpPDBAsXML { + get { + return ResourceManager.GetString("DEBUGDumpPDBAsXML", resourceCulture); + } + } + /// /// Looks up a localized string similar to Debug Steps. /// @@ -576,6 +594,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Decompile. + /// + public static string Decompile { + get { + return ResourceManager.GetString("Decompile", resourceCulture); + } + } + /// /// Looks up a localized string similar to Decompiler. /// @@ -585,6 +612,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Always inline local variables if possible. + /// + public static string DecompilerSettings_AggressiveInlining { + get { + return ResourceManager.GetString("DecompilerSettings.AggressiveInlining", resourceCulture); + } + } + /// /// Looks up a localized string similar to Allow extension 'Add' methods in collection initializer expressions. /// @@ -829,7 +865,7 @@ namespace ICSharpCode.ILSpy.Properties { } /// - /// Looks up a localized string similar to Transform to do-while, if possible.. + /// Looks up a localized string similar to Transform to do-while, if possible. /// public static string DecompilerSettings_DoWhileStatement { get { @@ -838,7 +874,7 @@ namespace ICSharpCode.ILSpy.Properties { } /// - /// Looks up a localized string similar to Transform to for, if possible.. + /// Looks up a localized string similar to Transform to for, if possible. /// public static string DecompilerSettings_ForStatement { get { @@ -864,6 +900,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Allow init; accessors. + /// + public static string DecompilerSettings_InitAccessors { + get { + return ResourceManager.GetString("DecompilerSettings.InitAccessors", resourceCulture); + } + } + /// /// Looks up a localized string similar to Insert using declarations. /// @@ -1002,7 +1047,7 @@ namespace ICSharpCode.ILSpy.Properties { } /// - /// Looks up a localized string similar to Separate local variable declarations and initializers (int x = 5; -> int x; x = 5;), if possible.. + /// Looks up a localized string similar to Separate local variable declarations and initializers (int x = 5; -> int x; x = 5;), if possible. /// public static string DecompilerSettings_SeparateLocalVariableDeclarations { get { @@ -1019,6 +1064,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Switch expressions. + /// + public static string DecompilerSettings_SwitchExpressions { + get { + return ResourceManager.GetString("DecompilerSettings.SwitchExpressions", resourceCulture); + } + } + /// /// Looks up a localized string similar to Use discards. /// @@ -1379,6 +1433,33 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Generation complete in {0} seconds.. + /// + public static string GenerationCompleteInSeconds { + get { + return ResourceManager.GetString("GenerationCompleteInSeconds", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Generation was cancelled.. + /// + public static string GenerationWasCancelled { + get { + return ResourceManager.GetString("GenerationWasCancelled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Go to token. + /// + public static string GoToToken { + get { + return ResourceManager.GetString("GoToToken", resourceCulture); + } + } + /// /// Looks up a localized string similar to Hide empty metadata tables from tree view. /// @@ -1397,6 +1478,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to ILSpyAboutPage.txt. + /// + public static string ILSpyAboutPageTxt { + get { + return ResourceManager.GetString("ILSpyAboutPageTxt", resourceCulture); + } + } + /// /// Looks up a localized string similar to ILSpy version . /// @@ -1677,6 +1767,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Portable PDB|*.pdb|All files|*.*. + /// + public static string PortablePDBPdbAllFiles { + get { + return ResourceManager.GetString("PortablePDBPdbAllFiles", resourceCulture); + } + } + /// /// Looks up a localized string similar to for ex. property getter/setter access. To get optimal decompilation results, please manually add the missing references to the list of loaded assemblies.. /// @@ -1931,6 +2030,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Select PDB.... + /// + public static string SelectPDB { + get { + return ResourceManager.GetString("SelectPDB", resourceCulture); + } + } + /// /// Looks up a localized string similar to Select version of language to output. /// @@ -2291,6 +2399,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Visual Studio Solution file|*.sln|All files|*.*. + /// + public static string VisualStudioSolutionFileSlnAllFiles { + get { + return ResourceManager.GetString("VisualStudioSolutionFileSlnAllFiles", resourceCulture); + } + } + /// /// Looks up a localized string similar to Warning: This assembly is marked as 'reference assembly', which means that it only contains metadata and no executable code.. /// @@ -2319,7 +2436,7 @@ namespace ICSharpCode.ILSpy.Properties { } /// - /// Looks up a localized string similar to Close all documents. + /// Looks up a localized string similar to _Close all documents. /// public static string Window_CloseAllDocuments { get { @@ -2328,7 +2445,7 @@ namespace ICSharpCode.ILSpy.Properties { } /// - /// Looks up a localized string similar to Reset layout. + /// Looks up a localized string similar to _Reset layout. /// public static string Window_ResetLayout { get { diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index 62a8ffa83..f5cb6e288 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -117,766 +117,805 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Navigation + + About - - Back + + Add preconfigured list... - - Forward + + Add shell integration - - _Check for Updates + + This will add "{0}" to the registry at "HKCU\Software\Classes\dllfile\shell\Open with ILSpy\command" and "HKCU\Software\Classes\exefile\shell\Open with ILSpy\command" to allow opening .dll and .exe files from the Windows Explorer context menu. + +Do you want to continue? - - _Help + + |All Files|*.* - - _File + + Allow multiple instances - - Open + + Always use braces - - DEBUG -- Disassemble All + + Analyze - - E_xit + + Assemblies - - Exit + + Assembly - - Save + + The directory is not empty. File will be overwritten. +Are you sure you want to continue? - - _Open... + + Project Directory not empty - - Open from _GAC... + + Automatically check for updates every week - - Manage assembly _lists... + + Back - - Reload all assemblies + + Base Types - - DEBUG -- Decompile All + + C_lone - - DEBUG -- Decompile 100x + + Cancel - - Generate portable PDB + + Entity could not be resolved. Cannot analyze entities from missing assembly references. Add the missing reference and try again. - - Reload all assemblies + + Cannot create PDB file for {0}, because it does not contain a PE Debug Directory Entry of type 'CodeView'. - - _Reload + + Check again - - _Remove Assemblies with load errors + + Check for updates - - Remove + + Checking... - - _Save Code... + + Close - - _Show debug steps + + Collapse all tree nodes - - _View + + Copy - - Sort assembly _list by name + + Copy error message - - View + + Copy FQ Name - - Sort assembly list by name + + Create - - _Collapse all tree nodes + + Culture - - Collapse all tree nodes + + DEBUG -- Decompile All - - Name + + DEBUG -- Decompile 100x - - Value (as string) + + DEBUG -- Disassemble All - - Type + + DEBUG -- Dump PDB as XML - - String Table + + Debug Steps - - Value + + Debug this step - - Use variable names from debug symbols, if available + + Decompile - - Show info from debug symbols, if available + + Decompile to new tab - - Show XML documentation in decompiled code + + Decompiler - - Enable folding on all blocks in braces + + Always inline local variables if possible - - Remove dead and side effect free code + + Allow extension 'Add' methods in collection initializer expressions - - Insert using declarations + + Use 'ref' extension methods - - Always use braces + + Always cast targets of explicit interface implementation calls - - Expand member definitions after decompilation + + Always show enum member values - - Font + + Always use braces - - Other options + + Apply Windows Runtime projections on loaded assemblies - - Show line numbers + + Array initializer expressions - - Show metadata tokens + + Decompile async IAsyncEnumerator methods - - Enable word wrap + + Decompile ?. and ?[] operators - - Sort results by fitness + + Decompile anonymous methods/lambdas - - Allow multiple instances + + Decompile anonymous types - - Cancel + + Decompile async methods - - OK + + Decompile automatic events - - Search + + Decompile automatic properties - - Search for t:TypeName, m:Member or c:Constant; use exact match (=term), 'should not contain' (-term) or 'must contain' (+term); use /reg(ular)?Ex(pressions)?/ or both - t:/Type(Name)?/... + + Decompile await in catch/finally blocks - - _Search for: + + Decompile C# 1.0 'public unsafe fixed int arr[10];' members - - Location + + Decompile [DecimalConstant(...)] as simple literal values - - Decompiling... + + Decompile enumerators (yield return) - - Copy + + Decompile expression trees - - Editor + + Decompile use of the 'dynamic' type - - Toggle All Folding + + Detect awaited using and foreach statements - - Folding + + Detect foreach statements - - Resources file (*.resources)|*.resources|Resource XML file|*.resx + + Detect lock statements - - _Remove + + Detect switch on string - - _Load Dependencies + + Detect tuple comparisons - - Dependencies + + Detect using statements - - _Add To Main List + + Dictionary initializer expressions - - _Open Containing Folder + + Transform to do-while, if possible - - Shell + + F#-specific options - - _Open Command Line Here + + Transform to for, if possible - - Copy FQ Name + + Include XML documentation comments in the decompiled code - - Loading... + + Allow init; accessors - - Copy error message + + Insert using declarations - - Derived Types + + Introduce local functions - - References + + Introduce static local functions - - Resources + + IsByRefLikeAttribute should be replaced with 'ref' modifiers on structs - - _About + + IsReadOnlyAttribute should be replaced with 'readonly'/'in' modifiers on structs/parameters - - ILSpy version + + IsUnmanagedAttribute on type parameters should be replaced with 'unmanaged' constraints - - Automatically check for updates every week + + Use nint/nuint types - - Check for updates + + Nullable reference types - - Checking... + + Object/collection initializer expressions - - You are using the latest release. + + Other - - Version {0} is available. + + Ranges - - Download + + Read-only methods - - You are using a nightly build newer than the latest release. + + Remove dead and side effect free code (use with caution!) - - Show public, private and internal + + Remove dead stores (use with caution!) - - Show public, private and internal + + Remove optional arguments, if possible - - Stand by... + + Separate local variable declarations and initializers (int x = 5; -> int x; x = 5;), if possible - - Status + + Show info from debug symbols, if available - - A new ILSpy version is available. + + Switch expressions - - No update for ILSpy found. + + Use discards - - Check again + + Use expression-bodied member syntax for get-only properties - - Searching... + + Use extension method syntax - - Search aborted, more than 1000 results found. + + Use implicit conversions between tuple types - - Search... + + Use implicit method group conversions - - Display Code + + Use LINQ expression syntax - - Save Code + + Use lambda syntax, if possible - - |All Files|*.* + + Use lifted operators for nullables - - Open Explorer + + Use named arguments - - Select All + + Use non-trailing named arguments - - Toggle Folding + + Use out variable declarations - - Analyze + + Use pattern-based fixed statement - - Enter a list name: + + Use new SDK style format for generated project files (*.csproj) - - Create + + Use stackalloc initializer syntax - - New list + + Use string interpolation - - Select assemblies to open: + + Use throw expressions - - Nuget Package Browser + + Use tuple type syntax - - Open From GAC + + Use variable names from debug symbols, if available - - _Search: + + VB-specific options - - Reference Name + + The settings selected below are applied to the decompiler output in combination with the selection in the language drop-down. Selecting a lower language version in the drop-down will deactivate all selected options of the higher versions. Note that some settings implicitly depend on each other, e.g.: LINQ expressions cannot be introduced without first transforming static calls to extension method calls. - - Version + + Decompiling... - - Public Key Token + + Dependencies - - Manage Assembly Lists + + Derived Types - - Select a list: + + Display - - _New + + Display Code - - _Open + + Font: - - _Delete + + Download - - _Reset + + E_xit - - Options + + Editor - - _Options... + + Enable folding on all blocks in braces - - Display + + Enable word wrap - - Decompiler + + Enter a list name: - - Misc + + Exit - - Font: + + Expand member definitions after decompilation - - Size: + + Expand using declarations after decompilation - - Debug Steps + + Folding - - UseFieldSugar + + Font - - UseLogicOperationSugar + + Forward - - ShowILRanges + + Generate portable PDB - - ShowChildIndexInBlock + + Generation complete in {0} seconds. - - Show state before this step + + Generation was cancelled. - - Show state after this step + + Go to token - - Debug this step + + Hide empty metadata tables from tree view - - Warning: This assembly is marked as 'reference assembly', which means that it only contains metadata and no executable code. + + Highlight matching braces - - Warning: Some assembly references could not be resolved automatically. This might lead to incorrect decompilation of some parts, + + ILSpyAboutPage.txt - - for ex. property getter/setter access. To get optimal decompilation results, please manually add the missing references to the list of loaded assemblies. + + ILSpy version - - Show assembly load log + + A new ILSpy version is available. - - Other Resources + + Indent size: - - Use tabs instead of spaces + + Indentation - - Show metadata tokens in base 10 + + Insert using declarations - - Expand using declarations after decompilation + + Are you sure that you want to delete the selected assembly list? + + + A list with the same name was found. + + + Are you sure that you want to remove all assembly lists and recreate the default assembly lists? Load assemblies that were loaded in the last instance. - - Indentation + + Loading... - - Indent size: + + Location - - Tab size: + + Manage Assembly Lists - - Search (Ctrl+Shift+F or Ctrl+E) + + Manage assembly _lists... - - Show all types and members + + Misc - - Other + + Name - - Show _all types and members + + Navigation - - The settings selected below are applied to the decompiler output in combination with the selection in the language drop-down. Selecting a lower language version in the drop-down will deactivate all selected options of the higher versions. Note that some settings implicitly depend on each other, e.g.: LINQ expressions cannot be introduced without first transforming static calls to extension method calls. + + Navigation failed because the target is hidden or a compiler-generated class.\n +Please disable all filters that might hide the item (i.e. activate "View > Show internal types and members") and try again. - - Decompile enumerators (yield return) + + New list - - Decompile anonymous methods/lambdas + + New Tab - - Decompile anonymous types + + Nuget Package Browser - - Use lambda syntax, if possible + + OK - - Decompile expression trees + + Open - - Decompile use of the 'dynamic' type + + Open Explorer - - Decompile async methods + + Open From GAC - - Decompile await in catch/finally blocks + + Open from _GAC... - - Decompile [DecimalConstant(...)] as simple literal values + + _Delete - - Decompile C# 1.0 'public unsafe fixed int arr[10];' members + + _Open - - Use lifted operators for nullables + + Options - - Decompile ?. and ?[] operators + + Other - - Decompile automatic properties + + Other options - - Decompile automatic events + + Other Resources - - Detect using statements + + Portable PDB|*.pdb|All files|*.* - - Other + + for ex. property getter/setter access. To get optimal decompilation results, please manually add the missing references to the list of loaded assemblies. - - Always use braces + + Public Key Token - - Detect foreach statements + + R_ename - - Detect lock statements + + Reference Name - - Detect switch on string + + References - - Insert using declarations + + Reload all assemblies - - Use extension method syntax + + Reload all assemblies - - Use LINQ expression syntax + + Remove - - Use implicit method group conversions + + Remove dead and side effect free code - - Always cast targets of explicit interface implementation calls + + Remove shell integration - - Use variable names from debug symbols, if available + + This will remove "{0}" from the registry at "HKCU\Software\Classes\dllfile\shell\Open with ILSpy\command" and "HKCU\Software\Classes\exefile\shell\Open with ILSpy\command". + +Do you want to continue? - - Array initializer expressions + + Rename list - - Object/collection initializer expressions + + Reset to defaults - - Dictionary initializer expressions + + Do you really want to load the default settings for the active page? - - Allow extension 'Add' methods in collection initializer expressions + + Resources file (*.resources)|*.resources|Resource XML file|*.resx - - Use string interpolation + + Save - - Include XML documentation comments in the decompiled code + + Save Code - - Use expression-bodied member syntax for get-only properties + + Search... - - Use out variable declarations + + Search aborted, more than 1000 results found. - - Use discards + + Search (Ctrl+Shift+F or Ctrl+E) - - IsByRefLikeAttribute should be replaced with 'ref' modifiers on structs + + Search Microsoft Docs... - - IsReadOnlyAttribute should be replaced with 'readonly'/'in' modifiers on structs/parameters + + Search - - IsUnmanagedAttribute on type parameters should be replaced with 'unmanaged' constraints + + Searching... - - Use stackalloc initializer syntax + + Select All - - Use tuple type syntax + + Select assemblies to open: - - Use implicit conversions between tuple types + + Select a list of assemblies - - Detect tuple comparisons + + Select language to decompile to - - Use named arguments + + Select a list: - - Use non-trailing named arguments + + Select PDB... - - Remove optional arguments, if possible + + Select version of language to output - - Introduce local functions + + Shell - - Nullable reference types + + Show all types and members - + + Show assembly load log + + + ShowChildIndexInBlock + + + Show XML documentation in decompiled code + + + ShowILRanges + + Show info from debug symbols, if available - - VB-specific options + + Show public, private and internal - - F#-specific options + + Show line numbers - - Remove dead and side effect free code (use with caution!) + + Show metadata tokens - - Apply Windows Runtime projections on loaded assemblies + + Show metadata tokens in base 10 - - Search Microsoft Docs... + + Show only public types and members - - Entity could not be resolved. Cannot analyze entities from missing assembly references. Add the missing reference and try again. + + Show state after this step - - Use throw expressions + + Show state before this step - - Use 'ref' extension methods + + Show _all types and members - - The directory is not empty. File will be overwritten. -Are you sure you want to continue? + + Show public, private and internal - - Project Directory not empty + + Show only _public types and members - - Highlight matching braces + + Size: - - Select language to decompile to + + Sort assembly list by name - - Select version of language to output + + Sort assembly _list by name - - Remove dead stores (use with caution!) + + Sort results by fitness - - Always show enum member values + + Stand by... - - Show only public types and members + + Status - - Show only _public types and members + + String Table - - Assembly + + Tab size: - - Decompile to new tab + + Toggle All Folding - - Decompile async IAsyncEnumerator methods + + Type - - Read-only methods + + No update for ILSpy found. - - Detect awaited using and foreach statements + + UseFieldSugar - - About + + UseLogicOperationSugar - - Assemblies + + Use tabs instead of spaces - - Close all documents + + You are using the latest release. - - Reset layout + + You are using a nightly build newer than the latest release. - - _Window + + Value - - Use pattern-based fixed statement + + Value (as string) - - Transform to do-while, if possible. + + Use variable names from debug symbols, if available - - Transform to for, if possible. + + Version - - Separate local variable declarations and initializers (int x = 5; -> int x; x = 5;), if possible. + + Version {0} is available. - - Reset to defaults + + View - - Do you really want to load the default settings for the active page? + + Visual Studio Solution file|*.sln|All files|*.* - - Select a list of assemblies + + Warning: This assembly is marked as 'reference assembly', which means that it only contains metadata and no executable code. - - Close + + Warning: Some assembly references could not be resolved automatically. This might lead to incorrect decompilation of some parts, - - C_lone + + Search for t:TypeName, m:Member or c:Constant; use exact match (=term), 'should not contain' (-term) or 'must contain' (+term); use /reg(ular)?Ex(pressions)?/ or both - t:/Type(Name)?/... - - Are you sure that you want to delete the selected assembly list? + + _Close all documents - - A list with the same name was found. + + _Reset layout - - Are you sure that you want to remove all assembly lists and recreate the default assembly lists? + + _About - - Introduce static local functions + + _Add To Main List - - Hide empty metadata tables from tree view + + _Check for Updates - - Navigation failed because the target is hidden or a compiler-generated class.\n -Please disable all filters that might hide the item (i.e. activate "View > Show internal types and members") and try again. + + _Collapse all tree nodes - - Add shell integration + + _File - - This will add "{0}" to the registry at "HKCU\Software\Classes\dllfile\shell\Open with ILSpy\command" and "HKCU\Software\Classes\exefile\shell\Open with ILSpy\command" to allow opening .dll and .exe files from the Windows Explorer context menu. - -Do you want to continue? + + _Help - - Remove shell integration + + _Load Dependencies - - This will remove "{0}" from the registry at "HKCU\Software\Classes\dllfile\shell\Open with ILSpy\command" and "HKCU\Software\Classes\exefile\shell\Open with ILSpy\command". - -Do you want to continue? + + _New - - Ranges + + _Open... - - Add preconfigured list... + + _Open Command Line Here - - R_ename + + _Open Containing Folder - - Rename list + + _Options... - - Culture + + _Reload - - New Tab + + _Remove - - Use new SDK style format for generated project files (*.csproj) + + _Remove Assemblies with load errors - - Use nint/nuint types + + _Reset - - Base Types + + Resources + + + _Save Code... + + + _Search: + + + _Search for: + + + _Show debug steps + + + Toggle Folding + + + _View + + + _Window \ No newline at end of file diff --git a/ILSpy/Properties/Resources.zh-Hans.resx b/ILSpy/Properties/Resources.zh-Hans.resx index 980fec4c7..5636a202e 100644 --- a/ILSpy/Properties/Resources.zh-Hans.resx +++ b/ILSpy/Properties/Resources.zh-Hans.resx @@ -117,622 +117,796 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 导航 + + 关于 + + + 添加预配置列表... + + + 添加文件资源管理器集成 + + + 这会在注册表的“HKCU\Software\Classes\dllfile\shell\Open with ILSpy\command”与“HKCU\Software\Classes\exefile\shell\Open with ILSpy\command”中添加“{0}”。这样在文件资源管理器中,.dll 文件与 .exe 文件可以在右键菜单中使用 ILSpy 打开。 + +确定继续? + + + |所有文件|*.* + + + 允许多个实例 + + + 始终使用大括号 + + + 分析 + + + 程序集 + + + 程序集 + + + 目录非空。文件会被覆盖。 +确定继续? + + + 项目目录非空 + + + 每周自动检查更新 后退 - - 前进 + + 基类型 - - 检查更新(_C) + + 克隆(_L) - - 帮助(_H) + + 取消 - - 文件(_F) + + 无法解析实体。可能是由于缺少程序集引用。请添加缺少的程序集并重试。 - - 打开 + + 无法创建为{0}创建PDB文件,因为它不包含PE调试目录类型 'CodeView'. - - DEBUG -- 反编译全部 + + 再次检查 - - 退出(_X) + + 检查更新 - - 退出 + + 检查... - - 保存 + + 关闭 - - 打开(_O)... + + 折叠所有树节点 - - 从_GAC中打开...... + + 复制 - - 打开列表(_L) + + 复制错误信息 - - 重新加载全部程序集 + + 复制完全限定名称 + + + 创建 + + + 区域 - DEBUG - 全部反编译 + 调试 -- 全部反编译到 C# - DEBUG - 反编译100x + 调试 -- 反编译100次 - - 生成可携带PDB + + 调试 -- 全部反编译到 IL - - 重新加载程序集 + + 调试 -- PDB 转储为 XML - - 重新加载(_R) + + 调试步骤 - - 移除程序集及其加载错误(_R) + + 调试此步骤 - - 移除 + + 反编译 - - 保存代码(_S) + + 在新选项卡反编译 - - 显示调试步骤(_S) + + 反编译器 - - 视图(_V) + + 在集合初始化器表达式中允许扩展 Add 方法 - - 按名称排列程序集列表(_L) + + 使用 ref 扩展方法 - - 视图 + + 始终强制转换显式接口实现调用的目标 - - 按名称排列程序集列表 + + 始终显示枚举数值 - - 折叠所有树节点(_C) + + 始终使用大括号 - - 折叠所有树节点 + + 在已加载的程序集上应用 Windows 运行时投影 - - 名称 + + 数组初始化器表达式 - - 值(为字符串) + + 反编译异步 IAsyncEnumerator 方法 - - 类型 + + 反编译 ?. 和 ?[] 运算符 - - 字符串表 + + 反编译匿名方法或 lambda - - + + 反编译匿名类型 - - 资源 + + 反编译异步方法 - - 下载 + + 反编译自动事件 - - 检查... + + 反编译自动属性 - - 检查更新 + + 反编译 catch/finally 内的 await - - 关于(_A) + + 反编译 C# 1.0“public unsafe fixed int arr[10];”成员 - - 加载中... + + 反编译 [DecimalConstant(...)] 作为简单的文本值 - - 引用 + + 反编译枚举器(yield return) - - ILSpy版本 + + 反编译表达树 - - 正在反编译... + + 反编译使用 dynamic 类型 - - 复制 + + 检测使用了 await 的 using 和 foreach 块 - - 编辑器 + + 检测 foreach 语句 - - 位置 + + 检测 lock 语句 - - 搜索(_S): + + 检测 switch 的字符串 - - 移除(_R) + + 检测元组比较 - - 加载依赖(_L) + + 检测 using 语句 - - 依赖(_L) + + Dictionary 初始值设定项表达式 - - 确定 + + 如果可能,转换到 do-while 块 - - 搜索 + + F# 特定选项 - - 状态 + + 如果可能,转换到 for 块 - - 字体 + + 在反编译代码中包括 XML 文档注释 - - 其他选项 + + 插入 using 声明 - - 显示行号 + + 引入局部函数(local functions) - - 取消 + + 引入静态局部函数(static local functions) - - 折叠 + + IsByRefLikeAttribute 应替换为结构上的 ref 修饰符 - - 添加到主列表(_A) + + IsReadOnlyAttribute 应替为结构参数上的 readonly/in 中的修饰符 - - 打开包含文件夹(_O) + + 类型参数上的 IsUnmanagedAttribute 应替换为 unmanaged 约束 - - Shell + + 使用 nint/nuint 类型 - - 在这里打开命令行(_O) + + 可空引用类型 - - 复制FQ名称 + + 对象或集合初始化器表达式 - - 复制错误信息 + + 其他 - - 使用调试符号中的变量名称 (如果可用) + + 范围(ranges) - - 显示调试符号的信息 (如果可用) + + 只读方法 - - 在反编译代码中显示 XML 文档 + + 删除死代码和无副作用的代码(请谨慎使用) + + + 删除死值(请谨慎使用) + + + 如果可能,删除可选参数 + + + 如果可能,分离局部变量的声明与初始化(int x = 5; -> int x; x = 5;) + + + 显示调试符号中的信息(如果可用) + + + 使用丢弃物 + + + 对仅获取属性使用表达式内部成员语法 + + + 使用扩展方法语法 + + + 在元组类型之间使用隐式转换 + + + 使用隐式方法组转换 + + + 使用 LINQ 表达式语法 + + + 如果可能, 请使用 lambda 语法 + + + 对空变量使用提升运算符 + + + 使用命名参数 + + + 使用非尾随命名参数 + + + 使用外部变量声明 + + + 使用基于模式的 fixed 语句 + + + 使用新的 SDK 格式(*.csproj)生成项目文件 + + + 使用 stackalloc 初始化器语法 + + + 使用字符串插值 + + + 使用 throw 表达式 + + + 使用元组类型语法 + + + 使用调试符号中的变量名(如果可用) + + + VB 特定选项 + + + 下面选择的设置将与语言下拉列表中的选择一起应用于反编译程序输出。在下拉列表中选择较低的语言版本将停用较高版本的所有选定选项。请注意, 某些设置隐式依赖于彼此, 例如: 如果不首先将静态调用转换为扩展方法调用, 则无法引入 LINQ 表达式。 + + + 正在反编译... + + + 依赖(_L) + + + 派生类型 + + + 显示 + + + 显示代码 + + + 字体: + + + 下载 + + + 退出(_X) + + + 编辑器 在大括号中的所有块上启用折叠 - - 删除死亡和副作用免费代码 + + 启用自动换行 - - 插入使用声明 + + 输入列表名称: - - 始终使用大括号 + + 退出 在反编译后展开成员定义 - - 显示元数据标记 + + 反编译后展开引用和声明 - - 启用自动换行 + + 折叠 - - 允许多个实例 + + 字体 - - 切换所有折叠 + + 前进 - - 派生类型 + + 生成可携带 PDB - - 资源文件 (*.resources)|*.resources|XML资源文件|*.resx + + 生成完成,耗时 {0} 秒。 - - 排序结果自适应 + + 已取消生成。 - - 重置(_R) + + 转至令牌 - - 删除(_D) + + 在树视图中隐藏空的元数据表 - - 打开(_O) + + 高亮配对的大括号 - - 创建(_C) + + ILSpyAboutPage_zh_Hans.txt - - 引用名称 + + ILSpy 版本 - - 版本 + + 有新的 ILSpy 版本已经可用。 - - 区域 + + 缩进长度: - - 公钥标记 + + 缩进 - - 打开列表 + + 插入使用声明 - - 选择一个列表: + + 是否删除选中的程序集列表? - - 搜索(_S): + + 已有同名的列表。 - - 从 GAC 打开 + + 是否删除所有程序集列表,并重建默认程序集列表? - - Nuget 包浏览器 + + 加载在最后一个实例中加载的程序集。 - - 新建列表 + + 加载中... - - 选择要打开的程序集: + + 位置 - - 创建 + + 管理程序集列表 - - 输入列表名称: + + 管理程序集列表(_L)... - - 分析 + + 杂项 - - 切换折叠 + + 名称 - - 全部选择 + + 导航 + + + 导航失败,因为目标是隐藏的或编译器生成的类。\n +请禁用所有的、可能隐藏它们的筛选器(例如,启用“视图 > 显示内部类型和成员”),并重试。 + + + 新建列表 + + + 新选项卡 + + + Nuget 包浏览器 + + + 确定 + + + 打开 打开资源管理器 - - |所有文件 |*.* + + 从 GAC 中打开 - - 保存代码 + + 从 GAC 中打开(_G)... - - 显示代码 + + 删除(_D) - - 搜索... + + 打开(_O) - - 搜索已中止, 发现超过1000个结果。 + + 选项 - - 搜索... + + 其他 - - 再次检查 + + 其他选项 - - 找不到 ILSpy 的更新。 + + 其他资源 - - 有新的 ILSpy 版本已经可用。 + + 可携带 PDB(*.pdb)|*.pdb|所有文件(*.*)|*.* - - 就绪... + + 例如属性 getter/setter 访问。要获得最佳反编译结果,请手动将缺少的引用添加到加载的程序集列表中。 - - 显示内部类型和成员 + + 公钥标记 - - 显示内部类型和成员(_I) + + 重命名(_E) - - 您使用的是每日构建版本,比最新版本更新。 + + 引用名称 - - 版本 {0} 已可用。 + + 引用 - - 您使用的是最新版本。 + + 重新加载程序集 - - 每周自动检查更新 + + 重新加载全部程序集 - - 搜索 t:TypeName, m:Member 或c:Constant; 使用完全匹配 (=term), '不应包含' (-term) 或 '必须包含' (+term); 使用 /reg(ular)?Ex(pressions)?/或两者 - t:/Type(Name)?/... + + 移除 - - 选项 + + 删除死代码和无副作用的代码 + + + 移除文件资源管理器集成 + + + 这会在注册表的“HKCU\Software\Classes\dllfile\shell\Open with ILSpy\command”与“HKCU\Software\Classes\exefile\shell\Open with ILSpy\command”中移除“{0}”。 + +确定继续? + + + 重命名列表 + + + 还原到默认值 + + + 是否为活动页面加载默认值? + + + 资源文件|*.resources|XML资源文件|*.resx - - 选项(_O)... + + 保存 - - 显示 + + 保存代码 - - 反编译器 + + 搜索... - - 杂项 + + 搜索已中止,发现超过1000个结果。 - - 字体: + + 搜索(Ctrl + Shift + F 或 Ctrl + E) - - 大小: + + 搜索 Microsoft Docs... - - 调试步骤 + + 搜索 - - UseFieldSugar + + 搜索... - - UseLogicOperationSugar + + 全部选择 - - ShowILRanges + + 选择要打开的程序集: - - ShowChildIndexInBlock + + 选择一个程序集列表 - - 在此步骤之前显示状态 + + 选择一个反编译目标语言 - - 在此步骤之后显示状态 + + 选择一个列表: - - 调试此步骤 + + 选择 PDB... - - 警告: 此程序集被标记为 "引用程序集", 这意味着它只包含元数据, 没有可执行代码。 + + 选择输出语言的版本 - - 警告: 某些程序集引用无法自动解析。这可能会导致某些部分反编译错误, + + Shell - - 比如属性getter/setter 访问。要获得最佳反编译结果, 请手动将缺少的引用添加到加载的程序集列表中。 + + 显示所有类型和成员 显示程序集加载日志 - - 其他资源 + + 在块中显示子索引 - - 使用Tab替代空格 + + 在反编译代码中显示 XML 文档 - - 在基数10中显示元数据标记 + + 显示 IL 范围 - - 反编译后展开引用和声明 + + 显示调试符号的信息(如果可用) - - 加载在最后一个实例中加载的程序集。 + + 显示内部类型和成员 - - 缩进 + + 显示行号 - - 缩进长度: + + 显示元数据标记 - - Tab长度: + + 使用十进制显示元数据标记 - - 搜索 (Ctrl + Shift + F 或 Ctrl + E) + + 只显示公有类型和成员 - - 显示所有类型和成员 + + 在此步骤之后显示状态 - - 其他 + + 在此步骤之前显示状态 显示所有类型和成员(_A) - - 下面选择的设置将与语言下拉列表中的选择一起应用于反编译程序输出。在下拉列表中选择较低的语言版本将停用较高版本的所有选定选项。请注意, 某些设置隐式依赖于彼此, 例如: 如果不首先将静态调用转换为扩展方法调用, 则无法引入 LINQ 表达式。 - - - 反编译枚举器 (yield return) - - - 反编译匿名方法或lambdas - - - 反编译匿名类型 + + 显示内部类型和成员(_I) - - 如果可能, 请使用 lambda 语法 + + 只显示公有类型和成员(_P) - - 反编译表达树 + + 大小: - - 反编译使用"dynamic" 类型 + + 按名称排列程序集列表 - - 反编译异步方法 + + 按名称排列程序集列表(_L) - - 反编译catch/finally内的await + + 排序结果自适应 - - 反编译 [DecimalConstant(...)] 作为简单的文本值 + + 就绪... - - 反编译 C# 1.0 "public unsafe fixed int arr[10];" 成员 + + 状态 - - 对空变量使用提升运算符 + + 字符串表 - - 反编译 ?. 和 ?[] 运算符 + + Tab 长度: - - 反编译自动属性 + + 切换所有折叠 - - 反编译自动事件 + + 类型 - - 检测using语句 + + 找不到 ILSpy 的更新。 - - 其他 + + 使用 fld 语法糖 - - 始终使用大括号 + + 使用 logic 语法糖 - - 检测 foreach语句 + + 使用 Tab 替代空格 - - 检测 lock语句 + + 您使用的是最新版本。 - - 检测switch 的字符串 + + 您使用的是每日构建版本,比最新版本更新。 - - 插入using声明 + + - - 使用扩展方法语法 + + 值(以字符串形式) - - 使用 LINQ 表达式语法 + + 使用调试符号中的变量名称(如果可用) - - 使用隐式方法组转换 + + 版本 - - 始终强制转换显式接口实现调用的目标 + + 版本 {0} 已可用。 - - 使用调试符号中的变量名 (如果可用) + + 视图 - - 数组初始化器表达式 + + Visual Studio 解决方案文件(*.sln)|*.sln|所有文件(*.*)|*.* - - 对象或集合初始化器表达式 + + 警告:此程序集被标记为“引用程序集”,这意味着它只包含元数据,没有可执行代码。 - - Dictionary初始值设定项表达式 + + 警告:某些程序集引用无法自动解析。这可能会导致某些部分反编译错误, - - 在集合初始化器表达式中允许扩展 "添加" 方法 + + 搜索 t:类型、m:成员 或 c:常量;使用完全匹配(=term)、不应包含(-term)或必须包含(+term);使用 /正(则)?表达(式)?/;或同时使用二者 - t:/类(型)?/... - - 使用字符串插值 + + 关闭所有文档(_C) - - 在反编译代码中包括 XML 文档注释 + + 重置布局(_R) - - 对仅获取属性使用表达式内部成员语法 + + 关于(_A) - - 使用外部变量声明 + + 添加到主列表(_A) - - 使用丢弃物 + + 检查更新(_C) - - IsByRefLikeAttribute应替换为结构上的 "ref" 修饰符 + + 折叠所有树节点(_C) - - IsReadOnlyAttribute 应替为结构参数上的 "readonly"/"in"中的修饰符 + + 文件(_F) - - 类型参数上的IsUnmanagedAttribute 应替换为 "非托管" 约束 + + 帮助(_H) - - 使用stackalloc 初始化器语法 + + 加载依赖(_L) - - 使用元组类型语法 + + 新建(_N) - - 在元组类型之间使用隐式转换 + + 打开(_O)... - - 检测元组比较 + + 在这里打开命令行(_O) - - 使用命名参数 + + 打开包含文件夹(_O) - - 使用非尾随命名参数 + + 选项(_O)... - - 如果可能, 删除可选参数 + + 重新加载(_R) - - 引入本地功能 + + 移除(_R) - - C# 7.0 本地函数未实现! + + 移除程序集及其加载错误(_R) - - 可空引用类型 + + 重置(_R) - - 显示调试符号中的信息 (如果可用) + + 资源 - - Vb 特定选项 + + 保存代码(_S)... - - F # 特定选项 + + 搜索(_S): - - 删除死的和副作用免费的代码 (请谨慎使用) + + 搜索(_S): - - 在已加载的程序集上应用 Windows 运行时投影 + + 显示调试步骤(_S) - - 搜索Microsoft Docs... + + 切换折叠 - - 关于 + + 视图(_V) - - 使用新的 SDK 格式 (*.csproj) 生成项目文件 + + 窗口(_W) \ No newline at end of file diff --git a/ILSpy/SolutionWriter.cs b/ILSpy/SolutionWriter.cs index 03ee32ab3..614076206 100644 --- a/ILSpy/SolutionWriter.cs +++ b/ILSpy/SolutionWriter.cs @@ -99,7 +99,12 @@ namespace ICSharpCode.ILSpy Stopwatch stopwatch = Stopwatch.StartNew(); try { - await Task.Run(() => Parallel.ForEach(assemblies, n => WriteProject(n, language, solutionDirectory, ct))) + // Explicitly create an enumerable partitioner here to avoid Parallel.ForEach's special cases for lists, + // as those seem to use static partitioning which is inefficient if assemblies take differently + // long to decompile. + await Task.Run(() => Parallel.ForEach(Partitioner.Create(assemblies), + new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, + n => WriteProject(n, language, solutionDirectory, ct))) .ConfigureAwait(false); await Task.Run(() => SolutionCreator.WriteSolutionFile(solutionFilePath, projects)) diff --git a/README.md b/README.md index f9aa68fc0..6b97ca8f6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ILSpy [![Join the chat at https://gitter.im/icsharpcode/ILSpy](https://badges.gitter.im/icsharpcode/ILSpy.svg)](https://gitter.im/icsharpcode/ILSpy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![NuGet](https://img.shields.io/nuget/v/ICSharpCode.Decompiler.svg)](https://nuget.org/packages/ICSharpCode.Decompiler) [![Build status](https://ci.appveyor.com/api/projects/status/imgec05g0wwv25ij/branch/master?svg=true)](https://ci.appveyor.com/project/icsharpcode/ilspy/branch/master) [![Twitter Follow](https://img.shields.io/twitter/follow/ILSpy.svg?label=Follow%20@ILSpy)](https://twitter.com/ilspy) [![ILSpy VS extension](https://img.shields.io/badge/VS%20Extension-ILSpy-blue.svg)](https://visualstudiogallery.msdn.microsoft.com/8ef1d688-f80c-4380-8004-2ec7f814e7de) [![Build Status](https://icsharpcode.visualstudio.com/icsharpcode-pipelines/_apis/build/status/icsharpcode.ILSpy?branchName=master)](https://icsharpcode.visualstudio.com/icsharpcode-pipelines/_build/latest?definitionId=1&branchName=master) +# ILSpy [![NuGet](https://img.shields.io/nuget/v/ICSharpCode.Decompiler.svg)](https://nuget.org/packages/ICSharpCode.Decompiler) [![Build status](https://ci.appveyor.com/api/projects/status/imgec05g0wwv25ij/branch/master?svg=true)](https://ci.appveyor.com/project/icsharpcode/ilspy/branch/master) [![Twitter Follow](https://img.shields.io/twitter/follow/ILSpy.svg?label=Follow%20@ILSpy)](https://twitter.com/ilspy) [![ILSpy VS extension](https://img.shields.io/badge/VS%20Extension-ILSpy-blue.svg)](https://visualstudiogallery.msdn.microsoft.com/8ef1d688-f80c-4380-8004-2ec7f814e7de) [![Build Status](https://icsharpcode.visualstudio.com/icsharpcode-pipelines/_apis/build/status/icsharpcode.ILSpy?branchName=master)](https://icsharpcode.visualstudio.com/icsharpcode-pipelines/_build/latest?definitionId=1&branchName=master) ILSpy is the open-source .NET assembly browser and decompiler. diff --git a/appveyor.yml b/appveyor.yml index 151bac6c2..5b3fdceef 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,7 +22,7 @@ build_script: - msbuild ILSpy.sln /v:minimal /p:ResolveNuGetPackages=false "/logger:%ProgramFiles%\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" after_build: -- 7z a ILSpy_binaries.zip %APPVEYOR_BUILD_FOLDER%\ILSpy\bin\%configuration%\net472\*.dll %APPVEYOR_BUILD_FOLDER%\ILSpy\bin\%configuration%\net472\*.exe %APPVEYOR_BUILD_FOLDER%\ILSpy\bin\%configuration%\net472\*.config %APPVEYOR_BUILD_FOLDER%\ILSpy\bin\%configuration%\net472\*\ILSpy.resources.dll +- 7z a ILSpy_binaries.zip %APPVEYOR_BUILD_FOLDER%\ILSpy\bin\%configuration%\net472\*.dll %APPVEYOR_BUILD_FOLDER%\ILSpy\bin\%configuration%\net472\*.exe %APPVEYOR_BUILD_FOLDER%\ILSpy\bin\%configuration%\net472\*.config %APPVEYOR_BUILD_FOLDER%\ILSpy\bin\%configuration%\net472\*\ILSpy.resources.dll %APPVEYOR_BUILD_FOLDER%\ILSpy\bin\%configuration%\net472\*\ILSpy.ReadyToRun.Plugin.resources.dll test_script: - vstest.console.exe /logger:Appveyor /Parallel "ICSharpCode.Decompiler.Tests\bin\%configuration%\net472\ICSharpCode.Decompiler.Tests.exe" "ILSpy.Tests\bin\%configuration%\net472\ILSpy.Tests.exe" "ILSpy.BamlDecompiler.Tests\bin\%configuration%\net472\ILSpy.BamlDecompiler.Tests.exe" diff --git a/doc/ILSpyAboutPage_zh_Hans.txt b/doc/ILSpyAboutPage_zh_Hans.txt new file mode 100644 index 000000000..9a9221be0 --- /dev/null +++ b/doc/ILSpyAboutPage_zh_Hans.txt @@ -0,0 +1,13 @@ +ILSpy 是开源的 .NET 程序集浏览器和反编译器。 + +网站: https://ilspy.net/ +反馈BUG: https://github.com/icsharpcode/ILSpy/issues/new/choose +中文翻译反馈:https://github.com/maikebing/ILSpy/issues + +Copyright 2011-2020 AlphaSierraPapa for the ILSpy team +当前和过去的贡献者: https://github.com/icsharpcode/ILSpy/graphs/contributors + +ILSpy 基于 MIT 许可证发行。 + +ILSpy 使用了其他开源库才能魔术般地实现,在此我们想感谢为那些组件付出的人们! +它们各自的许可和版权信息请查看第三方注意事项(third-party notices)。 \ No newline at end of file