| File: System\Windows\Documents\TextMapOffsetErrorLogger.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // // Description: A trace logging manager for collecting debug information. // See doc comments below for more details. // // The underlying logging infrastructure does the right thing with // respect to user opt-in for CEIP. // using System.Collections; using System.Diagnostics.Tracing; namespace System.Windows.Documents { // The main definition for Speller is found in Speller.cs. // This file holds only the definition for TextMapOffsetErrorLogger. internal partial class Speller { /// <summary> /// <see cref="TextMap.MapOffsetToPosition(int)"/> sometimes fails with an assertion /// because the offset passed to it is outside the range [0.._textLength]. /// This logger will be used to collected contextual information just before such /// failures occur and log them so that the data coudl enable debugging and /// a future fix. /// </summary> /// <remarks> /// Once a future fix is done, this class and any references to it can be /// safely removed. /// /// <see cref="Speller"/> has been marked as partial to enable /// this class to be declared in a separate source file - reverting this /// change should also be considered. /// /// <see cref="TextMapOffsetErrorLogger"/> is declared as a nested type /// within Speller because we need access to the internals of <see cref="Speller.TextMap"/> /// </remarks> private partial class TextMapOffsetErrorLogger { private static readonly string TextMapOffsetError = "TextMapOffsetError"; private DebugInfo _debugInfo; public enum CalculationModes : int { ContentPosition = 0, ContextPosition = 1 }; /// <summary> /// In the calculations within <see cref="ExpandToWordBreakAndContext"/>, the smallest value /// expected for various indices and offsets is -1. Therefore -2 can act as a handy way to /// represent an "unset" or "uninitialized" value; /// </summary> private static readonly int UnsetValue = -2; public TextMapOffsetErrorLogger(LogicalDirection direction, TextMap textMap, ArrayList segments, int positionInSegmentList, int leftWordBreak, int rightWordBreak, int contentOffset) { _debugInfo = new DebugInfo { Direction = direction.ToString(), SegmentCount = segments.Count, SegmentStartsAndLengths = new SegmentInfo[segments.Count], PositionInSegmentList = positionInSegmentList, LeftWordBreak = leftWordBreak, RightWordBreak = rightWordBreak, ContentOffSet = contentOffset, ContextOffset = UnsetValue, CalculationMode = CalculationModes.ContentPosition, TextMapText = string.Join(string.Empty, textMap.Text), TextMapTextLength = textMap.TextLength, TextMapContentStartOffset = textMap.ContentStartOffset, TextMapContentEndOffset = textMap.ContentEndOffset }; for (int i = 0; i < segments.Count; i++) { var textSegment = segments[i] as SpellerInteropBase.ITextRange; if (textSegment != null) { _debugInfo.SegmentStartsAndLengths[i] = new SegmentInfo { Start = textSegment.Start, Length = textSegment.Length }; } } } public int ContextOffset { set { _debugInfo.ContextOffset = value; _debugInfo.CalculationMode = CalculationModes.ContextPosition; } } public void LogDebugInfo() { int offset = (_debugInfo.CalculationMode == CalculationModes.ContentPosition ? _debugInfo.ContentOffSet : _debugInfo.ContextOffset); // Error condition occurs when the offsets are outside the valid range. // The next call to TextMap.MapOffsetToPosition would fail an Invariant.Assert // We log relevant details here. if ((offset < 0) || (offset > _debugInfo.TextMapTextLength)) { var logger = MS.Internal.Telemetry.PresentationFramework.TraceLoggingProvider.GetProvider(); // The data we are about to log contains text being spell-checked. Although this is // not normally expected to be PII, it might very well contain such information. // We'd want to prevent Part A fields that might allow identification or // cross-correlation would be dropped from the event. var eventSourceOptions = new EventSourceOptions { Keywords = MS.Internal.Telemetry.PresentationFramework.TelemetryEventSource.MeasuresKeyword, Tags = MS.Internal.Telemetry.PresentationFramework.TelemetryEventSource.DropPii }; logger?.Write<DebugInfo>(TextMapOffsetError, eventSourceOptions, _debugInfo); } } /// <summary> /// Structure encoding contextual debug information /// during execution of TextMap.MapOffsetPosition /// coming from within ExpandToWordBreakAndContext. /// </summary> /// <remarks> /// Because this type is marked as <see cref="System.Diagnostics.Tracing.EventDataAttribute"/>, /// the properties must be either a simple framework type such as Int16, Int32, Int64, String, Guid, /// DateTime or DateTimeOffset, or an array of primitive types. /// </remarks> [EventData] private struct DebugInfo { #region Contextual Information from ExpandToWordBreakAndContext /// <summary> /// string representation of a <see cref="LogicalDirection"/> instance. /// Values could be "Backward" or "Forward" /// </summary> public string Direction { get; set; } /// <summary> /// Number of segments (i.e., words) returned by the call to <see cref="SpellerInteropBase.EnumTextSegments"/> /// </summary> public int SegmentCount { get; set; } /// <summary> /// Each segment returned by <see cref="SpellerInteropBase.EnumTextSegments"/> is an <see cref="SpellerInteropBase.ITextRange"/> /// instance containing <see cref="SpellerInteropBase.ITextRange.Start"/> and <see cref="SpellerInteropBase.ITextRange.Length"/> values. /// This array represents the list of Start, Length values for each segment. /// </summary> public SegmentInfo[] SegmentStartsAndLengths { get; set; } /// <summary> /// This is the value returned by <see cref="FindPositionInSegmentList"/>. /// The crash dumps we have seen typically have this value set to -1 or 0. /// </summary> public int PositionInSegmentList { get; set; } /// <summary> /// 'leftWordBreak' returned by <see cref="FindPositionInSegmentList"/> /// </summary> public int LeftWordBreak { get; set; } /// <summary> /// 'rightWordBreak' returned by <see cref="FindPositionInSegmentList"/> /// </summary> public int RightWordBreak { get; set; } /// <summary> /// 'contentOffset' value calculated within <see cref="ExpandToWordBreakAndContext"/> /// </summary> public int ContentOffSet { get; set; } /// <summary> /// 'contextOffset' value calculated within <see cref="ExpandToWordBreakAndContext"/>. /// This is only set and valid when <see cref="CalculationMode"/> is "ContextOffset". /// </summary> public int ContextOffset { get; set; } /// <summary> /// Within <see cref="ExpandToWordBreakAndContext"/>, <see cref="TextMap.MapOffsetToPosition"/> /// is called twice - once to calculate 'contentPosition' and again to calcualte 'contextPosition'. /// This property identifies which value is being calculated. /// </summary> public CalculationModes CalculationMode { get; set; } #endregion #region Contextual Information from TextMap instance /// <summary> /// <see cref="TextMap.Text"/> property of the 'textMap' instance /// </summary> public string TextMapText { get; set; } /// <summary> /// <see cref="TextMap.TextLength"/> property of the 'textMap' instance /// </summary> public int TextMapTextLength { get; set; } /// <summary> /// <see cref="TextMap.ContentStartOffset"/> property of the 'textMap' instance /// </summary> public int TextMapContentStartOffset { get; set; } /// <summary> /// <see cref="TextMap.ContentEndOffset"/> property of the 'textMap' instance. /// </summary> public int TextMapContentEndOffset { get; set; } #endregion } /// <summary> /// Represents the Start and Length fields of a <see cref="SpellerInteropBase.ITextRange"/> /// instance. /// </summary> [EventData] private struct SegmentInfo { public int Start { get; set; } public int Length { get; set; } } } } }