File: FrameDecoder.cs
Web Access
Project: src\src\ExpressionEvaluator\Core\Source\ExpressionCompiler\Microsoft.CodeAnalysis.ExpressionCompiler.csproj (Microsoft.CodeAnalysis.ExpressionEvaluator.ExpressionCompiler)
// 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.
 
using System;
using System.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Symbols;
using Microsoft.VisualStudio.Debugger;
using Microsoft.VisualStudio.Debugger.CallStack;
using Microsoft.VisualStudio.Debugger.Clr;
using Microsoft.VisualStudio.Debugger.ComponentInterfaces;
using Microsoft.VisualStudio.Debugger.Evaluation;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.ExpressionEvaluator
{
    /// <summary>
    /// This class provides method name, argument and return type information for the Call Stack window and DTE.
    /// </summary>
    /// <remarks>
    /// While it might be nice to provide language-specific syntax in the Call Stack window, previous implementations have
    /// always used C# syntax (but with language-specific "special names").  Since these names are exposed through public
    /// APIs, we will remain consistent with the old behavior (for consumers who may be parsing the frame names).
    /// </remarks>
    internal abstract class FrameDecoder<TCompilation, TMethodSymbol, TModuleSymbol, TTypeSymbol, TTypeParameterSymbol> : IDkmLanguageFrameDecoder
        where TCompilation : Compilation
        where TMethodSymbol : class, IMethodSymbolInternal
        where TModuleSymbol : class, IModuleSymbolInternal
        where TTypeSymbol : class, ITypeSymbolInternal
        where TTypeParameterSymbol : class, ITypeParameterSymbolInternal
    {
        private readonly InstructionDecoder<TCompilation, TMethodSymbol, TModuleSymbol, TTypeSymbol, TTypeParameterSymbol> _instructionDecoder;
 
        internal FrameDecoder(InstructionDecoder<TCompilation, TMethodSymbol, TModuleSymbol, TTypeSymbol, TTypeParameterSymbol> instructionDecoder)
        {
            _instructionDecoder = instructionDecoder;
        }
 
        void IDkmLanguageFrameDecoder.GetFrameName(
            DkmInspectionContext inspectionContext,
            DkmWorkList workList,
            DkmStackWalkFrame frame,
            DkmVariableInfoFlags argumentFlags,
            DkmCompletionRoutine<DkmGetFrameNameAsyncResult> completionRoutine)
        {
            try
            {
                Debug.Assert((argumentFlags & (DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types | DkmVariableInfoFlags.Values)) == argumentFlags,
                    $"Unexpected argumentFlags '{argumentFlags}'");
 
                GetNameWithGenericTypeArguments(workList, frame, onSuccess: method => GetFrameName(inspectionContext, workList, frame, argumentFlags, completionRoutine, method),
                    onFailure: e => completionRoutine(DkmGetFrameNameAsyncResult.CreateErrorResult(e)));
            }
            catch (NotImplementedMetadataException)
            {
                inspectionContext.GetFrameName(workList, frame, argumentFlags, completionRoutine);
            }
            catch (Exception e) when (ExpressionEvaluatorFatalError.CrashIfFailFastEnabled(e))
            {
                throw ExceptionUtilities.Unreachable();
            }
        }
 
        void IDkmLanguageFrameDecoder.GetFrameReturnType(
            DkmInspectionContext inspectionContext,
            DkmWorkList workList,
            DkmStackWalkFrame frame,
            DkmCompletionRoutine<DkmGetFrameReturnTypeAsyncResult> completionRoutine)
        {
            try
            {
                GetNameWithGenericTypeArguments(workList, frame, onSuccess: method => completionRoutine(new DkmGetFrameReturnTypeAsyncResult(_instructionDecoder.GetReturnTypeName(method))),
                    onFailure: e => completionRoutine(DkmGetFrameReturnTypeAsyncResult.CreateErrorResult(e)));
            }
            catch (NotImplementedMetadataException)
            {
                inspectionContext.GetFrameReturnType(workList, frame, completionRoutine);
            }
            catch (Exception e) when (ExpressionEvaluatorFatalError.CrashIfFailFastEnabled(e))
            {
                throw ExceptionUtilities.Unreachable();
            }
        }
 
        private void GetNameWithGenericTypeArguments(
            DkmWorkList workList,
            DkmStackWalkFrame frame,
            Action<TMethodSymbol> onSuccess,
            Action<Exception> onFailure)
        {
            // NOTE: We could always call GetClrGenericParameters, pass them to GetMethod and have that
            // return a constructed method symbol, but it seems unwise to call GetClrGenericParameters
            // for all frames (as this call requires a round-trip to the debuggee process).
 
            // [nullability] We do not expect frames without instruction addresses in this path. Additionally, there is no good 
            // way to return if we receive a frame with no address, as executing the onFailure callback would just hide a deeper issue.
            RoslynDebug.AssertNotNull(frame.InstructionAddress);
            var instructionAddress = (DkmClrInstructionAddress)frame.InstructionAddress;
            var compilation = _instructionDecoder.GetCompilation(instructionAddress.ModuleInstance);
            var method = _instructionDecoder.GetMethod(compilation, instructionAddress);
            var typeParameters = _instructionDecoder.GetAllTypeParameters(method);
            if (!typeParameters.IsEmpty)
            {
                frame.GetClrGenericParameters(
                    workList,
                    result =>
                    {
                        try
                        {
                            // DkmGetClrGenericParametersAsyncResult.ParameterTypeNames will throw if ErrorCode != 0.
                            var serializedTypeNames = (result.ErrorCode == 0) ? result.ParameterTypeNames : null;
                            var typeArguments = _instructionDecoder.GetTypeSymbols(compilation, method, serializedTypeNames);
                            if (!typeArguments.IsEmpty)
                            {
                                method = _instructionDecoder.ConstructMethod(method, typeParameters, typeArguments);
                            }
                            onSuccess(method);
                        }
                        catch (Exception e)
                        {
                            onFailure(e);
                        }
                    });
            }
            else
            {
                onSuccess(method);
            }
        }
 
        private void GetFrameName(
            DkmInspectionContext inspectionContext,
            DkmWorkList workList,
            DkmStackWalkFrame frame,
            DkmVariableInfoFlags argumentFlags,
            DkmCompletionRoutine<DkmGetFrameNameAsyncResult> completionRoutine,
            TMethodSymbol method)
        {
            var includeParameterTypes = argumentFlags.Includes(DkmVariableInfoFlags.Types);
            var includeParameterNames = argumentFlags.Includes(DkmVariableInfoFlags.Names);
 
            if (argumentFlags.Includes(DkmVariableInfoFlags.Values))
            {
                // No need to compute the Expandable bit on
                // argument values since that can be expensive.
                inspectionContext = DkmInspectionContext.Create(
                    inspectionContext.InspectionSession,
                    inspectionContext.RuntimeInstance,
                    inspectionContext.Thread,
                    inspectionContext.Timeout,
                    inspectionContext.EvaluationFlags | DkmEvaluationFlags.NoExpansion,
                    inspectionContext.FuncEvalFlags,
                    inspectionContext.Radix,
                    inspectionContext.Language,
                    inspectionContext.ReturnValue,
                    inspectionContext.AdditionalVisualizationData,
                    inspectionContext.AdditionalVisualizationDataPriority,
                    inspectionContext.ReturnValues);
 
                // GetFrameArguments returns an array of formatted argument values. We'll pass
                // ourselves (GetFrameName) as the continuation of the GetFrameArguments call.
                inspectionContext.GetFrameArguments(
                    workList,
                    frame,
                    result =>
                    {
                        // DkmGetFrameArgumentsAsyncResult.Arguments will throw if ErrorCode != 0.
                        var argumentValues = (result.ErrorCode == 0) ? result.Arguments : null;
                        try
                        {
                            ArrayBuilder<string?>? builder = null;
                            if (argumentValues != null)
                            {
                                builder = ArrayBuilder<string?>.GetInstance();
                                foreach (var argument in argumentValues)
                                {
                                    var formattedArgument = argument as DkmSuccessEvaluationResult;
                                    // Not expecting Expandable bit, at least not from this EE.
                                    Debug.Assert((formattedArgument == null) || (formattedArgument.Flags & DkmEvaluationResultFlags.Expandable) == 0);
                                    builder.Add(formattedArgument?.Value);
                                }
                            }
 
                            var frameName = _instructionDecoder.GetName(method, includeParameterTypes, includeParameterNames, argumentValues: builder);
                            builder?.Free();
                            completionRoutine(new DkmGetFrameNameAsyncResult(frameName));
                        }
                        catch (Exception e)
                        {
                            completionRoutine(DkmGetFrameNameAsyncResult.CreateErrorResult(e));
                        }
                        finally
                        {
                            if (argumentValues != null)
                            {
                                foreach (var argument in argumentValues)
                                {
                                    argument.Close();
                                }
                            }
                        }
                    });
            }
            else
            {
                var frameName = _instructionDecoder.GetName(method, includeParameterTypes, includeParameterNames, argumentValues: null);
                completionRoutine(new DkmGetFrameNameAsyncResult(frameName));
            }
        }
    }
}