/// <summary> /// A function to handle breakpoints in asynchronous methods. Pass this as an argument to <see cref="Compile(Func{BreakpointInfo, bool}, Func{BreakpointInfo, Task{bool}})"/>. /// </summary> /// <param name="info">A <see cref="BreakpointInfo"/> object containing information about the location of the breakpoint and the current value of local variables.</param> /// <returns>A <see cref="Task"/> that completes when code execution resumes after the breakpoint.</returns> public async Task <bool> AsynchronousBreak(BreakpointInfo info) { using SemaphoreSlim semaphore = new SemaphoreSlim(0, 1); void resumeHandler(object sender, EventArgs e) { semaphore.Release(); } await Dispatcher.UIThread.InvokeAsync(() => { EditorControl.ActiveBreakpoint = info.BreakpointSpan.Start - PreSource.Length - 1; EditorControl.SetSelection(info.BreakpointSpan.End - PreSource.Length - 1, 0); BreakpointPanel.SetContent(info); BreakpointPanel.ResumeClicked += resumeHandler; this.FindAncestorOfType <Window>().Closing += resumeHandler; OpenSidePanel(); }); await semaphore.WaitAsync(); bool tbr = false; await Dispatcher.UIThread.InvokeAsync(() => { tbr = BreakpointPanel.IgnoreFurtherOccurrences; CloseSidePanel(); BreakpointPanel.ResumeClicked -= resumeHandler; this.FindAncestorOfType <Window>().Closing -= resumeHandler; EditorControl.ActiveBreakpoint = -1; }); semaphore.Dispose(); return(tbr); }
public void SetContent(BreakpointInfo breakpointInfo) { this.FindControl <StackPanel>("LocalVariablesContainer").Children.Clear(); this.FindControl <ToggleButton>("IgnoreFurtherOccurrences").IsChecked = false; foreach (KeyValuePair <string, object> kvp in (from el in breakpointInfo.LocalVariables orderby el.Key ascending select el)) { this.FindControl <StackPanel>("LocalVariablesContainer").Children.Add(new VariableExpander(kvp.Key, kvp.Value, breakpointInfo.LocalVariableDisplayParts[kvp.Key], this.FindControl <ToggleButton>("ToggleNonPublicVisibility")) { Margin = new Thickness(0, 0, 5, 0) }); } }
internal static Func <int, string[], string[], object[], Task> GetBreakpointAsyncFunction(Func <BreakpointInfo, Task <bool> > callback) { Dictionary <int, bool> suppressedBreakPoints = new Dictionary <int, bool>(); return(async(spanStart, variableNames, variableDisplay, variableValues) => { bool suppressed = suppressedBreakPoints.TryGetValue(spanStart, out bool suppressedInDictionary) && suppressedInDictionary; if (!suppressed) { BreakpointInfo info = new BreakpointInfo(spanStart, variableNames, variableDisplay, variableValues); suppressedBreakPoints[spanStart] = (await callback?.Invoke(info)) == true; } }); }
/// <summary> /// A function to handle breakpoints in synchronous methods. Pass this as an argument to <see cref="Compile(Func{BreakpointInfo, bool}, Func{BreakpointInfo, Task{bool}})"/>. To prevent deadlocks, this function will have no effect if called from the UI thread. /// </summary> /// <param name="info">A <see cref="BreakpointInfo"/> object containing information about the location of the breakpoint and the current value of local variables.</param> /// <returns><see langword="true" /> if further occurrences of the same breakpoint should be ignored; <see langword="false"/> otherwise.</returns> public bool SynchronousBreak(BreakpointInfo info) { if (!CheckAccess()) { EventWaitHandle waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); bool tbr = false; async void resumeHandler(object sender, EventArgs e) { await Dispatcher.UIThread.InvokeAsync(() => tbr = BreakpointPanel.IgnoreFurtherOccurrences); waitHandle.Set(); } Dispatcher.UIThread.InvokeAsync(() => { EditorControl.ActiveBreakpoint = info.BreakpointSpan.Start - PreSource.Length - 1; EditorControl.SetSelection(info.BreakpointSpan.End - PreSource.Length - 1, 0); BreakpointPanel.SetContent(info); BreakpointPanel.ResumeClicked += resumeHandler; this.FindAncestorOfType <Window>().Closing += resumeHandler; OpenSidePanel(); }); waitHandle.WaitOne(); Dispatcher.UIThread.InvokeAsync(() => { CloseSidePanel(); BreakpointPanel.ResumeClicked -= resumeHandler; this.FindAncestorOfType <Window>().Closing -= resumeHandler; EditorControl.ActiveBreakpoint = -1; }); return(tbr); } else { return(false); } }
/* public static Action<BreakpointInfo> SynchronousBreakRemote(BreakpointInfo info) * { * return (info) => * { * EventWaitHandle waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); * void resumeHandler(object sender, EventArgs e) * { * waitHandle.Set(); * } * * Dispatcher.UIThread.InvokeAsync(() => * { * EditorControl.ActiveBreakpoint = info.BreakpointSpan.Start - PreSource.Length - 1; * EditorControl.SetSelection(info.BreakpointSpan.End - PreSource.Length - 1, 0); * BreakpointPanel.SetContent(info); * BreakpointPanel.ResumeClicked += resumeHandler; * this.FindAncestorOfType<Window>().Closing += resumeHandler; * OpenSidePanel(); * }); * * waitHandle.WaitOne(); * * Dispatcher.UIThread.InvokeAsync(() => * { * CloseSidePanel(); * BreakpointPanel.ResumeClicked -= resumeHandler; * this.FindAncestorOfType<Window>().Closing -= resumeHandler; * EditorControl.ActiveBreakpoint = -1; * }); * } * }*/ /// <summary> /// Compile the source code to an <see cref="Assembly"/>. /// </summary> /// <param name="synchronousBreak">The function to handle synchronous breakpoints. If this is <see langword="null" />, these breakpoints will be skipped. If you want to enable the default UI for breakpoints, use <see cref="SynchronousBreak(BreakpointInfo)"/> (or a function that calls it after performing additional operations).</param> /// <param name="asynchronousBreak">The function to handle asynchronous breakpoints. If this is <see langword="null" />, these breakpoints will be skipped. If you want to enable the default UI for breakpoints, use <see cref="AsynchronousBreak(BreakpointInfo)"/> (or a function that calls it after performing additional operations).</param> /// <returns>An <see cref="Assembly"/> containing the compiled code, or <see langword="null"/> if the compilation fails, as well as a <see cref="CSharpCompilation"/> that also contains information about any compilation errors.</returns> public async Task <(Assembly Assembly, CSharpCompilation Compilation)> Compile(Func <BreakpointInfo, bool> synchronousBreak = null, Func <BreakpointInfo, Task <bool> > asynchronousBreak = null) { string source = null; await Dispatcher.UIThread.InvokeAsync(() => { source = this.FullSource; }); SourceText sourceText = SourceText.From(source); ImmutableList <MetadataReference> references; lock (ReferencesLock) { references = References; } SyntaxTree tree = await this.OriginalDocument.WithText(sourceText).GetSyntaxTreeAsync(); CSharpCompilation comp = CSharpCompilation.Create("compilation", new[] { tree }, references, this.CompilationOptions); string debuggerGuid = "_" + System.Guid.NewGuid().ToString("N"); if (synchronousBreak != null || asynchronousBreak != null) { List <(TextSpan, bool)> validBreakpoints = new List <(TextSpan, bool)>(); foreach (int i in source.AllIndicesOf(Utils.BreakpointMarker)) { SyntaxNode node = tree.GetRoot().FindNode(new TextSpan(i, 1)); SyntaxNode fullNode = node; while (fullNode.Parent != null && !fullNode.Kind().IsStatement()) { fullNode = fullNode.Parent; } if (fullNode.Kind().IsStatement()) { SyntaxNode methodNode = fullNode; while (methodNode.Parent != null && methodNode.Kind() != SyntaxKind.MethodDeclaration && methodNode.Kind() != SyntaxKind.ParenthesizedLambdaExpression && methodNode.Kind() != SyntaxKind.AnonymousMethodExpression) { methodNode = methodNode.Parent; } if (methodNode.Kind() == SyntaxKind.MethodDeclaration || methodNode.Kind() == SyntaxKind.ParenthesizedLambdaExpression || methodNode.Kind() == SyntaxKind.AnonymousMethodExpression) { bool isAsync = false; if (methodNode.Kind() == SyntaxKind.MethodDeclaration) { MethodDeclarationSyntax method = (MethodDeclarationSyntax)methodNode; foreach (SyntaxToken token in method.Modifiers) { if (token.Text == "async") { isAsync = true; } } } else if (methodNode.Kind() == SyntaxKind.ParenthesizedLambdaExpression) { ParenthesizedLambdaExpressionSyntax method = (ParenthesizedLambdaExpressionSyntax)methodNode; if (method.AsyncKeyword.Text == "async") { isAsync = true; } } else if (methodNode.Kind() == SyntaxKind.AnonymousMethodExpression) { AnonymousMethodExpressionSyntax method = (AnonymousMethodExpressionSyntax)methodNode; if (method.AsyncKeyword.Text == "async") { isAsync = true; } } if ((!isAsync && synchronousBreak != null) || (isAsync && asynchronousBreak != null)) { validBreakpoints.Add((new TextSpan(i, Utils.BreakpointMarker.Length), isAsync)); } } } } SyntaxTree debuggerTree = BreakpointInfo.GetDebuggerSyntaxTree(debuggerGuid); SemanticModel model = comp.GetSemanticModel(tree, false); for (int i = validBreakpoints.Count - 1; i >= 0; i--) { ILocalSymbol[] locals = (from el in model.LookupSymbols(validBreakpoints[i].Item1.Start) where el.Kind == SymbolKind.Local && !el.IsInaccessibleLocal(model, validBreakpoints[i].Item1.Start, tree.GetRoot().FindNode(validBreakpoints[i].Item1)) && !string.IsNullOrEmpty(el.Name) select(ILocalSymbol) el).ToArray(); string breakpointSource = BreakpointInfo.GetBreakpointSource(validBreakpoints[i].Item1, locals, model, debuggerGuid, validBreakpoints[i].Item2); sourceText = sourceText.Replace(validBreakpoints[i].Item1, breakpointSource); } string text = sourceText.ToString(); tree = await this.OriginalDocument.WithText(sourceText).GetSyntaxTreeAsync(); comp = CSharpCompilation.Create("compilation", new[] { debuggerTree, tree }, references, this.CompilationOptions); } using MemoryStream ms = new MemoryStream(); EmitResult result = comp.Emit(ms); if (!result.Success) { return(null, comp); } else { ms.Seek(0, SeekOrigin.Begin); Assembly assembly = Assembly.Load(ms.ToArray()); if (synchronousBreak != null || asynchronousBreak != null) { assembly.GetType(debuggerGuid + ".Debugger").InvokeMember("Breakpoint", BindingFlags.Static | BindingFlags.Public | BindingFlags.SetField, null, null, new object[] { BreakpointInfo.GetBreakpointFunction(synchronousBreak) }); assembly.GetType(debuggerGuid + ".Debugger").InvokeMember("BreakpointAsync", BindingFlags.Static | BindingFlags.Public | BindingFlags.SetField, null, null, new object[] { BreakpointInfo.GetBreakpointAsyncFunction(asynchronousBreak) }); } return(assembly, comp); } }