From cf1d05042f789d6cfb0c6f7c6beb90c494d08396 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 12 Aug 2018 21:21:46 +0200 Subject: [PATCH] Add detection of local functions, so we do not hide the methods/display classes. --- .../CorrectnessTestRunner.cs | 6 + .../ICSharpCode.Decompiler.Tests.csproj | 1 + .../TestCases/Correctness/LocalFunctions.cs | 76 ++++++++++++ .../CSharp/CSharpDecompiler.cs | 14 ++- ICSharpCode.Decompiler/DecompilerSettings.cs | 20 +++- .../ICSharpCode.Decompiler.csproj | 1 + .../IL/Transforms/LocalFunctionDecompiler.cs | 113 ++++++++++++++++++ 7 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Correctness/LocalFunctions.cs create mode 100644 ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs diff --git a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs index 364180edc..ad3399bf9 100644 --- a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs @@ -283,6 +283,12 @@ namespace ICSharpCode.Decompiler.Tests RunCS(options: options); } + [Test] + public void LocalFunctions([ValueSource(nameof(roslynOnlyOptions))] CSharpCompilerOptions options) + { + RunCS(options: options); + } + void RunCS([CallerMemberName] string testName = null, CSharpCompilerOptions options = CSharpCompilerOptions.UseDebug) { string testFileName = testName + ".cs"; diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 3852f4955..ed47fa69c 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -68,6 +68,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/LocalFunctions.cs new file mode 100644 index 000000000..f05d95f27 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/LocalFunctions.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LocalFunctions +{ + class LocalFunctions + { + int field; + + public static void Main(string[] args) + { + StaticContextNoCapture(10); + StaticContextSimpleCapture(10); + StaticContextCaptureInForLoop(10); + var inst = new LocalFunctions() { field = 10 }; + inst.ContextNoCapture(); + inst.ContextSimpleCapture(); + inst.ContextCaptureInForLoop(); + } + + public static void StaticContextNoCapture(int length) + { + for (int i = 0; i < length; i++) { + LocalWrite("Hello " + i); + } + + void LocalWrite(string s) => Console.WriteLine(s); + } + + public static void StaticContextSimpleCapture(int length) + { + for (int i = 0; i < length; i++) { + LocalWrite(); + } + + void LocalWrite() => Console.WriteLine("Hello " + length); + } + + public static void StaticContextCaptureInForLoop(int length) + { + for (int i = 0; i < length; i++) { + void LocalWrite() => Console.WriteLine("Hello " + i + "/" + length); + LocalWrite(); + } + } + + public void ContextNoCapture() + { + for (int i = 0; i < field; i++) { + LocalWrite("Hello " + i); + } + + void LocalWrite(string s) => Console.WriteLine(s); + } + + public void ContextSimpleCapture() + { + for (int i = 0; i < field; i++) { + LocalWrite(); + } + + void LocalWrite() => Console.WriteLine("Hello " + field); + } + + public void ContextCaptureInForLoop() + { + for (int i = 0; i < field; i++) { + void LocalWrite() => Console.WriteLine("Hello " + i + "/" + field); + LocalWrite(); + } + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 7a5d0acfe..8e8a3f127 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -249,9 +249,11 @@ namespace ICSharpCode.Decompiler.CSharp var methodSemantics = module.MethodSemanticsLookup.GetSemantics(methodHandle).Item2; if (methodSemantics != 0 && methodSemantics != System.Reflection.MethodSemanticsAttributes.Other) return true; + if (LocalFunctionDecompiler.IsLocalFunctionMethod(module, methodHandle)) + return settings.LocalFunctions; if (settings.AnonymousMethods && methodHandle.HasGeneratedName(metadata) && methodHandle.IsCompilerGenerated(metadata)) return true; - if (settings.AsyncAwait && AsyncAwaitDecompiler.IsCompilerGeneratedMainMethod(module, (MethodDefinitionHandle)member)) + if (settings.AsyncAwait && AsyncAwaitDecompiler.IsCompilerGeneratedMainMethod(module, methodHandle)) return true; return false; case HandleKind.TypeDefinition: @@ -259,6 +261,8 @@ namespace ICSharpCode.Decompiler.CSharp var type = metadata.GetTypeDefinition(typeHandle); name = metadata.GetString(type.Name); if (!type.GetDeclaringType().IsNil) { + if (LocalFunctionDecompiler.IsLocalFunctionDisplayClass(module, typeHandle)) + return settings.LocalFunctions; if (settings.AnonymousMethods && IsClosureType(type, metadata)) return true; if (settings.YieldReturn && YieldReturnDecompiler.IsCompilerGeneratorEnumerator(typeHandle, metadata)) @@ -1070,6 +1074,14 @@ namespace ICSharpCode.Decompiler.CSharp } FixParameterNames(methodDecl); var methodDefinition = metadata.GetMethodDefinition((MethodDefinitionHandle)method.MetadataToken); + if (!settings.LocalFunctions && LocalFunctionDecompiler.IsLocalFunctionMethod(method.ParentModule.PEFile, (MethodDefinitionHandle)method.MetadataToken)) { + // if local functions are not active and we're dealing with a local function, + // reduce the visibility of the method to private, + // otherwise this leads to compile errors because the display classes have lesser accessibility. + // Note: removing and then adding the static modifier again is necessary to set the private modifier before all other modifiers. + methodDecl.Modifiers &= ~(Modifiers.Internal | Modifiers.Static); + methodDecl.Modifiers |= Modifiers.Private | (method.IsStatic ? Modifiers.Static : 0); + } if (methodDefinition.HasBody()) { DecompileBody(method, methodDecl, decompileRun, decompilationContext); } else if (!method.IsAbstract && method.DeclaringType.Kind != TypeKind.Interface) { diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 39c24387f..d7a19d80c 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -81,6 +81,7 @@ namespace ICSharpCode.Decompiler tupleTypes = false; tupleConversions = false; discards = false; + localFunctions = false; } if (languageVersion < CSharp.LanguageVersion.CSharp7_2) { introduceReadonlyAndInModifiers = false; @@ -100,7 +101,7 @@ namespace ICSharpCode.Decompiler if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers || nonTrailingNamedArguments) return CSharp.LanguageVersion.CSharp7_2; // C# 7.1 missing - if (outVariables || tupleTypes || tupleConversions || discards) + if (outVariables || tupleTypes || tupleConversions || discards || localFunctions) return CSharp.LanguageVersion.CSharp7; if (awaitInCatchFinally || useExpressionBodyForCalculatedGetterOnlyProperties || nullPropagation || stringInterpolation || dictionaryInitializers || extensionMethodsInCollectionInitializers) @@ -774,6 +775,23 @@ namespace ICSharpCode.Decompiler } } + bool localFunctions = false; + + /// + /// Gets/Sets whether C# 7.0 local functions should be used. + /// Note: this language feature is currenly not implemented and this setting is always false. + /// + public bool LocalFunctions { + get { return localFunctions; } + set { + if (localFunctions != value) { + throw new NotImplementedException("C# 7.0 local functions are not implemented!"); + //localFunctions = value; + //OnPropertyChanged(); + } + } + } + #region Options to aid VB decompilation bool assumeArrayLengthFitsIntoInt32 = true; diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index b85178a10..1c57853f4 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -267,6 +267,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs new file mode 100644 index 000000000..2b8304815 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Reflection; +using System.Reflection.Metadata; +using System.Text; +using System.Text.RegularExpressions; +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.Decompiler.Util; + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + class LocalFunctionDecompiler : IILTransform + { + public void Run(ILFunction function, ILTransformContext context) + { + throw new NotImplementedException(); + } + + public static bool IsLocalFunctionMethod(PEFile module, MethodDefinitionHandle methodHandle) + { + var metadata = module.Metadata; + var method = metadata.GetMethodDefinition(methodHandle); + + if ((method.Attributes & MethodAttributes.Assembly) == 0 || !method.IsCompilerGenerated(metadata)) + return false; + + if (!ParseLocalFunctionName(metadata.GetString(method.Name), out _, out _)) + return false; + + return true; + } + + public static bool IsLocalFunctionDisplayClass(PEFile module, TypeDefinitionHandle typeHandle) + { + var metadata = module.Metadata; + var type = metadata.GetTypeDefinition(typeHandle); + + if ((type.Attributes & TypeAttributes.NestedPrivate) == 0) + return false; + if (!type.HasGeneratedName(metadata)) + return false; + + var declaringTypeHandle = type.GetDeclaringType(); + var declaringType = metadata.GetTypeDefinition(declaringTypeHandle); + + foreach (var method in declaringType.GetMethods()) { + if (!IsLocalFunctionMethod(module, method)) + continue; + var md = metadata.GetMethodDefinition(method); + if (md.DecodeSignature(new FindTypeDecoder(typeHandle), default).ParameterTypes.Any()) + return true; + } + + return false; + } + + /// + /// Newer Roslyn versions use the format "<callerName>g__functionName|x_y" + /// Older versions use "<callerName>g__functionNamex_y" + /// + static readonly Regex functionNameRegex = new Regex(@"^<(.*)>g__(.*)\|{0,1}\d+_\d+$", RegexOptions.Compiled); + + static bool ParseLocalFunctionName(string name, out string callerName, out string functionName) + { + callerName = null; + functionName = null; + if (string.IsNullOrWhiteSpace(name)) + return false; + var match = functionNameRegex.Match(name); + callerName = match.Groups[1].Value; + functionName = match.Groups[2].Value; + return match.Success; + } + + struct FindTypeDecoder : ISignatureTypeProvider + { + TypeDefinitionHandle handle; + + public FindTypeDecoder(TypeDefinitionHandle handle) + { + this.handle = handle; + } + + public bool GetArrayType(bool elementType, ArrayShape shape) => elementType; + public bool GetByReferenceType(bool elementType) => elementType; + public bool GetFunctionPointerType(MethodSignature signature) => false; + public bool GetGenericInstantiation(bool genericType, ImmutableArray typeArguments) => genericType; + public bool GetGenericMethodParameter(Unit genericContext, int index) => false; + public bool GetGenericTypeParameter(Unit genericContext, int index) => false; + public bool GetModifiedType(bool modifier, bool unmodifiedType, bool isRequired) => unmodifiedType; + public bool GetPinnedType(bool elementType) => elementType; + public bool GetPointerType(bool elementType) => elementType; + public bool GetPrimitiveType(PrimitiveTypeCode typeCode) => false; + public bool GetSZArrayType(bool elementType) => false; + + public bool GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) + { + return this.handle == handle; + } + + public bool GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) + { + return false; + } + + public bool GetTypeFromSpecification(MetadataReader reader, Unit genericContext, TypeSpecificationHandle handle, byte rawTypeKind) + { + return reader.GetTypeSpecification(handle).DecodeSignature(this, genericContext); + } + } + } +}