public static ErrorVariableInformation LogAndGetEvaluationError( NatvisDiagnosticLogger logger, string natvisType, string parentType, string displayName, string errorCause) { logger.Error(() => $"Failed to evaluate {natvisType} node" + $" for {displayName}, type: {parentType}."); return(new ErrorVariableInformation(displayName, $"<Error> Reason: {errorCause}")); }
/// <summary> /// Loads Natvis files from string. This method is used in tests. /// </summary> /// <param name="natvisText">String with Natvis specification.</param> /// <param name="typeVisualizers">Type visualizers to initialize.</param> public void LoadFromString(string natvisText, ICollection <NatvisVisualizerScanner.FileInfo> typeVisualizers) { try { using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(natvisText))) { LoadFromStream(stream, "<From String>", typeVisualizers); } using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(natvisText))) { NatvisValidator validator = _validatorFactory.Create(); validator.Validate(stream); } } catch (InvalidOperationException ex) { // Handles invalid XML errors. // Don't allow natvis failures to stop debugging. var reason = ex.InnerException != null ? $"{ex.Message}: {ex.InnerException.Message}" : $"{ex.Message}"; TraceWriteLine(NatvisLoggingLevel.ERROR, $"Failed to load Natvis text. Reason: {reason}" + $"{Environment.NewLine}Stacktrace:{ex.StackTrace}"); } catch (Exception ex) { // TODO: Ensure 'unhandled' exceptions are logged at a higher level, such // as a global error handler. _logger.Error( $"Failed to load Natvis text. Reason: {ex.Message}" + $"{Environment.NewLine}Text:{Environment.NewLine}{natvisText}" + $"{Environment.NewLine}Stacktrace:{Environment.NewLine}{ex.StackTrace}"); throw; } }
/// <summary> /// Asynchronously returns a formatted string based on the format string context and /// variable provided. /// In case this method does not succeeded, it returns the fallback value specified. /// </summary> /// <param name="formatStringContext">The format string context that the formatted string /// should rely on</param> /// <param name="variable">The variable context used to evaluate expressions.</param> /// <param name="subexpressionFormatter">Delegate used to format subexpressions found /// within the string.</param> /// <param name="elementName">The Natvis element name that should be reported in logs. /// </param> /// <param name="fallbackValue">Fallback value used in case this method fails.</param> internal async Task <string> FormatStringAsync( FormatStringContext formatStringContext, IVariableInformation variable, Func <IVariableInformation, Task <string> > subexpressionFormatter, string elementName, Func <Task <string> > fallbackValue) { try { if (++_curFormatStringElementDepth > _maxFormatDepth) { return("..."); } foreach (var element in formatStringContext.StringElements) { try { // e.g. <DisplayString>{{ size={_Mypair._Myval2._Mylast - // _Mypair._Myval2._Myfirst} }}</DisplayString> if (!NatvisViewsUtil.IsViewVisible(variable.FormatSpecifier, element.IncludeView, element.ExcludeView) || !await _evaluator.EvaluateConditionAsync( element.Condition, variable, formatStringContext.NatvisScope)) { continue; } return(await FormatValueAsync(element.Value, variable, formatStringContext.NatvisScope, subexpressionFormatter)); } catch (ExpressionEvaluationFailed ex) { if (!element.Optional) { throw; } string expression = variable == null ? null : await variable.ValueAsync(); _logger.Verbose( () => $"Failed to evaluate natvis {elementName} expression" + $" '{expression}' for type " + $"'{variable?.TypeName}'. Reason: {ex.Message}"); } catch (Exception ex) when(ex is NotSupportedException || ex is InvalidOperationException) { _logger.Log(NatvisLoggingLevel.ERROR, $"Failed to format natvis {elementName}. " + $"Reason: {ex.Message}."); break; } catch (Exception ex) { _logger.Error(() => $"Failed to format natvis {elementName} for type" + $" '{variable?.TypeName}'. " + $"Reason: {ex.Message}.{Environment.NewLine}" + $"Stacktrace:{Environment.NewLine}{ex.StackTrace}"); throw; } } return(await fallbackValue.Invoke()); } finally { --_curFormatStringElementDepth; } }
/// <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); }