/// <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); }
protected LeafEntity(IVariableInformation variable, NatvisDiagnosticLogger logger, NatvisExpressionEvaluator evaluator, NatvisScope natvisScope) { _variable = variable; _logger = logger; _evaluator = evaluator; _natvisScope = natvisScope; }
ExpandedItemEntity(IVariableInformation variable, NatvisScope natvisScope, ExpandedItemType expandedItem, NatvisDiagnosticLogger logger, NatvisEntityStore store, NatvisExpressionEvaluator evaluator) : base(variable, logger, evaluator, natvisScope) { _expandedItem = expandedItem; _store = store; }
TreeItemsEntity(IVariableInformation variable, NatvisScope natvisScope, TreeItemsType treeItems, NatvisDiagnosticLogger logger, NatvisEntityStore store, NatvisExpressionEvaluator evaluator, NatvisSizeParser sizeParser) : base(variable, logger, evaluator, natvisScope) { _treeItems = treeItems; _store = store; _sizeParser = sizeParser; }
public VisualizerInfo(VisualizerType viz, TypeName name) { Visualizer = viz; // add the template parameter macro values NatvisScope = new NatvisScope(); for (int i = 0; i < name.Args.Count; ++i) { NatvisScope.AddScopedName($"$T{i + 1}", name.Args[i].FullyQualifiedName); } }
SmartPointerEntity(NatvisExpressionEvaluator evaluator, NatvisDiagnosticLogger logger, IVariableInformation variable, SmartPointerType smartPointerItem, NatvisScope natvisScope, IChildAdapter fallbackAdapter) { _evaluator = evaluator; _logger = logger; _variable = variable; _smartPointerItem = smartPointerItem; _natvisScope = natvisScope; _fallbackAdapter = fallbackAdapter; }
CustomListItemsEntity(IVariableInformation variable, NatvisScope natvisScope, CustomListItemsType customList, NatvisDiagnosticLogger logger, NatvisEntityStore store, NatvisExpressionEvaluator evaluator, IVariableNameTransformer nameTransformer, CodeBlockParser parser) : base(variable, logger, evaluator, natvisScope) { _customList = customList; _store = store; _nameTransformer = nameTransformer; _parser = parser; }
SyntheticItemEntity(IVariableInformation variable, NatvisScope natvisScope, SyntheticItemType item, NatvisDiagnosticLogger logger, NatvisEntityStore store, NatvisExpressionEvaluator evaluator, NatvisStringFormatter stringFormatter, NatvisCollectionEntity.Factory natvisCollectionFactory) : base(variable, logger, evaluator, natvisScope) { _item = item; _store = store; _stringFormatter = stringFormatter; _natvisCollectionFactory = natvisCollectionFactory; }
/// <summary> /// Asynchronously processes a mixed format string that contains literal text and /// expressions embedded inside curly braces. The expressions are evaluated within the /// context of the variable specified, and subsequently formatted using the subexpression /// formatter provided. /// </summary> /// <remarks> /// Examples: /// FormatValueAsync("Some literal text and an {expression}", varInfo, natvisScope, /// subexpressionFormatter); /// FormatValueAsync("{{Escaped, literal text.}}", varInfo, natvisScope, /// subexpressionFormatter); /// </remarks> async Task <string> FormatValueAsync( string format, IVariableInformation variable, NatvisScope natvisScope, Func <IVariableInformation, Task <string> > subexpressionFormatter) { if (string.IsNullOrWhiteSpace(format)) { return(string.Empty); } var value = new StringBuilder(); for (int i = 0; i < format.Length; ++i) { if (format[i] == '{') { if (i + 1 < format.Length && format[i + 1] == '{') { value.Append('{'); i++; continue; } // start of expression Match m = _expressionRegex.Match(format.Substring(i)); if (m.Success) { string expression = format.Substring(i + 1, m.Length - 2); IVariableInformation exprValue = await _evaluator.EvaluateExpressionAsync( expression, variable, natvisScope, null); value.Append(await subexpressionFormatter(exprValue)); i += m.Length - 1; } } else if (format[i] == '}') { // Accept both } and }} as closing braces to match native behavior. value.Append('}'); if (i + 1 < format.Length && format[i + 1] == '}') { i++; } } else { value.Append(format[i]); } } return(value.ToString()); }
/// <summary> /// Evaluates a condition in the context of a variable. /// </summary> /// <returns>The result of the condition evaluation.</returns> public async Task <bool> EvaluateConditionAsync(string condition, IVariableInformation variable, NatvisScope natvisScope) { if (string.IsNullOrWhiteSpace(condition)) { return(true); } IVariableInformation exprValue = await EvaluateExpressionAsync(condition, variable, natvisScope, null); return(exprValue.IsTruthy); }
/// <summary> /// Invokes GetExpressionValue, but returns error variable in case of evaluation error. /// </summary> public async Task <IVariableInformation> GetExpressionValueOrErrorAsync( string expression, IVariableInformation variable, NatvisScope natvisScope, string displayName, string natvisType) { try { return(await EvaluateExpressionAsync(expression, variable, natvisScope, displayName)); } catch (ExpressionEvaluationFailed e) { return(NatvisErrorUtils.LogAndGetEvaluationError( _logger, natvisType, variable?.TypeName, displayName, e.Message)); } }
public override async Task <IList <IVariableInformation> > GetChildrenAsync( int from, int count) { await InitAsync(); var result = new List <IVariableInformation>(); if (_store.ValidationError != null) { result.Add(_store.ValidationError); return(result.GetRange(from, count)); } var indexDic = new NatvisScope(_natvisScope); for (int index = from; index < from + count; index++) { IVariableInformation varInfo = await _store.GetOrEvaluateAsync(index, async i => { indexDic.AddScopedName("$i", $"{i}U"); string displayName = $"[{i}]"; // From the list of all <ValueNode> children, filter all with non-empty body // and return the first which Condition evaluates to true. IndexNodeType valueNode = await _indexListItems .ValueNode?.Where(v => !string.IsNullOrWhiteSpace(v.Value)) .FirstOrDefaultAsync(v => _evaluator.EvaluateConditionAsync( v.Condition, _variable, indexDic)); if (valueNode == null) { // For the current index $i, there is no <ValueNode> which passes the // Condition check. return(new ErrorVariableInformation( displayName, "<Error> No valid <ValueNode> found.")); } return(await _evaluator.GetExpressionValueOrErrorAsync( valueNode.Value, _variable, indexDic, displayName, "IndexListItems")); }); result.Add(varInfo); } return(result); }
internal NatvisSyntheticVariableInformation( NatvisStringFormatter stringFormatter, NatvisCollectionEntity.Factory natvisCollectionFactory, NatvisScope natvisScope, SyntheticItemType syntheticItemType, IVariableInformation varInfo, string displayValue) : base(varInfo) { _stringFormatter = stringFormatter; _natvisCollectionFactory = natvisCollectionFactory; _natvisScope = natvisScope; _syntheticItemType = syntheticItemType; _displayValue = displayValue; // Synthetic items should never show the raw view. if (syntheticItemType.Expand != null) { syntheticItemType.Expand.HideRawView = true; } }
/// <summary> /// Evaluates a Natvis expression in the context of a variable asynchronously. /// /// Examples: /// "myData[0] == true" /// "myData[$i]" /// "MyContainer<$T1, $T2>", /// "(char*)myData,[myLength]s" /// </summary> /// <param name="expression">The expression to evaluate. Natvis tokens are resolved prior to /// evaluation, ex. $i, $Tx. </param> /// <param name="variable"></param> /// <param name="scopedNames">The Natvis tokens to resolve in expression.</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> public async Task <IVariableInformation> EvaluateExpressionAsync( string expression, IVariableInformation variable, NatvisScope natvisScope, string displayName) { var vsExpression = await _vsExpressionCreator.CreateAsync( expression, async (sizeExpression) => { IVariableInformation value = await EvaluateLldbExpressionAsync( _vsExpressionCreator.Create(sizeExpression, ""), variable, natvisScope, displayName); uint size; if (!uint.TryParse(await value.ValueAsync(), out size)) { throw new ExpressionEvaluationFailed("Expression isn't a uint"); } return(size); }); return(await EvaluateLldbExpressionAsync(vsExpression, variable, natvisScope, displayName)); }
public INatvisEntity Create(IVariableInformation variable, NatvisScope natvisScope, IndexListItemsType indexListItems) => new IndexListItemsEntity(variable, natvisScope, indexListItems, _logger, new NatvisEntityStore(), _evaluator, _sizeParser);
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); }
public INatvisEntity Create(IVariableInformation variable, NatvisScope natvisScope, CustomListItemsType customListItems) => new CustomListItemsEntity(variable, natvisScope, customListItems, _logger, new NatvisEntityStore(), _evaluator, _nameTransformer, new CodeBlockParser(_evaluator));
/// <summary> /// Processes a SizeType array and returns the first valid size value. /// /// Throws ExpressionEvaluationFailed in case of an evaluation error and /// InvalidOperationException if valid <Size> node is not found. /// </summary> /// <returns></returns> internal async Task <uint> ParseSizeAsync(SizeType[] sizes, IVariableInformation varInfo, NatvisScope natvisScope) { if (sizes == null) { throw new InvalidOperationException("Valid <Size> node not found."); } foreach (SizeType curSize in sizes) { string errorMsg = null; string sizeText = null; try { if (!NatvisViewsUtil.IsViewVisible(varInfo.FormatSpecifier, curSize.IncludeView, curSize.ExcludeView) || !await _evaluator.EvaluateConditionAsync(curSize.Condition, varInfo, natvisScope)) { continue; } if (string.IsNullOrEmpty(curSize.Value)) { errorMsg = "The expression cannot be empty."; } else { IVariableInformation sizeVarInfo = await _evaluator.EvaluateExpressionAsync( curSize.Value, varInfo, natvisScope, null); sizeVarInfo.FallbackValueFormat = ValueFormat.Default; sizeText = await sizeVarInfo.ValueAsync(); } } catch (ExpressionEvaluationFailed ex) when(curSize.Optional) { errorMsg = ex.Message; } uint size = 0; if (errorMsg == null) { if (!ParseUint(sizeText, out size)) { errorMsg = "The expression's value was not a number. " + $"Expression='{curSize.Value}' Value='{sizeText}'"; } } if (errorMsg != null) { if (!curSize.Optional) { throw new ExpressionEvaluationFailed("Failed to evaluate <Size> node. " + errorMsg); } _logger.Verbose(() => $"Failed to evaluate <Size> node for type" + $" '{varInfo.TypeName}'. Reason: {errorMsg}"); } else { return(size); } } throw new InvalidOperationException("Valid <Size> node not found."); }
public INatvisEntity Create(IVariableInformation variable, ExpandType expandType, NatvisScope natvisScope) { var children = new List <INatvisEntity>(); if (expandType?.Items == null) { return(new NatvisCollectionEntity(children, variable, expandType?.HideRawView ?? true)); } foreach (object item in expandType.Items) { if (item is ItemType itemType) { children.Add(_itemFactory.Create(variable, natvisScope, itemType)); } else if (item is SyntheticItemType syntheticItemType) { children.Add(_syntheticItemFactory.Create(variable, natvisScope, syntheticItemType, this)); } else if (item is ExpandedItemType expandedItemType) { children.Add( _expandedItemFactory.Create(variable, natvisScope, expandedItemType)); } else if (item is IndexListItemsType indexListItems) { children.Add(RangedNatvisEntityDecorator.First( _maxChildrenPerRangeIndexListItems, _indexListItemsFactory.Create(variable, natvisScope, indexListItems))); } else if (item is ArrayItemsType arrayItems) { children.Add(RangedNatvisEntityDecorator.First( _maxChildrenPerRangeArrayItems, _arrayItemsFactory.Create(variable, natvisScope, arrayItems))); } else if (item is LinkedListItemsType linkedListItems) { children.Add(RangedNatvisEntityDecorator.First( _maxChildrenPerRangeLinkedListItems, _linkedListItemsFactory.Create(variable, natvisScope, linkedListItems))); } else if (item is TreeItemsType treeItems) { children.Add(RangedNatvisEntityDecorator.First( _maxChildrenPerRangeTreeItems, _treeItemsFactory.Create(variable, natvisScope, treeItems))); } else if (item is CustomListItemsType customListItems && _natvisExperimentsEnabled()) { // Use "MaxItemsPerView" attribute to limit the number of items per view. // If not defined (default value is 0), use the "default" limit to avoid // huge lists. // TODO: Consider removing the default limit if the rendering performance // is no longer an issue. // The default limit in Visual Studio (Code) is 5000 -- see docs for // MaxItemsPerViewType at https://code.visualstudio.com/docs/cpp/natvis. int maxItemsPerView = customListItems.MaxItemsPerView > 0 ? (int)customListItems.MaxItemsPerView : _maxChildrenPerRangeCustomListItems; children.Add(RangedNatvisEntityDecorator.First( maxItemsPerView, _customListItemsFactory.Create(variable, natvisScope, customListItems))); }
public CustomListItemsContext(NatvisScope natvisScope, IVariableInformation variable) { NatvisScope = natvisScope; Variable = variable; }
public SmartPointerEntity Create(IVariableInformation variable, SmartPointerType smartPointerItem, NatvisScope natvisScope, IChildAdapter fallbackAdapter) => new SmartPointerEntity(_evaluator, _logger, variable, smartPointerItem, natvisScope, fallbackAdapter);
public INatvisEntity Create(IVariableInformation variable, NatvisScope natvisScope, TreeItemsType treeItems) => new TreeItemsEntity(variable, natvisScope, treeItems, _logger, new NatvisEntityStore(), _evaluator, _sizeParser);
/// <summary> /// Constructs a copy of the other natvis scope. /// </summary> public NatvisScope(NatvisScope other) { ScopedNames = new Dictionary <string, string>(other.ScopedNames); ContextVariables = new Dictionary <string, RemoteValue>(other.ContextVariables); }
public INatvisEntity Create(IVariableInformation variable, NatvisScope natvisScope, ExpandedItemType expandedItem) => new ExpandedItemEntity(variable, natvisScope, expandedItem, _logger, new NatvisEntityStore(), _evaluator);
public INatvisEntity Create(IVariableInformation variable, NatvisScope natvisScope, SyntheticItemType item, NatvisCollectionEntity.Factory natvisCollectionFactory) => new SyntheticItemEntity(variable, natvisScope, item, _logger, new NatvisEntityStore(), _evaluator, _stringFormatter, natvisCollectionFactory);
/// <summary> /// Declare a variable in using the given variable scope to execute the value expression. /// Token replacement using scopedNames is done against both the variable name and the /// value expression. /// </summary> /// <exception cref="ExpressionEvaluationFailed"> /// Expression to declare the variable failed to evaluate. /// </exception> public async Task DeclareVariableAsync(IVariableInformation variable, string variableName, string valueExpression, NatvisScope natvisScope) { string scratchVar = ReplaceScopedNames(variableName, natvisScope?.ScopedNames, out bool ignore); VsExpression vsExpression = _vsExpressionCreator.Create(valueExpression, "") .MapValue(e => ReplaceScopedNames(e, natvisScope?.ScopedNames, out ignore)); // Declare variable and return it. Pure declaration expressions will always return // error because these expressions don't return a valid value. VsExpression createExpression = vsExpression.MapValue(e => $"auto {scratchVar}={e}; {scratchVar}"); if (variable.IsPointer || variable.IsReference) { variable = variable.Dereference(); if (variable == null) { string failMsg = $"Failed to dereference pointer: Name: {variableName}"; _logger.Error(failMsg); throw new ExpressionEvaluationFailed(failMsg); } } // TODO: Split the logic for LLDB and lldb-eval. Currently, LLDB is always // used to create a scratch variable (even if lldb-eval is the selected engine). IVariableInformation result = await variable.EvaluateExpressionAsync(variableName, createExpression); if (result != null && !result.Error && natvisScope != null) { // Result of 'auto {scratchVar}={e}; {scratchVar}' creates a copy of the scratch // variable. Evaluating '{scratchVar}' returns the reference to the original // variable. By using the original variable we make sure that the we always use its // up-to-date value. // TODO: Use RemoteFrame.FindValue to get the scratch variable. // EvaluateExpression method already is optimised for the case of fetching scratch // variables, but it isn't a convenient one. result = await variable.EvaluateExpressionAsync( variableName, _vsExpressionCreator.Create($"{scratchVar}", "")); if (result != null && !result.Error) { natvisScope.AddContextVariable(scratchVar, result.GetRemoteValue()); return; } } string msg = $"Failed to declare variable: Name: {variableName}, " + $"Expression: {valueExpression}"; string resultMessage = result?.ErrorMessage; if (!string.IsNullOrEmpty(resultMessage)) { msg += $", Info: {{{resultMessage}}}"; } _logger.Error(msg); throw new ExpressionEvaluationFailed(msg); }