File: InstructionDecoderTests.cs
Web Access
Project: ..\..\..\src\ExpressionEvaluator\CSharp\Test\ExpressionCompiler\Microsoft.CodeAnalysis.CSharp.ExpressionCompiler.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.ExpressionCompiler.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
using Microsoft.CodeAnalysis.ExpressionEvaluator;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.VisualStudio.Debugger.Evaluation;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.UnitTests
{
    public class InstructionDecoderTests : ExpressionCompilerTestBase
    {
        [Fact]
        public void GetNameGenerics()
        {
            var source = @"
using System;
class Class1<T>
{
    void M1<U>(Action<Int32> a)
    {
    }
    void M2<U>(Action<T> a)
    {
    }
    void M3<U>(Action<U> a)
    {
    }
}";
 
            Assert.Equal(
                "Class1<T>.M1<U>(System.Action<int> a)",
                GetName(source, "Class1.M1", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types));
 
            Assert.Equal(
                "Class1<T>.M2<U>(System.Action<T> a)",
                GetName(source, "Class1.M2", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types));
 
            Assert.Equal(
                "Class1<T>.M3<U>(System.Action<U> a)",
                GetName(source, "Class1.M3", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types));
 
            Assert.Equal(
                "Class1<string>.M1<decimal>(System.Action<int> a)",
                GetName(source, "Class1.M1", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, new[] { typeof(string), typeof(decimal) }));
 
            Assert.Equal(
                "Class1<string>.M2<decimal>(System.Action<string> a)",
                GetName(source, "Class1.M2", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, new[] { typeof(string), typeof(decimal) }));
 
            Assert.Equal(
                "Class1<string>.M3<decimal>(System.Action<decimal> a)",
                GetName(source, "Class1.M3", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, new[] { typeof(string), typeof(decimal) }));
        }
 
        [Fact]
        public void GetNameNullTypeArguments()
        {
            var source = @"
using System;
class Class1<T>
{
    void M<U>(Action<U> a)
    {
    }
}";
 
            Assert.Equal(
                "Class1<T>.M<U>(System.Action<U> a)",
                GetName(source, "Class1.M", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, typeArguments: new Type[] { null, null }));
 
            Assert.Equal(
                "Class1<T>.M<U>(System.Action<U> a)",
                GetName(source, "Class1.M", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, typeArguments: new[] { typeof(string), null }));
 
            Assert.Equal(
                "Class1<T>.M<U>(System.Action<U> a)",
                GetName(source, "Class1.M", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, typeArguments: new[] { null, typeof(decimal) }));
        }
 
        [Fact]
        public void GetNameGenericArgumentTypeNotInReferences()
        {
            var source = @"
class Class1
{
}";
 
            var serializedTypeArgumentName = "Class1, " + nameof(InstructionDecoderTests) + ", Culture=neutral, PublicKeyToken=null";
            Assert.Equal(
                "System.Collections.Generic.Comparer<Class1>.Create(System.Comparison<Class1> comparison)",
                GetName(source, "System.Collections.Generic.Comparer.Create", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, typeArguments: new[] { serializedTypeArgumentName }));
        }
 
        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1107977")]
        public void GetNameGenericAsync()
        {
            var source = @"
using System.Threading.Tasks;
class C
{
    static async Task<T> M<T>(T x)
    {
        await Task.Yield();
        return x;
    }
}";
 
            Assert.Equal(
                    "C.M<System.Exception>(System.Exception x)",
                    GetName(source, "C.<M>d__0.MoveNext", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, new[] { typeof(Exception) }));
        }
 
        [Fact]
        public void GetNameLambda()
        {
            var source = @"
using System;
class C
{
    void M()
    {
        Func<int> f = () => 3;
    }
}";
 
            Assert.Equal(
                "C.M.AnonymousMethod__0_0()",
                GetName(source, "C.<>c.<M>b__0_0", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types));
        }
 
        [Fact]
        public void GetNameGenericLambda()
        {
            var source = @"
using System;
class C<T>
{
    void M<U>() where U : T
    {
        Func<U, T> f = (U u) => u;
    }
}";
 
            Assert.Equal(
                "C<System.Exception>.M.AnonymousMethod__0_0(System.ArgumentException u)",
                GetName(source, "C.<>c__0.<M>b__0_0", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types, new[] { typeof(Exception), typeof(ArgumentException) }));
        }
 
        [Fact]
        public void GetNameProperties()
        {
            var source = @"
class C
{
    int P { get; set; }
    int this[object x]
    {
        get { return 42; }
        set { }
    }
}";
 
            Assert.Equal(
                "C.P.get()",
                GetName(source, "C.get_P", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types));
 
            Assert.Equal(
                "C.P.set(int value)",
                GetName(source, "C.set_P", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types));
 
            Assert.Equal(
                "C.this[object].get(object x)",
                GetName(source, "C.get_Item", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types));
 
            Assert.Equal(
                "C.this[object].set(object x, int value)",
                GetName(source, "C.set_Item", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types));
        }
 
        [Fact]
        public void GetNameExplicitInterfaceImplementation()
        {
            var source = @"
using System;
class C : IDisposable
{
    void IDisposable.Dispose() { }
}";
 
            Assert.Equal(
                "C.System.IDisposable.Dispose()",
                GetName(source, "C.System.IDisposable.Dispose", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types));
        }
 
        [Fact]
        public void GetNameExtensionMethod()
        {
            var source = @"
static class Extensions
{
    static void M(this string @this) { }
}";
 
            Assert.Equal(
                "Extensions.M(string this)",
                GetName(source, "Extensions.M", DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types));
        }
 
        [Fact]
        public void GetNameArgumentFlagsNone()
        {
            var source = @"
static class C
{
    static void M1() { }
    static void M2(int x, int y) { }
}";
 
            Assert.Equal(
                "C.M1",
                GetName(source, "C.M1", DkmVariableInfoFlags.None));
 
            Assert.Equal(
                "C.M2",
                GetName(source, "C.M2", DkmVariableInfoFlags.None));
        }
 
        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1107978")]
        public void GetNameRefAndOutParameters()
        {
            var source = @"
class C
{
    static void M(ref int x, out int y)
    {
        y = x;
    }
}";
 
            Assert.Equal(
                "C.M",
                GetName(source, "C.M", DkmVariableInfoFlags.None));
 
            Assert.Equal(
                "C.M(1, 2)",
                GetName(source, "C.M", DkmVariableInfoFlags.None, argumentValues: new[] { "1", "2" }));
 
            Assert.Equal(
                "C.M(ref int, out int)",
                GetName(source, "C.M", DkmVariableInfoFlags.Types));
 
            Assert.Equal(
                "C.M(x, y)",
                GetName(source, "C.M", DkmVariableInfoFlags.Names));
 
            Assert.Equal(
                "C.M(ref int x, out int y)",
                GetName(source, "C.M", DkmVariableInfoFlags.Types | DkmVariableInfoFlags.Names));
        }
 
        [Fact]
        public void GetNameParamsParameters()
        {
            var source = @"
class C
{
    static void M(params int[] x)
    {
    }
}";
 
            Assert.Equal(
                "C.M(int[] x)",
                GetName(source, "C.M", DkmVariableInfoFlags.Types | DkmVariableInfoFlags.Names));
        }
 
        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1154945")]
        public void GetNameIncorrectNumberOfArgumentValues()
        {
            var source = @"
class C
{
    void M(int x, int y)
    {
    }
}";
            var expected = "C.M(int x, int y)";
 
            Assert.Equal(expected,
                GetName(source, "C.M", DkmVariableInfoFlags.Types | DkmVariableInfoFlags.Names, argumentValues: new string[] { }));
 
            Assert.Equal(expected,
                GetName(source, "C.M", DkmVariableInfoFlags.Types | DkmVariableInfoFlags.Names, argumentValues: new string[] { "1" }));
 
            Assert.Equal(expected,
                GetName(source, "C.M", DkmVariableInfoFlags.Types | DkmVariableInfoFlags.Names, argumentValues: new string[] { "1", "2", "3" }));
        }
 
        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1134081")]
        public void GetFileNameWithoutExtension()
        {
            Assert.Equal(".", MetadataUtilities.GetFileNameWithoutExtension("."));
            Assert.Equal(".a", MetadataUtilities.GetFileNameWithoutExtension(".a"));
            Assert.Equal("a.", MetadataUtilities.GetFileNameWithoutExtension("a."));
            Assert.Equal(".dll.", MetadataUtilities.GetFileNameWithoutExtension(".dll."));
            Assert.Equal("a.b", MetadataUtilities.GetFileNameWithoutExtension("a.b"));
            Assert.Equal("a", MetadataUtilities.GetFileNameWithoutExtension("a.dll"));
            Assert.Equal("a", MetadataUtilities.GetFileNameWithoutExtension("a.exe"));
            Assert.Equal("a", MetadataUtilities.GetFileNameWithoutExtension("a.netmodule"));
            Assert.Equal("a", MetadataUtilities.GetFileNameWithoutExtension("a.winmd"));
            Assert.Equal("a.b.c", MetadataUtilities.GetFileNameWithoutExtension("a.b.c"));
            Assert.Equal("a.b.c", MetadataUtilities.GetFileNameWithoutExtension("a.b.c.dll"));
            Assert.Equal("mscorlib.nlp", MetadataUtilities.GetFileNameWithoutExtension("mscorlib.nlp"));
            Assert.Equal("Microsoft.CodeAnalysis", MetadataUtilities.GetFileNameWithoutExtension("Microsoft.CodeAnalysis"));
            Assert.Equal("Microsoft.CodeAnalysis", MetadataUtilities.GetFileNameWithoutExtension("Microsoft.CodeAnalysis.dll"));
        }
 
        [Fact]
        public void GetReturnTypeNamePrimitive()
        {
            var source = @"
static class C
{
    static uint M1() { return 42; }
}";
 
            Assert.Equal("uint", GetReturnTypeName(source, "C.M1"));
        }
 
        [Fact]
        public void GetReturnTypeNameNested()
        {
            var source = @"
static class C
{
    static N.D.E M1() { return default(N.D.E); }
}
namespace N
{
    class D
    {
        internal struct E
        {
        }
    }
}";
 
            Assert.Equal("N.D.E", GetReturnTypeName(source, "C.M1"));
        }
 
        [Fact]
        public void GetReturnTypeNameGenericOfPrimitive()
        {
            var source = @"
using System;
class C
{
    Action<Int32> M1() { return null; }
}";
 
            Assert.Equal("System.Action<int>", GetReturnTypeName(source, "C.M1"));
        }
 
        [Fact]
        public void GetReturnTypeNameGenericOfNested()
        {
            var source = @"
using System;
class C
{
    Action<D> M1() { return null; }
    class D
    {
    }
}";
 
            Assert.Equal("System.Action<C.D>", GetReturnTypeName(source, "C.M1"));
        }
 
        [Fact]
        public void GetReturnTypeNameGenericOfGeneric()
        {
            var source = @"
using System;
class C
{
    Action<Func<T>> M1<T>() { return null; }
}";
 
            Assert.Equal("System.Action<System.Func<object>>", GetReturnTypeName(source, "C.M1", new[] { typeof(object) }));
        }
 
        private string GetName(string source, string methodName, DkmVariableInfoFlags argumentFlags, Type[] typeArguments = null, string[] argumentValues = null)
        {
            var serializedTypeArgumentNames = typeArguments?.Select(t => t?.AssemblyQualifiedName).ToArray();
            return GetName(source, methodName, argumentFlags, serializedTypeArgumentNames, argumentValues);
        }
 
        private string GetName(string source, string methodName, DkmVariableInfoFlags argumentFlags, string[] typeArguments, string[] argumentValues = null)
        {
            Debug.Assert((argumentFlags & (DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types)) == argumentFlags,
                "Unexpected argumentFlags", "argumentFlags = {0}", argumentFlags);
 
            var instructionDecoder = CSharpInstructionDecoder.Instance;
            var method = GetConstructedMethod(source, methodName, typeArguments, instructionDecoder);
 
            var includeParameterTypes = argumentFlags.Includes(DkmVariableInfoFlags.Types);
            var includeParameterNames = argumentFlags.Includes(DkmVariableInfoFlags.Names);
            ArrayBuilder<string> builder = null;
            if (argumentValues != null)
            {
                builder = ArrayBuilder<string>.GetInstance();
                builder.AddRange(argumentValues);
            }
 
            var name = instructionDecoder.GetName(method, includeParameterTypes, includeParameterNames, builder);
            builder?.Free();
 
            return name;
        }
 
        private string GetReturnTypeName(string source, string methodName, Type[] typeArguments = null)
        {
            var instructionDecoder = CSharpInstructionDecoder.Instance;
            var serializedTypeArgumentNames = typeArguments?.Select(t => t?.AssemblyQualifiedName).ToArray();
            var method = GetConstructedMethod(source, methodName, serializedTypeArgumentNames, instructionDecoder);
 
            return instructionDecoder.GetReturnTypeName(method);
        }
 
        private MethodSymbol GetConstructedMethod(string source, string methodName, string[] serializedTypeArgumentNames, CSharpInstructionDecoder instructionDecoder)
        {
            var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugDll, assemblyName: nameof(InstructionDecoderTests));
            var runtime = CreateRuntimeInstance(compilation);
            var moduleInstances = runtime.Modules;
            var blocks = moduleInstances.SelectAsArray(m => m.MetadataBlock);
            compilation = blocks.ToCompilation(default(Guid), MakeAssemblyReferencesKind.AllAssemblies);
            var frame = (PEMethodSymbol)GetMethodOrTypeBySignature(compilation, methodName);
 
            // Once we have the method token, we want to look up the method (again)
            // using the same helper as the product code.  This helper will also map
            // async/iterator "MoveNext" methods to the original source method.
            MethodSymbol method = compilation.GetSourceMethod(
                ((PEModuleSymbol)frame.ContainingModule).Module.GetModuleVersionIdOrThrow(),
                frame.Handle);
            if (serializedTypeArgumentNames != null)
            {
                Assert.NotEmpty(serializedTypeArgumentNames);
                var typeParameters = instructionDecoder.GetAllTypeParameters(method);
                Assert.NotEmpty(typeParameters);
                // Use the same helper method as the FrameDecoder to get the TypeSymbols for the
                // generic type arguments (rather than using EETypeNameDecoder directly).
                var typeArguments = instructionDecoder.GetTypeSymbols(compilation, method, serializedTypeArgumentNames);
                if (!typeArguments.IsEmpty)
                {
                    method = instructionDecoder.ConstructMethod(method, typeParameters, typeArguments);
                }
            }
 
            return method;
        }
    }
}