/// <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 IncompatibleLldbEvalResultExceptionTest()
        {
            var stepsRecorder = new ExpressionEvaluationRecorder.StepsRecorder(_timeSource);

            Step step = stepsRecorder.NewStep(ExpressionEvaluationEngine.LLDB_EVAL);

            Assert.Throws <ArgumentException>(() => { step.Finalize(LLDBErrorCode.OK); });
        }
        public void EmptyNatvisValueIdExceptionTest()
        {
            var stepsRecorder = new ExpressionEvaluationRecorder.StepsRecorder(_timeSource);

            Assert.Throws <ArgumentException>(() =>
            {
                _expressionEvaluationRecorder.Record(
                    ExpressionEvaluationStrategy.LLDB,
                    ExpressionEvaluationContext.VALUE,
                    stepsRecorder, startTimestampUs: 750,
                    endTimestampUs: 21562);
            });
        }
Ejemplo n.º 4
0
        async Task <RemoteValue> CreateValueFromExpressionAsync(string expression)
        {
            var stepsRecorder = new ExpressionEvaluationRecorder.StepsRecorder(_timeSource);

            long        startTimestampUs = _timeSource.GetTimestampUs();
            RemoteValue remoteValue      =
                await CreateValueFromExpressionWithMetricsAsync(expression, stepsRecorder);

            long endTimestampUs = _timeSource.GetTimestampUs();

            _expressionEvaluationRecorder.Record(_expressionEvaluationStrategy,
                                                 _expressionEvaluationContext, stepsRecorder,
                                                 startTimestampUs, endTimestampUs);

            return(remoteValue);
        }
        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
            });
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Asynchronously creates RemoteValue from the expression. Returns null in case of error.
        /// </summary>
        async Task <RemoteValue> CreateValueFromExpressionWithMetricsAsync(
            string expression, ExpressionEvaluationRecorder.StepsRecorder stepsRecorder)
        {
            if (_text.StartsWith(ExpressionConstants.RegisterPrefix))
            {
                // If text is prefixed by '$', check if it refers to a register by trying to find
                // a register named expression[1:]. If we can't, simply return false to prevent
                // LLDB scratch variables, which also start with '$', from being accessible.
                return(_frame.FindValue(expression.Substring(1), DebuggerApi.ValueType.Register));
            }

            if (_expressionEvaluationStrategy == ExpressionEvaluationStrategy.LLDB_EVAL ||
                _expressionEvaluationStrategy ==
                ExpressionEvaluationStrategy.LLDB_EVAL_WITH_FALLBACK)
            {
                RemoteValue       value;
                LldbEvalErrorCode errorCode;

                using (var step = stepsRecorder.NewStep(ExpressionEvaluationEngine.LLDB_EVAL))
                {
                    value = await _frame.EvaluateExpressionLldbEvalAsync(expression);

                    // Convert an error code to the enum value.
                    errorCode = (LldbEvalErrorCode)Enum.ToObject(
                        typeof(LldbEvalErrorCode), value.GetError().GetError());

                    step.Finalize(errorCode);
                }

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

                if (errorCode == LldbEvalErrorCode.InvalidNumericLiteral ||
                    errorCode == LldbEvalErrorCode.InvalidOperandType ||
                    errorCode == LldbEvalErrorCode.UndeclaredIdentifier)
                {
                    // Evaluation failed with a well-known error. Don't fallback to LLDB native
                    // expression evaluator, since it will fail too.
                    return(value);
                }

                if (_expressionEvaluationStrategy !=
                    ExpressionEvaluationStrategy.LLDB_EVAL_WITH_FALLBACK)
                {
                    // Don't fallback to LLDB native expression evaluator if that option is
                    // disabled.
                    return(value);
                }
            }
            else
            {
                RemoteValue value;

                using (var step =
                           stepsRecorder.NewStep(ExpressionEvaluationEngine.LLDB_VARIABLE_PATH))
                {
                    value = EvaluateWithLldbVariablePath(expression);
                    step.Finalize(ToErrorCodeLLDB(value));
                }

                if (value != null)
                {
                    return(value);
                }
            }

            // Evaluate the expression using LLDB.
            {
                RemoteValue value;

                using (var step = stepsRecorder.NewStep(ExpressionEvaluationEngine.LLDB))
                {
                    value = await _frame.EvaluateExpressionAsync(expression);

                    step.Finalize(ToErrorCodeLLDB(value));
                }

                return(value);
            }

            LLDBErrorCode ToErrorCodeLLDB(RemoteValue v)
            {
                if (v == null)
                {
                    return(LLDBErrorCode.ERROR);
                }
                return(v.GetError().Success() ? LLDBErrorCode.OK : LLDBErrorCode.ERROR);
            }
        }
        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);
        }