File: ExpressionCompilerTestBase.vb
Web Access
Project: ..\..\..\src\ExpressionEvaluator\VisualBasic\Test\ExpressionCompiler\Microsoft.CodeAnalysis.VisualBasic.ExpressionCompiler.UnitTests.vbproj (Microsoft.CodeAnalysis.VisualBasic.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.
 
Imports System.Collections.Immutable
Imports System.Reflection.Metadata
Imports System.Reflection.Metadata.Ecma335
Imports System.Runtime.CompilerServices
Imports System.Runtime.InteropServices
Imports System.Xml.Linq
Imports Microsoft.CodeAnalysis.CodeGen
Imports Microsoft.CodeAnalysis.Emit
Imports Microsoft.CodeAnalysis.ExpressionEvaluator
Imports Microsoft.CodeAnalysis.ExpressionEvaluator.UnitTests
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE
Imports Microsoft.CodeAnalysis.VisualBasic.UnitTests
Imports Microsoft.DiaSymReader
Imports Microsoft.VisualStudio.Debugger.Clr
Imports Microsoft.VisualStudio.Debugger.Evaluation
Imports Microsoft.VisualStudio.Debugger.Evaluation.ClrCompilation
Imports Roslyn.Test.Utilities
Imports Roslyn.Utilities
Imports Xunit
 
Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator.UnitTests
    Public MustInherit Class ExpressionCompilerTestBase
        Inherits BasicTestBase
        Implements IDisposable
 
        Private ReadOnly _runtimeInstances As ArrayBuilder(Of IDisposable) = ArrayBuilder(Of IDisposable).GetInstance()
 
        Friend Shared ReadOnly NoAliases As ImmutableArray(Of [Alias]) = ImmutableArray(Of [Alias]).Empty
 
        Protected Sub New()
            ' We never want to swallow Exceptions (generate a non-fatal Watson) when running tests.
            ExpressionEvaluatorFatalError.IsFailFastEnabled = True
        End Sub
 
        Public Overrides Sub Dispose()
            MyBase.Dispose()
 
            For Each instance In _runtimeInstances
                instance.Dispose()
            Next
            _runtimeInstances.Free()
        End Sub
 
        Friend Shared Sub WithRuntimeInstance(compilation As Compilation, validator As Action(Of RuntimeInstance))
            WithRuntimeInstance(compilation, Nothing, validator)
        End Sub
 
        Friend Shared Sub WithRuntimeInstance(compilation As Compilation, references As IEnumerable(Of MetadataReference), validator As Action(Of RuntimeInstance))
            WithRuntimeInstance(compilation, references, includeLocalSignatures:=True, includeIntrinsicAssembly:=True, validator:=validator)
        End Sub
 
        Friend Shared Sub WithRuntimeInstance(
            compilation As Compilation,
            references As IEnumerable(Of MetadataReference),
            includeLocalSignatures As Boolean,
            includeIntrinsicAssembly As Boolean,
            validator As Action(Of RuntimeInstance))
            For Each debugFormat In {DebugInformationFormat.Pdb, DebugInformationFormat.PortablePdb}
                Using instance = RuntimeInstance.Create(compilation, references, debugFormat, includeLocalSignatures, includeIntrinsicAssembly)
                    validator(instance)
                End Using
            Next
        End Sub
 
        Friend Function CreateRuntimeInstance(modules As IEnumerable(Of ModuleInstance)) As RuntimeInstance
            Dim instance = RuntimeInstance.Create(modules)
            _runtimeInstances.Add(instance)
            Return instance
        End Function
 
        Friend Function CreateRuntimeInstance(
            compilation As Compilation,
            Optional references As IEnumerable(Of MetadataReference) = Nothing,
            Optional debugFormat As DebugInformationFormat = DebugInformationFormat.Pdb,
            Optional includeLocalSignatures As Boolean = True) As RuntimeInstance
 
            Dim instance = RuntimeInstance.Create(compilation, references, debugFormat, includeLocalSignatures, includeIntrinsicAssembly:=True)
            _runtimeInstances.Add(instance)
            Return instance
        End Function
 
        Friend Function CreateRuntimeInstance(
            [module] As ModuleInstance,
            Optional references As IEnumerable(Of MetadataReference) = Nothing) As RuntimeInstance
 
            Dim instance = RuntimeInstance.Create([module], references, DebugInformationFormat.Pdb)
            _runtimeInstances.Add(instance)
            Return instance
        End Function
 
        Friend Shared Function GetContextState(
            runtime As RuntimeInstance,
            methodName As String) As (ModuleVersionId As Guid, SymReader As ISymUnmanagedReader, MethodToken As Integer, LocalSignatureToken As Integer, ILOffset As UInteger)
 
            Dim blocks As ImmutableArray(Of MetadataBlock) = Nothing
            Dim moduleVersionId As Guid = Nothing
            Dim symReader As ISymUnmanagedReader = Nothing
            Dim methodToken As Integer
            Dim localSignatureToken As Integer
            GetContextState(runtime, methodName, blocks, moduleVersionId, symReader, methodToken, localSignatureToken)
            Dim ilOffset = ExpressionCompilerTestHelpers.GetOffset(methodToken, symReader)
            Return (moduleVersionId, symReader, methodToken, localSignatureToken, ilOffset)
        End Function
 
        Friend Shared Sub GetContextState(
            runtime As RuntimeInstance,
            methodOrTypeName As String,
            <Out> ByRef blocks As ImmutableArray(Of MetadataBlock),
            <Out> ByRef moduleVersionId As Guid,
            <Out> ByRef symReader As ISymUnmanagedReader,
            <Out> ByRef methodOrTypeToken As Integer,
            <Out> ByRef localSignatureToken As Integer)
 
            Dim moduleInstances = runtime.Modules
            blocks = moduleInstances.SelectAsArray(Function(m) m.MetadataBlock)
 
            Dim compilation = blocks.ToCompilation()
            Dim methodOrType = GetMethodOrTypeBySignature(compilation, methodOrTypeName)
            Dim [module] = DirectCast(methodOrType.ContainingModule, PEModuleSymbol)
            Dim id = [module].Module.GetModuleVersionIdOrThrow()
            Dim moduleInstance = moduleInstances.First(Function(m) m.ModuleVersionId = id)
 
            moduleVersionId = id
            symReader = DirectCast(moduleInstance.SymReader, ISymUnmanagedReader)
 
            Dim methodOrTypeHandle As EntityHandle
            If methodOrType.Kind = SymbolKind.Method Then
                methodOrTypeHandle = DirectCast(methodOrType, PEMethodSymbol).Handle
                localSignatureToken = moduleInstance.GetLocalSignatureToken(CType(methodOrTypeHandle, MethodDefinitionHandle))
            Else
                methodOrTypeHandle = DirectCast(methodOrType, PENamedTypeSymbol).Handle
                localSignatureToken = -1
            End If
 
            Dim reader As MetadataReader = Nothing ' Nothing should be okay.
            methodOrTypeToken = reader.GetToken(methodOrTypeHandle)
        End Sub
 
        Friend NotInheritable Class AppDomain
            Private _metadataContext As MetadataContext(Of VisualBasicMetadataContext)
 
            Friend Function GetMetadataContext() As MetadataContext(Of VisualBasicMetadataContext)
                Return _metadataContext
            End Function
 
            Friend Sub SetMetadataContext(metadataContext As MetadataContext(Of VisualBasicMetadataContext))
                _metadataContext = metadataContext
            End Sub
 
            Friend Sub RemoveMetadataContext()
                _metadataContext = Nothing
            End Sub
        End Class
 
        Friend Shared Function CreateTypeContext(
            appDomain As AppDomain,
            metadataBlocks As ImmutableArray(Of MetadataBlock),
            moduleVersionId As Guid,
            typeToken As Integer,
            kind As MakeAssemblyReferencesKind) As EvaluationContext
 
            Return VisualBasicExpressionCompiler.CreateTypeContextHelper(
                appDomain,
                Function(ad) ad.GetMetadataContext(),
                metadataBlocks,
                moduleVersionId,
                typeToken,
                kind)
        End Function
 
        Friend Shared Function CreateMethodContext(
            appDomain As AppDomain,
            metadataBlocks As ImmutableArray(Of MetadataBlock),
            lazyAssemblyReaders As Lazy(Of ImmutableArray(Of AssemblyReaders)),
            symReader As Object,
            moduleVersionId As Guid,
            methodToken As Integer,
            methodVersion As Integer,
            ilOffset As UInteger,
            localSignatureToken As Integer,
            kind As MakeAssemblyReferencesKind) As EvaluationContext
 
            Return VisualBasicExpressionCompiler.CreateMethodContextHelper(
                appDomain,
                Function(ad) ad.GetMetadataContext(),
                Sub(ad, mc, report) ad.SetMetadataContext(mc),
                metadataBlocks,
                lazyAssemblyReaders,
                symReader,
                moduleVersionId,
                methodToken,
                methodVersion,
                ilOffset,
                localSignatureToken,
                kind)
        End Function
 
        Friend Shared Function CreateMethodContext(
            appDomain As AppDomain,
            blocks As ImmutableArray(Of MetadataBlock),
            state As (ModuleVersionId As Guid, SymReader As ISymUnmanagedReader, MethodToken As Integer, LocalSignatureToken As Integer, ILOffset As UInteger)) As EvaluationContext
 
            Return CreateMethodContext(
                appDomain,
                blocks,
                MakeDummyLazyAssemblyReaders(),
                state.SymReader,
                state.ModuleVersionId,
                state.MethodToken,
                methodVersion:=1,
                state.ILOffset,
                state.LocalSignatureToken,
                MakeAssemblyReferencesKind.AllReferences)
        End Function
 
        Friend Shared Function GetMetadataContext(appDomainContext As MetadataContext(Of VisualBasicMetadataContext), Optional mvid As Guid = Nothing) As VisualBasicMetadataContext
            Dim assemblyContexts = appDomainContext.AssemblyContexts
            If assemblyContexts Is Nothing Then
                Return Nothing
            End If
            Dim context As VisualBasicMetadataContext = Nothing
            assemblyContexts.TryGetValue(New MetadataContextId(mvid), context)
            Return context
        End Function
 
        Friend Shared Function SetMetadataContext(appDomainContext As MetadataContext(Of VisualBasicMetadataContext), context As VisualBasicMetadataContext) As MetadataContext(Of VisualBasicMetadataContext)
            Return New MetadataContext(Of VisualBasicMetadataContext)(
                appDomainContext.MetadataBlocks,
                appDomainContext.AssemblyContexts.SetItem(Nothing, context))
        End Function
 
        Friend Shared Function CreateMethodContext(
            runtime As RuntimeInstance,
            methodName As String,
            Optional atLineNumber As Integer = -1,
            Optional lazyAssemblyReaders As Lazy(Of ImmutableArray(Of AssemblyReaders)) = Nothing) As EvaluationContext
 
            Dim blocks As ImmutableArray(Of MetadataBlock) = Nothing
            Dim moduleVersionId As Guid = Nothing
            Dim symReader As ISymUnmanagedReader = Nothing
            Dim methodToken = 0
            Dim localSignatureToken = 0
            GetContextState(runtime, methodName, blocks, moduleVersionId, symReader, methodToken, localSignatureToken)
            Const methodVersion = 1
 
            Dim ilOffset = ExpressionCompilerTestHelpers.GetOffset(methodToken, symReader, atLineNumber)
 
            Return CreateMethodContext(
                New AppDomain(),
                blocks,
                If(lazyAssemblyReaders, MakeDummyLazyAssemblyReaders()),
                symReader,
                moduleVersionId,
                methodToken,
                methodVersion,
                ilOffset,
                localSignatureToken,
                MakeAssemblyReferencesKind.AllAssemblies)
        End Function
 
        Friend Shared Function MakeDummyLazyAssemblyReaders() As Lazy(Of ImmutableArray(Of AssemblyReaders))
            Dim f As Func(Of ImmutableArray(Of AssemblyReaders)) =
                Function()
                    ' The vast majority of tests should not trigger evaluation of the Lazy.
                    Throw ExceptionUtilities.Unreachable
                End Function
            Return New Lazy(Of ImmutableArray(Of AssemblyReaders))(f)
        End Function
 
        Friend Shared Function CreateTypeContext(
            runtime As RuntimeInstance,
            typeName As String) As EvaluationContext
 
            Dim blocks As ImmutableArray(Of MetadataBlock) = Nothing
            Dim moduleVersionId As Guid = Nothing
            Dim symReader As ISymUnmanagedReader = Nothing
            Dim typeToken = 0
            Dim localSignatureToken = 0
            GetContextState(runtime, typeName, blocks, moduleVersionId, symReader, typeToken, localSignatureToken)
            Return VisualBasicExpressionCompiler.CreateTypeContextHelper(
                New AppDomain(),
                Function(ad) ad.GetMetadataContext(),
                blocks,
                moduleVersionId,
                typeToken,
                MakeAssemblyReferencesKind.AllAssemblies)
        End Function
 
        Friend Function Evaluate(
            source As String,
            outputKind As OutputKind,
            methodName As String,
            expr As String,
            Optional atLineNumber As Integer = -1,
            Optional includeSymbols As Boolean = True) As CompilationTestData
 
            Dim resultProperties As ResultProperties = Nothing
            Dim errorMessage As String = Nothing
            Dim result = Evaluate(source, outputKind, methodName, expr, resultProperties, errorMessage, atLineNumber, includeSymbols)
            Assert.Null(errorMessage)
            Return result
        End Function
 
        Friend Function Evaluate(
            source As String,
            outputKind As OutputKind,
            methodName As String,
            expr As String,
            <Out> ByRef resultProperties As ResultProperties,
            <Out> ByRef errorMessage As String,
            Optional atLineNumber As Integer = -1,
            Optional includeSymbols As Boolean = True) As CompilationTestData
 
            Dim compilation0 = CreateEmptyCompilationWithReferences(
                {Parse(source, SyntaxHelpers.ParseOptions)},
                {MscorlibRef_v4_0_30316_17626, SystemRef, MsvbRef},
                options:=If(outputKind = OutputKind.DynamicallyLinkedLibrary, TestOptions.DebugDll, TestOptions.DebugExe))
 
            Dim runtime = CreateRuntimeInstance(compilation0, debugFormat:=If(includeSymbols, DebugInformationFormat.Pdb, Nothing))
            Dim context = CreateMethodContext(runtime, methodName, atLineNumber)
            Dim testData = New CompilationTestData()
            Dim missingAssemblyIdentities As ImmutableArray(Of AssemblyIdentity) = Nothing
            Dim result = context.CompileExpression(
                    expr,
                    DkmEvaluationFlags.TreatAsExpression,
                    NoAliases,
                    DebuggerDiagnosticFormatter.Instance,
                    resultProperties,
                    errorMessage,
                    missingAssemblyIdentities,
                    EnsureEnglishUICulture.PreferredOrNull,
                    testData)
            Assert.Empty(missingAssemblyIdentities)
            Return testData
        End Function
 
        ''' <summary>
        ''' Verify all type parameters from the method are from that method or containing types.
        ''' </summary>
        Friend Shared Sub VerifyTypeParameters(method As Symbols.MethodSymbol)
            Assert.True(method.IsContainingSymbolOfAllTypeParameters(method.ReturnType))
            AssertEx.All(method.TypeParameters, Function(typeParameter) method.IsContainingSymbolOfAllTypeParameters(typeParameter))
            AssertEx.All(method.TypeArguments, Function(typeArgument) method.IsContainingSymbolOfAllTypeParameters(typeArgument))
            AssertEx.All(method.Parameters, Function(parameter) method.IsContainingSymbolOfAllTypeParameters(parameter.Type))
            VerifyTypeParameters(method.ContainingType)
        End Sub
 
        ''' <summary>
        ''' Verify all type parameters from the type are from that type or containing types.
        ''' </summary>
        Friend Shared Sub VerifyTypeParameters(type As Symbols.NamedTypeSymbol)
            AssertEx.All(type.TypeParameters, Function(typeParameter) type.IsContainingSymbolOfAllTypeParameters(typeParameter))
            AssertEx.All(type.TypeArguments, Function(typeArgument) type.IsContainingSymbolOfAllTypeParameters(typeArgument))
            Dim container = type.ContainingType
            If container IsNot Nothing Then
                VerifyTypeParameters(container)
            End If
        End Sub
 
        Friend Shared Function MakeSources(source As String, Optional assemblyName As String = Nothing) As XElement
            Return <compilation name=<%= If(assemblyName, ExpressionCompilerUtilities.GenerateUniqueName()) %>>
                       <file name="a.vb">
                           <%= source %>
                       </file>
                   </compilation>
        End Function
 
        Friend Shared Function GetAllXmlReferences() As ImmutableArray(Of MetadataReference)
            Dim builder = ArrayBuilder(Of MetadataReference).GetInstance()
            builder.Add(MscorlibRef)
            builder.Add(MsvbRef)
            builder.AddRange(XmlReferences)
            Return builder.ToImmutableAndFree()
        End Function
 
        Friend Shared Sub VerifyLocal(
            testData As CompilationTestData,
            typeName As String,
            localAndMethod As LocalAndMethod,
            expectedMethodName As String,
            expectedLocalName As String,
            Optional expectedLocalDisplayName As String = Nothing,
            Optional expectedFlags As DkmClrCompilationResultFlags = DkmClrCompilationResultFlags.None,
            Optional expectedILOpt As String = Nothing,
            Optional expectedGeneric As Boolean = False,
            <CallerFilePath> Optional expectedValueSourcePath As String = Nothing,
            <CallerLineNumber> Optional expectedValueSourceLine As Integer = 0)
 
            ExpressionCompilerTestHelpers.VerifyLocal(Of MethodSymbol)(
                testData,
                typeName,
                localAndMethod,
                expectedMethodName,
                expectedLocalName,
                If(expectedLocalDisplayName, expectedLocalName),
                expectedFlags,
                AddressOf VerifyTypeParameters,
                expectedILOpt,
                expectedGeneric,
                expectedValueSourcePath,
                expectedValueSourceLine)
        End Sub
 
        Friend Shared Function GetMethodOrTypeBySignature(compilation As Compilation, signature As String) As Symbol
            Dim parameterTypeNames() As String = Nothing
            Dim methodOrTypeName = ExpressionCompilerTestHelpers.GetMethodOrTypeSignatureParts(signature, parameterTypeNames)
 
            Dim candidates = compilation.GetMembers(methodOrTypeName)
            Dim methodOrType = If(parameterTypeNames Is Nothing,
                candidates.FirstOrDefault(),
                candidates.FirstOrDefault(Function(c) parameterTypeNames.SequenceEqual(DirectCast(c, MethodSymbol).Parameters.Select(Function(p) p.Type.Name))))
 
            Assert.False(methodOrType Is Nothing, "Could not find method or type with signature '" + signature + "'.")
            Return methodOrType
        End Function
 
        Friend Shared Function VariableAlias(name As String, Optional type As Type = Nothing) As [Alias]
            Return VariableAlias(name, If(type, GetType(Object)).AssemblyQualifiedName)
        End Function
 
        Friend Shared Function VariableAlias(name As String, typeAssemblyQualifiedName As String) As [Alias]
            Return New [Alias](DkmClrAliasKind.Variable, name, name, typeAssemblyQualifiedName, Nothing, Nothing)
        End Function
 
        Friend Shared Function ObjectIdAlias(id As UInteger, Optional type As Type = Nothing) As [Alias]
            Return ObjectIdAlias(id, If(type, GetType(Object)).AssemblyQualifiedName)
        End Function
 
        Friend Shared Function ObjectIdAlias(id As UInteger, typeAssemblyQualifiedName As String) As [Alias]
            Assert.NotEqual(Of UInteger)(0, id) ' Not a valid id.
            Dim name = $"${id}"
            Return New [Alias](DkmClrAliasKind.ObjectId, name, name, typeAssemblyQualifiedName, Nothing, Nothing)
        End Function
 
        Friend Shared Function ReturnValueAlias(Optional id As Integer = -1, Optional type As Type = Nothing) As [Alias]
            Return ReturnValueAlias(id, If(type, GetType(Object)).AssemblyQualifiedName)
        End Function
 
        Friend Shared Function ReturnValueAlias(id As Integer, typeAssemblyQualifiedName As String) As [Alias]
            Dim name = $"Method M{If(id < 0, "", id.ToString())} returned"
            Dim fullName = If(id < 0, "$ReturnValue", $"$ReturnValue{id}")
            Return New [Alias](DkmClrAliasKind.ReturnValue, name, fullName, typeAssemblyQualifiedName, Nothing, Nothing)
        End Function
 
        Friend Shared Function ExceptionAlias(Optional type As Type = Nothing, Optional stowed As Boolean = False) As [Alias]
            Return ExceptionAlias(If(type, GetType(Exception)).AssemblyQualifiedName, stowed)
        End Function
 
        Friend Shared Function ExceptionAlias(typeAssemblyQualifiedName As String, Optional stowed As Boolean = False) As [Alias]
            Dim fullName = If(stowed, "$stowedexception", "$exception")
            Const name = "Error"
            Dim kind = If(stowed, DkmClrAliasKind.StowedException, DkmClrAliasKind.Exception)
            Return New [Alias](kind, name, fullName, typeAssemblyQualifiedName, Nothing, Nothing)
        End Function
 
        Friend Shared Function GetMethodDebugInfo(runtime As RuntimeInstance, qualifiedMethodName As String, Optional ilOffset As Integer = 0) As MethodDebugInfo(Of TypeSymbol, LocalSymbol)
            Dim peCompilation = runtime.Modules.SelectAsArray(Function(m) m.MetadataBlock).ToCompilation()
            Dim peMethod = peCompilation.GlobalNamespace.GetMember(Of PEMethodSymbol)(qualifiedMethodName)
            Dim peModule = DirectCast(peMethod.ContainingModule, PEModuleSymbol)
            Dim symReader = runtime.Modules.Single(Function(mi) mi.ModuleVersionId = peModule.Module.GetModuleVersionIdOrThrow()).SymReader
            Dim symbolProvider = New VisualBasicEESymbolProvider(peModule, peMethod)
            Return MethodDebugInfo(Of TypeSymbol, LocalSymbol).ReadMethodDebugInfo(DirectCast(symReader, ISymUnmanagedReader3), symbolProvider, MetadataTokens.GetToken(peMethod.Handle), methodVersion:=1, ilOffset:=ilOffset, isVisualBasicMethod:=True)
        End Function
 
        Friend Shared Sub CheckAttribute(assembly As IEnumerable(Of Byte), method As IMethodSymbol, description As AttributeDescription, expected As Boolean)
            Dim [module] = AssemblyMetadata.CreateFromImage(assembly).GetModules().Single().Module
            Dim typeName = method.ContainingType.Name
            Dim typeHandle = [module].MetadataReader.TypeDefinitions.Single(Function(handle) [module].GetTypeDefNameOrThrow(handle) = typeName)
 
            Dim methodName = method.Name
            Dim methodHandle = [module].GetMethodsOfTypeOrThrow(typeHandle).Single(Function(handle) [module].GetMethodDefNameOrThrow(handle) = methodName)
 
            Dim returnParamHandle = [module].GetParametersOfMethodOrThrow(methodHandle).FirstOrDefault()
 
            If returnParamHandle.IsNil Then
                Assert.False(expected)
            Else
                Dim attributes = [module].GetCustomAttributesOrThrow(returnParamHandle).Where(Function(handle) [module].GetTargetAttributeSignatureIndex(handle, description) <> -1)
 
                If expected Then
                    Assert.Equal(1, attributes.Count())
                Else
                    Assert.Empty(attributes)
                End If
            End If
        End Sub
 
        Friend Shared Sub CheckAttribute(assembly As IEnumerable(Of Byte), method As IMethodSymbolInternal, description As AttributeDescription, expected As Boolean)
            CheckAttribute(assembly, DirectCast(method, IMethodSymbol), description, expected)
        End Sub
 
        Friend Shared Sub CheckAttribute(assembly As IEnumerable(Of Byte), method As MethodSymbol, description As AttributeDescription, expected As Boolean)
            CheckAttribute(assembly, DirectCast(method, IMethodSymbolInternal), description, expected)
        End Sub
    End Class
End Namespace