/// <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); }); }
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 }); }
/// <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); }