<commit_before>﻿// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
using ICSharpCode.Decompiler.Metadata;
using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.Editor.CSharp.DecompiledSource
{
    internal class AssemblyResolver : IAssemblyResolver
    {
        private readonly Compilation parentCompilation;
        private static readonly Version zeroVersion = new Version(0, 0, 0, 0);

        public AssemblyResolver(Compilation parentCompilation)
        {
            this.parentCompilation = parentCompilation;
        }

        public PEFile Resolve(IAssemblyReference name)
        {
            foreach (var assembly in parentCompilation.GetReferencedAssemblySymbols())
            {
                // First, find the correct IAssemblySymbol by name and PublicKeyToken.
                if (assembly.Identity.Name != name.Name
                    || !assembly.Identity.PublicKeyToken.SequenceEqual(name.PublicKeyToken ?? Array.Empty<byte>()))
                {
                    continue;
                }

                // Normally we skip versions that do not match, except if the reference is "mscorlib" (see comments below)
                // or if the name.Version is '0.0.0.0'. This is because we require the metadata of all transitive references
                // and modules, to achieve best decompilation results.
                // In the case of .NET Standard projects for example, the 'netstandard' reference contains no references
                // with actual versions. All versions are '0.0.0.0', therefore we have to ignore those version numbers,
                // and can just use the references provided by Roslyn instead.
                if (assembly.Identity.Version != name.Version && name.Version != zeroVersion
                    && !string.Equals("mscorlib", assembly.Identity.Name, StringComparison.OrdinalIgnoreCase))
                {
                    // MSBuild treats mscorlib special for the purpose of assembly resolution/unification, where all
                    // versions of the assembly are considered equal. The same policy is adopted here.
                    continue;
                }

                // reference assemblies should be fine here, we only need the metadata of references.
                var reference = parentCompilation.GetMetadataReference(assembly);
                return new PEFile(reference.Display, PEStreamOptions.PrefetchMetadata);
            }

            // not found
            return null;
        }

        public PEFile ResolveModule(PEFile mainModule, string moduleName)
        {
            // Primitive implementation to support multi-module assemblies
            // where all modules are located next to the main module.
            var baseDirectory = Path.GetDirectoryName(mainModule.FileName);
            var moduleFileName = Path.Combine(baseDirectory, moduleName);
            if (!File.Exists(moduleFileName))
            {
                return null;
            }

            return new PEFile(moduleFileName, PEStreamOptions.PrefetchMetadata);
        }
    }

}<commit_msg>Improve decompilation results by improving DecompiledSource.AssemblyResolver: 1) group all references by short assembly name. 2) if there is only one assembly with a given short name, just use that assembly and ignore its version: This is necessary because assembly versions requested by the decompiler almost never match up with what Visual Studio provides, this is a particularly big problem with .NET core / standard. 3) if there are multiple versions of the same assembly, try to find an exact match and the highest version match.   * if there's an exact match, use it   * otherwise use the highest version match.<commit_after>﻿// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
using ICSharpCode.Decompiler.Metadata;
using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.Editor.CSharp.DecompiledSource
{
    internal class AssemblyResolver : IAssemblyResolver
    {
        private readonly Compilation parentCompilation;
        private readonly Dictionary<string, List<IAssemblySymbol>> cache = new Dictionary<string, List<IAssemblySymbol>>();

        public AssemblyResolver(Compilation parentCompilation)
        {
            this.parentCompilation = parentCompilation;
            BuildReferenceCache();
            Log("{0} items in cache", cache.Count);
        }

        public PEFile Resolve(IAssemblyReference name)
        {
            Log("------------------");
            Log("Resolve: {0}", name.FullName);

            // First, find the correct list of assemblies by name
            if (!cache.TryGetValue(name.Name, out var assemblies))
            {
                Log("Could not find by name: {0}", name.FullName);
                return null;
            }

            // If we have only one assembly available, just use it.
            // This is necessary, because in most cases there is only one assembly,
            // but still might have a version different from what the decompiler asks for.
            if (assemblies.Count == 1)
            {
                Log("Found single assembly: {0}", assemblies[0]);
                if (assemblies[0].Identity.Version != name.Version)
                {
                    Log("WARN: Version mismatch!");
                    Log("WARN: Expected: {0}, Got: {1}", name.Version, assemblies[0].Identity.Version);
                }
                return MakePEFile(assemblies[0]);
            }

            // There are multiple assemblies
            Log("Found {0} assemblies for {1}:", assemblies.Count, name.Name);

            // Get an exact match or highest version match from the list
            IAssemblySymbol highestVersion = null;
            IAssemblySymbol exactMatch = null;

            var publicKeyTokenOfName = name.PublicKeyToken ?? Array.Empty<byte>();

            foreach (var assembly in assemblies)
            {
                Log(assembly.Identity.GetDisplayName());
                var version = assembly.Identity.Version;
                var publicKeyToken = assembly.Identity.PublicKey;
                if (version == name.Version && publicKeyToken.SequenceEqual(publicKeyTokenOfName))
                {
                    exactMatch = assembly;
                    Log("Found exact match: {0}", assembly);
                }
                else if (highestVersion == null || highestVersion.Identity.Version < version)
                {
                    highestVersion = assembly;
                    Log("Found higher version match: {0}", assembly);
                }
            }

            var chosen = exactMatch ?? highestVersion;
            Log("Choosing version: {0}", chosen);
            return MakePEFile(chosen);

            PEFile MakePEFile(IAssemblySymbol assembly)
            {
                // reference assemblies should be fine here, we only need the metadata of references.
                var reference = parentCompilation.GetMetadataReference(assembly);
                return new PEFile(reference.Display, PEStreamOptions.PrefetchMetadata);
            }
        }

        public PEFile ResolveModule(PEFile mainModule, string moduleName)
        {
            Log("-------------");
            Log("ResolveModule: {0} of {1}", moduleName, mainModule.FullName);

            // Primitive implementation to support multi-module assemblies
            // where all modules are located next to the main module.
            var baseDirectory = Path.GetDirectoryName(mainModule.FileName);
            var moduleFileName = Path.Combine(baseDirectory, moduleName);
            if (!File.Exists(moduleFileName))
            {
                Log("Not found!");
                return null;
            }

            Log("Found {0}", moduleFileName);
            return new PEFile(moduleFileName, PEStreamOptions.PrefetchMetadata);
        }

        private void BuildReferenceCache()
        {
            foreach (var reference in parentCompilation.GetReferencedAssemblySymbols())
            {
                if (!cache.TryGetValue(reference.Identity.Name, out var list))
                {
                    list = new List<IAssemblySymbol>();
                    cache.Add(reference.Identity.Name, list);
                }

                list.Add(reference);
            }
        }

        [Conditional("DEBUG")]
        private static void Log(string format, params object[] args)
        {
            File.AppendAllText("C:\\temp\\ICSharpCode.Decompiler.VSAssemblyResolver.log", string.Format(format, args) + Environment.NewLine);
        }
    }
}
{'fraction_non_alphanumeric': 0.0812054794520548, 'fraction_numerical': 0.004164383561643835, 'mean_word_length': 2.047078464106845, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6078326', 'n_tokens_mistral': 2145, 'n_tokens_neox': 2019, 'n_words': 696}