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