/// <summary>
        /// Evaluates an LLDB expression. It decides which expression evaluation method to use
        /// (e.g. LLDB, lldb-eval, path expression, etc.) depending on the Stadia SDK settings and
        /// the input |expression|. It doesn't support format specifiers, only expressions that
        /// can be directly evaluated in the LLDB environment.
        /// </summary>
        /// <param name="expression">The expression to be evaluated.</param>
        /// <param name="variable">The evaluation context.</param>
        /// <param name="natvisScope">The Natvis tokens to be resolved before evaluation.</param>
        /// <param name="displayName">The display name given to the result. If null the underlying
        /// debugger's context specific name is used.</param>
        /// <returns>The expression result.</returns>
        async Task <IVariableInformation> EvaluateLldbExpressionAsync(VsExpression expression,
                                                                      IVariableInformation variable,
                                                                      NatvisScope natvisScope,
                                                                      string displayName)
        {
            ExpressionEvaluationStrategy strategy = _extensionOptions.ExpressionEvaluationStrategy;
            var stepsRecorder = new ExpressionEvaluationRecorder.StepsRecorder(_timeSource);

            long startTimestampUs = _timeSource.GetTimestampUs();

            IVariableInformation variableInformation =
                await EvaluateLldbExpressionWithMetricsAsync(
                    expression, variable, natvisScope, displayName, strategy, stepsRecorder);

            // Evaluating a context variable will just return the reference to it. Because of
            // deferred evaluation of display values, some values could be incorrectly displayed
            // (in the case a context variable was changed in between two expression evaluations).
            // In order to prevent this, we create a copy of result if the expression was simply
            // a context variable.
            if (natvisScope.IsContextVariable(expression.Value))
            {
                variableInformation = variableInformation.Clone(expression.FormatSpecifier);
            }

            long endTimestampUs = _timeSource.GetTimestampUs();

            _expressionEvaluationRecorder.Record(strategy, ExpressionEvaluationContext.VALUE,
                                                 stepsRecorder, startTimestampUs, endTimestampUs,
                                                 variable.Id);

            return(variableInformation);
        }
        public void Record(ExpressionEvaluationStrategy strategy,
                           ExpressionEvaluationContext context, StepsRecorder stepsRecorder,
                           long startTimestampUs, long endTimestampUs, string natvisValueId)
        {
            var batchParams = new ExpressionEvaluationBatchParams(
                strategy, context, stepsRecorder.GetStepsList(), startTimestampUs, endTimestampUs,
                natvisValueId);

            _batchEventAggregator.Add(batchParams);
        }
Пример #3
0
 public ExpressionEvaluationBatchParams(ExpressionEvaluationStrategy strategy,
                                        ExpressionEvaluationContext context,
                                        List <ExpressionEvaluationStepBatchParams> evaluationSteps,
                                        long startTimestampUs, long endTimestampUs,
                                        string natvisValueId)
 {
     StrategyParam    = strategy;
     ContextParam     = context;
     EvaluationSteps  = evaluationSteps;
     StartTimestampUs = startTimestampUs;
     EndTimestampUs   = endTimestampUs;
     NatvisValueId    = natvisValueId;
 }
        public void Record(ExpressionEvaluationStrategy expressionEvaluationStrategy,
                           ExpressionEvaluationContext expressionEvaluationContext,
                           StepsRecorder stepsRecorder, long startTimestampUs, long endTimestampUs)
        {
            // Expression evaluation with 'Value' context should use the method signature that
            // includes the natvisValueId parameter. This method is only meant to be used by
            // 'Frame' context expression evaluations.
            if (expressionEvaluationContext == ExpressionEvaluationContext.VALUE)
            {
                throw new ArgumentException(
                          "Record method invocation with 'Value' context should include natvisValueId.");
            }

            Record(expressionEvaluationStrategy, expressionEvaluationContext, stepsRecorder,
                   startTimestampUs, endTimestampUs, null);
        }
Пример #5
0
        public void StrategyBatchTest(ExpressionEvaluationStrategy strategySource,
                                      ExpressionEvaluation.Types.Strategy strategyExpected)
        {
            _expressionEvaluationBatch.Add(new ExpressionEvaluationBatchParams(
                                               strategySource, ExpressionEvaluationContext.FRAME,
                                               new List <ExpressionEvaluationStepBatchParams>(), 500,
                                               2000, null));

            var batchSummary = _expressionEvaluationBatch.GetSummary();

            Assert.NotNull(batchSummary.Proto.ExpressionEvaluations);
            Assert.AreEqual(1, batchSummary.Proto.ExpressionEvaluations.Count);

            var expressionEvaluation = batchSummary.Proto.ExpressionEvaluations[0];

            Assert.AreEqual(strategyExpected, expressionEvaluation.Strategy);
        }
Пример #6
0
        IDebugAsyncExpression CreateExpression(string expression,
                                               ExpressionEvaluationStrategy
                                               expressionEvaluationStrategy =
                                               ExpressionEvaluationStrategy.LLDB)
        {
            var extensionOptionsMock = Substitute.For <IExtensionOptions>();

            extensionOptionsMock.ExpressionEvaluationStrategy.Returns(expressionEvaluationStrategy);

            var asyncEvaluatorFactory = new AsyncExpressionEvaluator.Factory(
                _createPropertyDelegate, _varInfoBuilder, _vsExpressionCreator,
                new ErrorDebugProperty.Factory(), _engineCommandsMock, extensionOptionsMock,
                _expressionEvaluationRecorder, _timeSource);

            return(new DebugAsyncExpression.Factory(asyncEvaluatorFactory, _taskExecutor).Create(
                       _mockDebuggerStackFrame, expression, _mockDebugEngineHandler, _mockProgram,
                       _mockThread));
        }
Пример #7
0
 AsyncExpressionEvaluator(RemoteFrame frame, string text,
                          VsExpressionCreator vsExpressionCreator,
                          VarInfoBuilder varInfoBuilder,
                          CreateDebugPropertyDelegate createPropertyDelegate,
                          ErrorDebugProperty.Factory errorDebugPropertyFactory,
                          IDebugEngineCommands debugEngineCommands,
                          ExpressionEvaluationStrategy expressionEvaluationStrategy,
                          ExpressionEvaluationRecorder expressionEvaluationRecorder,
                          ITimeSource timeSource)
 {
     _frame = frame;
     _text  = text;
     _vsExpressionCreator          = vsExpressionCreator;
     _varInfoBuilder               = varInfoBuilder;
     _createPropertyDelegate       = createPropertyDelegate;
     _errorDebugPropertyFactory    = errorDebugPropertyFactory;
     _debugEngineCommands          = debugEngineCommands;
     _expressionEvaluationStrategy = expressionEvaluationStrategy;
     _expressionEvaluationRecorder = expressionEvaluationRecorder;
     _timeSource = timeSource;
 }
        public void RecordSingleEventWithTwoStepsTest()
        {
            const ExpressionEvaluationStrategy strategySource = ExpressionEvaluationStrategy.LLDB;
            const ExpressionEvaluationContext  contextSource  = ExpressionEvaluationContext.FRAME;

            var stepsRecorder = new ExpressionEvaluationRecorder.StepsRecorder(_timeSource);

            using (Step step = stepsRecorder.NewStep(ExpressionEvaluationEngine.LLDB_VARIABLE_PATH))
            {
                step.Finalize(LLDBErrorCode.ERROR);
            }

            using (Step step = stepsRecorder.NewStep(ExpressionEvaluationEngine.LLDB))
            {
                step.Finalize(LLDBErrorCode.OK);
            }

            const long startTimestampUs = 750;
            const long endTimestampUs   = 21562;

            _expressionEvaluationRecorder.Record(strategySource, contextSource, stepsRecorder,
                                                 startTimestampUs, endTimestampUs);

            // Get a copy of the batch summary sent to batchEventAggregator so we can verify
            // that it matches the one being sent to metrics.
            ExpressionEvaluationBatchSummary batchSummary = null;

            _batchEventAggregator.BatchSummaryReady += (_, newSummary) => batchSummary = newSummary;

            _timer.Increment(_minimumBatchSeparationMilliseconds);
            _eventScheduler.Increment(_minimumBatchSeparationMilliseconds);

            const ExpressionEvaluation.Types.Strategy strategyExpected =
                ExpressionEvaluation.Types.Strategy.Lldb;
            const ExpressionEvaluation.Types.Context contextExpected = ExpressionEvaluation.Types
                                                                       .Context.Frame;

            Assert.AreEqual(1, batchSummary.Proto.ExpressionEvaluations.Count);
            ExpressionEvaluation received = batchSummary.Proto.ExpressionEvaluations[0];

            Assert.Multiple(() =>
            {
                Assert.AreEqual(strategyExpected, received.Strategy);
                Assert.AreEqual(contextExpected, received.Context);
                Assert.NotNull(received.EvaluationSteps);
                Assert.AreEqual(2, received.EvaluationSteps.Count);
                Assert.AreEqual(startTimestampUs, received.StartTimestampMicroseconds);
                Assert.AreEqual(endTimestampUs, received.EndTimestampMicroseconds);
                Assert.Null(received.NatvisValueId);
            });

            const ExpressionEvaluationStep.Types.Engine firstStepEngineExpected =
                ExpressionEvaluationStep.Types.Engine.LldbVariablePath;
            const ExpressionEvaluationStep.Types.EngineResult firstStepEngineResultExpected =
                ExpressionEvaluationStep.Types.EngineResult.LldbError;
            const ExpressionEvaluationStep.Types.Engine secondStepEngineExpected =
                ExpressionEvaluationStep.Types.Engine.Lldb;
            const ExpressionEvaluationStep.Types.EngineResult secondStepEngineResultExpected =
                ExpressionEvaluationStep.Types.EngineResult.LldbOk;

            ExpressionEvaluationStep firstStep  = received.EvaluationSteps[0];
            ExpressionEvaluationStep secondStep = received.EvaluationSteps[1];

            Assert.Multiple(() =>
            {
                Assert.AreEqual(firstStepEngineExpected, firstStep.Engine);
                Assert.AreEqual(firstStepEngineResultExpected, firstStep.Result);
                Assert.AreEqual(1, firstStep.DurationMicroseconds);
                Assert.AreEqual(secondStepEngineExpected, secondStep.Engine);
                Assert.AreEqual(secondStepEngineResultExpected, secondStep.Result);
                Assert.AreEqual(1, secondStep.DurationMicroseconds);
            });

            _metrics.Received(1)
            .RecordEvent(DeveloperEventType.Types.Type.VsiDebugExpressionEvaluationBatch,
                         new DeveloperLogEvent
            {
                DebugExpressionEvaluationBatch = batchSummary.Proto,
                StatusCode = DeveloperEventStatus.Types.Code.Success
            });
        }
        public void RecordSingleEventWithValueContextTest()
        {
            const ExpressionEvaluationStrategy strategySource =
                ExpressionEvaluationStrategy.LLDB_EVAL;
            const ExpressionEvaluationContext contextSource = ExpressionEvaluationContext.VALUE;

            var stepsRecorder = new ExpressionEvaluationRecorder.StepsRecorder(_timeSource);

            using (Step step = stepsRecorder.NewStep(ExpressionEvaluationEngine.LLDB_EVAL))
            {
                step.Finalize(LldbEvalErrorCode.Ok);
            }

            const long startTimestampUs = 750;
            const long endTimestampUs   = 21562;

            // Attempt to record expression evaluation with context Value, without natvisValueId
            // should throw an exception.
            Assert.Throws <ArgumentException>(() =>
            {
                _expressionEvaluationRecorder.Record(
                    strategySource, contextSource, stepsRecorder,
                    startTimestampUs, endTimestampUs);
            });

            const string natvisValueId = "TestId";

            _expressionEvaluationRecorder.Record(strategySource, contextSource, stepsRecorder,
                                                 startTimestampUs, endTimestampUs, natvisValueId);

            // Get a copy of the batch summary sent to batchEventAggregator so we can verify
            // that it matches the one being sent to metrics.
            ExpressionEvaluationBatchSummary batchSummary = null;

            _batchEventAggregator.BatchSummaryReady += (_, newSummary) => batchSummary = newSummary;

            _timer.Increment(_minimumBatchSeparationMilliseconds);
            _eventScheduler.Increment(_minimumBatchSeparationMilliseconds);

            const ExpressionEvaluation.Types.Strategy strategyExpected =
                ExpressionEvaluation.Types.Strategy.LldbEval;
            const ExpressionEvaluation.Types.Context contextExpected =
                ExpressionEvaluation.Types.Context.Value;

            Assert.AreEqual(1, batchSummary.Proto.ExpressionEvaluations.Count);
            ExpressionEvaluation received = batchSummary.Proto.ExpressionEvaluations[0];

            Assert.Multiple(() =>
            {
                Assert.AreEqual(strategyExpected, received.Strategy);
                Assert.AreEqual(contextExpected, received.Context);
                Assert.NotNull(received.EvaluationSteps);
                Assert.AreEqual(1, received.EvaluationSteps.Count);
                Assert.AreEqual(startTimestampUs, received.StartTimestampMicroseconds);
                Assert.AreEqual(endTimestampUs, received.EndTimestampMicroseconds);
                Assert.AreEqual(natvisValueId, received.NatvisValueId);
            });

            const ExpressionEvaluationStep.Types.Engine stepEngineExpected =
                ExpressionEvaluationStep.Types.Engine.LldbEval;
            const ExpressionEvaluationStep.Types.EngineResult stepEngineResultExpected =
                ExpressionEvaluationStep.Types.EngineResult.LldbEvalOk;

            ExpressionEvaluationStep receivedEvaluationStep = received.EvaluationSteps[0];

            Assert.Multiple(() =>
            {
                Assert.AreEqual(stepEngineExpected, receivedEvaluationStep.Engine);
                Assert.AreEqual(stepEngineResultExpected, receivedEvaluationStep.Result);
                Assert.AreEqual(1, receivedEvaluationStep.DurationMicroseconds);
            });

            _metrics.Received(1)
            .RecordEvent(DeveloperEventType.Types.Type.VsiDebugExpressionEvaluationBatch,
                         new DeveloperLogEvent
            {
                DebugExpressionEvaluationBatch = batchSummary.Proto,
                StatusCode = DeveloperEventStatus.Types.Code.Success
            });
        }
        async Task <IVariableInformation> EvaluateLldbExpressionWithMetricsAsync(
            VsExpression expression, IVariableInformation variable, NatvisScope natvisScope,
            string displayName, ExpressionEvaluationStrategy strategy,
            ExpressionEvaluationRecorder.StepsRecorder stepsRecorder)
        {
            bool variableReplaced = false;

            expression = expression.MapValue(
                v => ReplaceScopedNames(v, natvisScope?.ScopedNames, out variableReplaced));

            var lldbErrors = new List <string>();

            // A helper lambda function to construct an exception given the list of lldb errors.
            Func <IList <string>, ExpressionEvaluationFailed> createExpressionEvaluationException =
                errors =>
            {
                var exceptionMsg =
                    $"Failed to evaluate expression, display name: {displayName}, " +
                    $"expression: {expression}";

                errors = errors.Where(error => !string.IsNullOrEmpty(error)).ToList();
                if (errors.Any())
                {
                    exceptionMsg += $", info: {{{string.Join("; ", errors)}}}";
                }

                return(new ExpressionEvaluationFailed(exceptionMsg));
            };

            if (strategy == ExpressionEvaluationStrategy.LLDB_EVAL ||
                strategy == ExpressionEvaluationStrategy.LLDB_EVAL_WITH_FALLBACK)
            {
                IVariableInformation value;
                LldbEvalErrorCode    errorCode;

                using (var step = stepsRecorder.NewStep(ExpressionEvaluationEngine.LLDB_EVAL))
                {
                    value = await variable.EvaluateExpressionLldbEvalAsync(
                        displayName, expression, natvisScope.ContextVariables);

                    errorCode = (LldbEvalErrorCode)Enum.ToObject(typeof(LldbEvalErrorCode),
                                                                 value.ErrorCode);

                    step.Finalize(errorCode);
                }

                if (errorCode == LldbEvalErrorCode.Ok)
                {
                    value.FallbackValueFormat = variable.FallbackValueFormat;
                    return(value);
                }

                lldbErrors.Add(value?.ErrorMessage);

                if (errorCode == LldbEvalErrorCode.InvalidNumericLiteral ||
                    errorCode == LldbEvalErrorCode.InvalidOperandType ||
                    errorCode == LldbEvalErrorCode.UndeclaredIdentifier)
                {
                    // In the case of a well-known error, there's no need to fallback to
                    // LLDB, as it will fail with the same error.
                    throw createExpressionEvaluationException(lldbErrors);
                }
            }

            if (strategy == ExpressionEvaluationStrategy.LLDB)
            {
                // If lldb-eval is not enabled, try to interpret the expression as member access
                // before using LLDB to evaluate the expression in the context of the variable.
                IVariableInformation value;
                LLDBErrorCode        errorCode;

                using (var step =
                           stepsRecorder.NewStep(ExpressionEvaluationEngine.LLDB_VARIABLE_PATH))
                {
                    value     = GetValueForMemberAccessExpression(variable, expression, displayName);
                    errorCode = value != null && !value.Error
                        ? LLDBErrorCode.OK
                        : LLDBErrorCode.ERROR;

                    step.Finalize(errorCode);
                }

                if (errorCode == LLDBErrorCode.OK)
                {
                    value.FallbackValueFormat = variable.FallbackValueFormat;
                    return(value);
                }

                lldbErrors.Add(value?.ErrorMessage);
            }

            if (strategy == ExpressionEvaluationStrategy.LLDB ||
                strategy == ExpressionEvaluationStrategy.LLDB_EVAL_WITH_FALLBACK)
            {
                IVariableInformation value;
                LLDBErrorCode        errorCode;

                using (var step = stepsRecorder.NewStep(ExpressionEvaluationEngine.LLDB))
                {
                    value = await EvaluateExpressionInVariableScopeAsync(
                        variable, expression, displayName);

                    errorCode = value != null && !value.Error
                        ? LLDBErrorCode.OK
                        : LLDBErrorCode.ERROR;

                    step.Finalize(errorCode);
                }

                if (errorCode == LLDBErrorCode.OK)
                {
                    value.FallbackValueFormat = variable.FallbackValueFormat;
                    return(value);
                }

                lldbErrors.Add(value?.ErrorMessage);
            }

            throw createExpressionEvaluationException(lldbErrors);
        }